站在巨人的肩膀上好好学习SharedPreferences
2018-01-25 19:10
330 查看
站在巨人的肩膀上好好学习
本人小白菜,看了头条技术团队的文章 额假装有链接
特意read the fuck Souce code…
平时使用只会commit,看了大牛们的分析,才发现知之甚少
遗留:头条大神们建议的apply的优化,项目亲测后再说
注:欢迎大神们指出问题
本人小白菜,看了头条技术团队的文章 额假装有链接
特意read the fuck Souce code…
平时使用只会commit,看了大牛们的分析,才发现知之甚少
public void apply() { final MemoryCommitResult mcr = commitToMemory(); //放入任务子线程 final Runnable awaitCommit = new Runnable() { public void run() { try { //CountDownLatch .await()会暂停调用线程 //直到创建时设置的count值,减到0 //countDown()方法会进行减1的操作 mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; //添加 QueuedWork.addFinisher(awaitCommit); //另一个任务 Runnable postWriteRunnable = new Runnable() { public void run() { //执行上一个任务,即暂停调用线程 awaitCommit.run(); //清除 QueuedWork.removeFinisher(awaitCommit); } }; /** * 执行方法 * 第二个参数是runnable * 目前看已经具备了阻塞主线程的能力 * 那么阻塞主线程干什么了 **/ SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); //通知监听 notifyListeners(mcr); } //大致来说就是:把设置到内存的sp数据,转到了一个map中,这个map是用来写到磁盘的 //同时清空了,sp数据 private MemoryCommitResult commitToMemory() { long memoryStateGeneration; List<String> keysModified = null; Set<OnSharedPreferenceChangeListener> listeners = null; Map<String, Object> mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { //enqueueDiskWrite 在写入的方法中会做mDiskWritesInFlight-- //用来控制什么时候拷贝 if (mDiskWritesInFlight > 0) { //1.mModified 是sp保存的数据,保存到内存中 //2.在写入磁盘之前,先复制一份 mMap = new HashMap<String, Object>(mMap); } //真正写到磁盘时使用的map mapToWriteToDisk = mMap; mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList<String>(); listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (mLock) { boolean changesMade = false; if (mClear) { if (!mMap.isEmpty()) { changesMade = true; mMap.clear(); } mClear = false; } for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); if (v == this || v == null) { if (!mMap.containsKey(k)) { continue; } mMap.remove(k); } else { if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } //保存已设置过的内容到map,也能保存到mapToWriteToDisk? mMap.put(k, v); } changesMade = true; if (hasListeners) { keysModified.add(k); } } //清空内存数据 mModified.clear(); if (changesMade) { mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); } 添加到静态常量有序的runnable集合中 static final LinkedList<Runnable> sFinishers = new LinkedList<>(); 等下:研究这个东西的作用和始末 QueuedWork public static void addFinisher(Runnable finisher) { synchronized (sLock) { sFinishers.add(finisher); } } private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { //判定上一步传入的参数2是不是空,也就是区别是commit还是apply //commit 传null final boolean isFromSyncCommit = (postWriteRunnable == null); //真正写到磁盘的操作,是个runnable,那么调用这个任务的线程 //才是区别apply和commit的地方 final Runnable writeToDiskRunnable = new Runnable() { public void run() { //写入磁盘 synchronized (mWritingToDiskLock) { //流操作 writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } //启动阻塞主线程的操作 if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; //commit 同步执行 if (isFromSyncCommit) { boolean wasEmpty = false; //前面所有的apply和commit都会调用commitToMemory //即每次都会递增mDiskWritesInFlight synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } //当前线程执行,同步执行 if (wasEmpty) { writeToDiskRunnable.run(); return; } } //apply 异步执行 //加入到QueuedWork队列 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } 到这儿其实还是没有异步执行的影子,commit的同步执行已经追到 那现在这个QueuedWork就是问题关键了 发现QueuedWork从成员到方法都是静态的,那么好了,这个玩意是随进程走的。 要看apply最终被那个异步线程执行,就看那个线程run了 //handler消息机制,延时不延时的区别 public static void queue(Runnable work, boolean shouldDelay) { Handler handler = getHandler(); synchronized (sLock) { sWork.add(work); if (shouldDelay && sCanDelay) { handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } } //内部handler接收消息,处理,先卖个关子(Looper) private static class QueuedWorkHandler extends Handler { static final int MSG_RUN = 1; QueuedWorkHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { if (msg.what == MSG_RUN) { processPendingWork(); } } } //run了每一个任务 private static void processPendingWork() { long startTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } synchronized (sProcessingWork) { LinkedList<Runnable> work; synchronized (sLock) { work = (LinkedList<Runnable>) sWork.clone(); sWork.clear(); // Remove all msg-s as all work will be processed now getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); } if (work.size() > 0) { for (Runnable w : work) { w.run(); } } } } 还是没发现线程信息把,关子来了。。。looper 我们知道一个线程对应一个looper,looper可以说是线程的标识 那么看看这个looper /** * Lazily create a handler on a separate thread. * 惰性地在单独的线程上创建处理程序 * goole大神们还是很坦诚的。。。 */ private static Handler getHandler() { synchronized (sLock) { if (sHandler == null) { //开线程 HandlerThread handlerThread = new HandlerThread( "queued-work-looper",//线程名 Process.THREAD_PRIORITY_FOREGROUND);//优先级 handlerThread.start(); sHandler = new QueuedWorkHandler(handlerThread.getLooper()); } return sHandler; } } 最终apply这个写入磁盘的runnable就是在这个线程执行的 我们的主线程就是包名 adb shell ps -t 根据头条大神说 在activityThread 的handleStopService时,回调 4000 用 QueuedWork.waitToFinish(); 无限循环,依次获取QueuedWork中linklist中的runnable执行 其中sp中apply放入的那个runnable有个CountDownLatch .await() 所以主线程被阻塞 public static void waitToFinish() { try { while (true) { Runnable finisher; synchronized (sLock) { finisher = sFinishers.poll(); } if (finisher == null) { break; } finisher.run(); } } finally { sCanDelay = true; } } 那么这个释放线程阻塞的位置在哪? 减到0就释放阻塞 void setDiskWriteResult(boolean wasWritten, boolean result) { //减1 writtenToDiskLatch.countDown(); } writeToFile()方法中进行 .... mcr.setDiskWriteResult(false, true); 小结:sp的apply流程 我们存了很多数据putInt()...... sp保存到了内存中 mModified 调用apply(),将写数据到磁盘的任务,发给QueuedWork的线程 "queued-work-looper" 并设置了主线程的线程锁awaitCommit,有序的保存在QueuedWork中(QueuedWor在进程启动的时候初始化)QueuedWork.addFinisher(awaitCommit) 在service停止时,activityThread -> H ->handleStopService ,当前是在主线程中,包名 在ams 停止服务之前, QueuedWork.waitToFinish(), 开启sp的工作,阻塞线程, 写入磁盘 写入完毕,释放线程锁,主线程继续流程
遗留:头条大神们建议的apply的优化,项目亲测后再说
注:欢迎大神们指出问题
相关文章推荐
- 站在巨人的肩膀上重新学习CPP(2)
- 站在巨人的肩膀上重新学习CPP(1)
- Android提醒Dialog、Toast和Snackbar ---站在巨人的肩膀上学习总结
- Android属性动画(三) ------ 站在巨人的肩膀上学习总结
- 站在巨人的肩膀上学习Android开发
- 站在巨人的肩膀上重新学习CPP(1)
- 站在巨人的肩膀上学习
- 站在巨人的肩膀上学习
- 站在巨人的肩膀上学习Android开发
- 站在巨人的肩膀上--学习用例图(UML)
- 站在巨人肩膀上学习【转】[精华] 常用算法设计方法(转贴)
- Android LayoutInflater原理分析,带你一步步深入了解View(一) ---站在巨人的肩膀上学习总结
- 站在巨人的肩膀上学习Android开发
- 不重复造轮子 (find 好轮子,Learn It --- 学习站在巨人的肩膀上,才能看的更远)
- 站在巨人的肩膀上学习Android开发
- caffe学习日记--lesson8:站在巨人的肩膀上, denny的学习专栏——徐其华——caffe
- Android属性动画(二) ------ 站在巨人的肩膀上学习总结
- 站在巨人的肩膀上开发游戏(3) -- Orx 文字输出的简化及进一步学习
- Android视图绘制流程完全解析,带你一步步深入了解View(二) ---站在巨人的肩膀上学习总结
- Android属性动画(一) ------ 站在巨人的肩膀上学习总结