SharedPreference是Android开发中经常用到的一个数据持久化类, 我们可以把一些需要持久化的数据放到一个指定的 Preference文件中,并持久化到磁盘上以xml形式存储起来。 这个xml文件对于开发者来说基本算是透明的,开发者只需要面对 Preference 所需要的文件名。
关于SharedPreference的原理可以分读和写两部分理解,今天我们先说关于写的这部分。 而关于读就相对比较复杂一些,这里面会涉及到线程和进程等各方面的细节问题,后面我们在仔细分析。
总所周知Android提供了这两个方法来写入数据,一般来说写入数据的步骤是这样的
SharedPreferences pref = mContext.getSharedPreferences(Const.SHARED_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
Gson gson = new Gson();
String json = gson.toJson(info);
editor.putString(Const.PREFER_NAME, json);
editor.apply();
//editor.commit();
这里先给结论, · apply的写磁盘是异步行为 · commit的写磁盘是同步行为 · 两者在写磁盘前都会先同步的写到内存缓存中
首先要理解 SharedPreference有一个两级缓存系统,包括了内存缓存和磁盘缓存。 它用一个 HashMap对象mMap保存内存缓存,每次写的时候都会先更新这个对象的数据。 下面是调用 apply的简化后的源码
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
....
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
逐句分析, 先调用了commitToMemory()同步修改后的数据到内存中, 然后用Runnable把写磁盘的操作包了起来,并放到一个队列中进行异步处理, 最后通知监听者数据写入完成(因为并发的原因可能不一定写完磁盘)
下面是commit的代码
public boolean commit() {
long startTime = 0;
MemoryCommitResult mcr = commitToMemory();// <-- 一样的调用
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
对比apply()的代码就比较明显了, 虽然在调用 commitToMemory()的时机上是一样的,但是后面写入磁盘是个同步操作, 这也就导致了在主线程写入数据可能发生anr的问题。
apply()和commit()是异步和同步的差异, 两者都会先写入到内存缓存, 在主线程写入数据建议用 apply(), 而需要调用 commit()的话就建议在子线程中了。