创建SharedPreferences
- context.getSharedPreferences,其内部是执行ContextImpl.getSharedPreferences中创建的,根据name查看是否有创建过文件,有则直接服用已存在的SharedPreferencesImpl对象,如果没有则重新new 一个,并添加到缓存中
@Override public SharedPreferences getSharedPreferences(String name, int mode{ ... File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode); } @Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { ... sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } ... return sp; }
put相关方法
- 通过sp.edit()获取EditorImpl的对象,然后对其方法进行操作,将key- value保存到map中
public Editor edit() { synchronized (mLock) { awaitLoadedLocked(); } return new EditorImpl(); } public Editor putString(String key, @Nullable String value) { synchronized (mEditorLock) { mModified.put(key, value); return this; } }
commit
- commit操作,是将put好的数据提交到内存中以及磁盘中;先是将put在EditorImpl中的map数据缓存到SharedPreferencesImpl中,然后在当前线程中执行写入磁盘的任务,详细可看下面代码分析
public boolean commit() { ... // 注释1:这是添加内存缓存,将EditorImpl中put的数据缓存到SharedPreferences中 MemoryCommitResult mcr = commitToMemory(); // 注释2:排队执行写入磁盘任务 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null); ... return mcr.writeToDiskResult; } private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) { // 注释3:重点区分apply的流程点,postWriteRunnable为null,表示是commit操作 final boolean isFromSyncCommit = (postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } ... } }; if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { // 注释4:根据isFromSyncCommit,让commit操作的写入直接在当前线程中处理 writeToDiskRunnable.run(); return; } } ... }
apply
- apply操作,也是先将数据缓存到SharedPreferencesImpl中,然后执行磁盘写入操作,不一样的是,apply执行写入操作时,是在子线程中执行的,内部有个HandlerThread,虽说apply操作在子线程,但是也存在ANR问题,当处理的任务过多时,恰好这时候页面跳转,在ActivityThread.handlePauseActivity()中,会去将未处理的任务全部在当前线程中执行,这样,就导致handlePauseActivity()执行时间过长,导致ANR
public void apply() { // 注释1:同commit先缓存到SharedPreferences中 final MemoryCommitResult mcr = commitToMemory(); ... QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; // 注释2:执行磁盘写入,并传入postWriteRunnable SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); notifyListeners(mcr); } private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) { // 注释3:重点,因为apply传入了postWriteRunnable,所以此处为false final boolean isFromSyncCommit = (postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } // 注释4:由于isFromSyncCommit为false,所以上面逻辑不走 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } public static void queue(Runnable work, boolean shouldDelay) { // 注释5:创建一个HandlerThread并根据他的looper创建一个handler对象 Handler handler = getHandler(); synchronized (sLock) { // 注释6:用于当页面切换时,在主线程中执行任务 sWork.add(work); // 注释7:发送消息到子线程中处理 if (shouldDelay && sCanDelay) { handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } } private static Handler getHandler() { synchronized (sLock) { if (sHandler == null) { HandlerThread handlerThread = new HandlerThread("queued-work-looper", Process.THREAD_PRIORITY_FOREGROUND); handlerThread.start(); // 注释8:创建一个绑定子线程looper的handler sHandler = new QueuedWorkHandler(handlerThread.getLooper()); } return sHandler; } } 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(); } } } 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(); getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); } // 注释9:在当前线程中执行任务 if (work.size() > 0) { for (Runnable w : work) { w.run(); } } } } public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, PendingTransactionActions pendingActions, String reason) { ... if (r.isPreHoneycomb()) { // 注释10:在页面切换时,内部会执行processPendingWork方法,即将一些runnable任务放到主线程中 QueuedWork.waitToFinish(); } mSomeActivitiesChanged = true; } }
总结
- commit提交时会返回写入是否成功,apply无返回值,因为时异步的
- commit提交写入磁盘时在当前线程中,如果遇到数据量大,容易ANR
- apply提交是异步的,虽在子线程中,但是也不排除ANR的风险,当此时在页面切换时,任务尚未执行完,会将任务放到主线程中执行,则会造成ActivityThread.handlePauseActivity执行时间耗时太长,导致ANR