SharedPreferences在安卓中是最常用的保存数据 的方式。
下面就了解一下SharedPreferences的commit和apply这两个提交数据的方法。
其实SharedPreferences和我们常用的Context都是接口,所以具体的实现方法其实是在SharedPreferencesImpl和ContextImpl中的
下面看一下源码:
public boolean commit() { MemoryCommitResult mcr = commitToMemory();//获取需要保存的数据集合 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */);//提交数据,并进行保存操作 try { mcr.writtenToDiskLatch.await();//将当前线程挂起,等待写入线程操作执行完成 } catch (InterruptedException e) { return false; } notifyListeners(mcr);//通知数据 return mcr.writeToDiskResult; }可以看到最主要的写入数据到本地的方法就是enqueueDisWrite,然后闭锁等待所有文件写入成功,最后在返回写入结果。再看apply方法
public void apply() { final MemoryCommitResult mcr = commitToMemory();//提取需要保存的数据 final Runnable awaitCommit = new Runnable() { public void run() { try { mcr.writtenToDiskLatch.await();//挂起该子线程,等待写入数据线程执行 } catch (InterruptedException ignored) { } } }; QueuedWork.add(awaitCommit);//将等待线程加到工作队列中 Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.remove(awaitCommit); } };//该线程在数据写入成功之后会被执行。 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // Okay to notify the listeners before it's hit disk // because the listeners should always get the same // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(mcr); }可以看到执行enqueueDiskWrite方法中传入了一个子线程runnable,接下来就是最重要的enqueueDiskWrite方法:
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr);// } synchronized (SharedPreferencesImpl.this) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } };//开启子线程写入数据到本地 final boolean isFromSyncCommit = (postWriteRunnable == null); // Typical #commit() path with fewer allocations, doing a write on // the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (SharedPreferencesImpl.this) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run();//commit时直接在当前线程执行写数据任务 return; } } QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);//apply时,开启单个线程池执行任务 }
变量isFromSyncCommit的值取决于是否传入一个子线程,如果传入了,就直接在当前线程中(有可能是主线程)执行写数据,然后返回,如果没有传入,就会通过QueuedWork.singleThreadExecutor创建一个单线程的线程池来执行写数据的线程.所以commit方法有可能会在主线程中执行造成线程阻塞,但是会同步读写数据;而apply则是直接将写数据丢到一个子线程中去执行.
因为apply中首先会将等待写入数据的线程加入到QuereWork中,
public static void waitToFinish() { Runnable toFinish; while ((toFinish = sPendingWorkFinishers.poll()) != null) { toFinish.run(); } }当中的waitToFinish会轮寻线程,当然这个方法在activity的onPause中会调用,所以当并发apply的时候在这里轮循就可能导致anr异常。
其次,apply方法如果在应用进程退出的时候,因为开启了子线程执行,所以可能不会正确的保存数据。例如当apply之后调用
android.os.Process.killProcess(android.os.Process.myPid());
然后在进来的时候就会发现数据并没有被正确保存,但是commit确实可以的