我们先看一下SharedPreferences (下文用 SP 简写替代)的用法。
SharedPreferences preferences = getSharedPreferences("name", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("key", "value");
editor.commit(); // 或者 editor.apply();
第一步是 获取 sp 对象;
第二步是 获取 Editor 对象;
第三步是 将要存放的内容以 key, value 的型式放进去
第四步是 使用 Editor.commit() 或者 Editor.apply() 方法提交保存的内容,这两个方法的区别我们在后面会讲到;
下面我们看一下详细的内容
第一步 获取 sp 对象
Context 只是一个接口,真正实现的是它的实现类 ContextImpl, 所以
SharedPreferences preferences = Context.getSharedPreferences("name", Context.MODE_PRIVATE);
方法,最终调用的是 ContextImpl 里面的方法
SharedPreferences preferences = ContextImpl.getSharedPreferences("name", Context.MODE_PRIVATE);
看看在 ContextImpl 里面的 getSharedPreferences(…) 方法的源码
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
// sSharedPrefs 以 PackageName 为 key 保存 ArrayMap<String, SharedPreferencesImpl>();
// 而 ArrayMap<String, SharedPreferencesImpl>() 使用文件名 为 key 的 Map.
sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
}
final String packageName = getPackageName();
ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
// 可以以 null 作为文件名, null.xml
if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
// 从 ArrayMap<String, SharedPreferencesImpl>() , 中取出 SharedPreferenceImpl.
sp = packagePrefs.get(name);
if (sp == null) { // 第一次的时候 sp 是为空的, 创建一个新的 SharedPreferencesImpl,
// 放入 Map 中, 并返回 SharedPreferencesImpl 对象
File prefsFile = getSharedPrefsFile(name);
sp = new SharedPreferencesImpl(prefsFile, mode);
packagePrefs.put(name, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly();
}
// 非第一次的时候直接返回 SharedPreferencesImpl 对象
return sp;
}
说明:
1.存放的文件名可以为空,它会生成一个以 null 为文件名的 null.xml 文件;
2.SharedPreferences 只是接口,它的是实现类是 SharedPreferencsImpl, 所以getSharedPreferences 返回的是
SharedPreferencesImpl.
第一次使用 sp 的时候会生成一个新的 SharedPreferencesImpl 对象
SharedPreferencesImpl(File file, int mode) {
mFile = file; // 正式文件
mBackupFile = makeBackupFile(file); // 备份文件
mMode = mode; // 类型
mLoaded = false; // 加载标记位
mMap = null; // 存放内容的 Map
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
synchronized (SharedPreferencesImpl.this) {
loadFromDiskLocked();
}
}
}.start();
}
// 从磁盘中读取数据
private void loadFromDiskLocked() {
if (mLoaded) {
return;
}
// 如果备份文件存在,则将 mFile 文件删除,然后将备份文件的名称改为 mFile 文件的名称
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
Map map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(new FileInputStream(mFile), 16*1024);
// 把文件流读取到数据,放到 Map 中
map = XmlUtils.readMapXml(str);
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
}
// 加载标志位
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<String, Object>();
}
notifyAll();
}
private static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
说明:
1.生成 SharedPreferencesImpl 对象时,回到调用 startLoadFromDisk() 方法,在该方法里面,加了同步锁,并且启动新
线程,在新线程中调用 loadFromDiskLocked() 方法;
2. loadFromDiskLocked() 才是真正从磁盘中读取数据的方法,通过 XmlUtils 工具类将数据存放到 Map 中。
第二步是 获取 Editor 对象
Editor 也是一个接口,它的实现类是 EditorImpl;
第三步是 将要存放的内容以 key, value 的型式放进去
Editor.putXXX(…) 方法调用的是 EditorImpl.putXXX(…) 方法;
这里以 EditorImpl.putString(…) 方法分析,其他方法都是一样,只是返回值不一样而已;
// 放入到 Map 中
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
说明:
mModified 是一个 HashMap , 是 EditorImpl 类的一个成员变量
private final Map<String, Object> mModified = Maps.newHashMap();
第四步 提交数据
我们先看 EditorImpl.commit() 方法
public boolean commit() {
// 将前面 put 的值 写到内存中,其实是拷贝到 mMap 中,同时修改一些 mcr 的值
MemoryCommitResult mcr = commitToMemory();
// 写到磁盘中
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);
try {
// 等待前面的写进磁盘等工作的完成,使用 CountDownLatch 等待
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
// 回调接口
notifyListeners(mcr);
// 返回执行结果,boolean 类型
return mcr.writeToDiskResult;
}
说明:
1.将数据提交到内存中;
2.将数据写进磁盘中;
3.使用 CountDownLatch 等待写磁盘的工作;
4.回调接口;
5.返回执行结果。
commit() 是等数据写进磁盘的工作完成之后,才返回一个执行结果
EditorImpl.apply() 方法
public void apply() {
// 提交到内存
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
// 等待线程执行完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
// 只是放到链表队列中 ConcurrentLinkedQueue<Runnable>(),,并没有执行
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);
}
说明:
1.将数据提交到内存;
2.将等待任务放入到链表队列中;
3.将数据写进磁盘中,同时启动等待任务;
4.回调
apply() 方法是没有返回值的
那我们该怎样选择这两个方法呢,官方的文档(链接)上面有提到
Unlike commit()
, which writes its preferences out to persistent storage synchronously, apply()
commits its changes to the in-memorySharedPreferences
immediately but starts an asynchronous commit to disk and you won’t be notified of any failures. If another editor on thisSharedPreferences
does a regular commit()
while a apply()
is still outstanding, the commit()
will block until all async commits are completed as well as the commit itself.
If you don’t care about the return value and you’re using this from your application’s main thread, consider using apply()
instead.
apply() 方法提交数据是异步的,它只管提交,但是不知道提交是否成功;commit() 方法是同步提交数据,它会等待数据写进磁盘后才返回结果,这个结果可能是提交成功也或者是失败的。commit() 方法是在 UI 线程中写数据的,可能会对 UI 造成卡顿现象,而 apply() 中写数据进磁盘是在线程池中执行。
如果不在意返回结果,在 UI 线程时,尽量用 apply() 方法。
commit() 和 apply() 方法里面都调用了 commitToMemory() 方法。这个方法只是修改了 mcr 的一些值,同时将 mModified 的值拷贝到 mMap 中,并清空 mModified.
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (this) {
if (mClear) {
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
}
// 将 mModified 中的值拷贝到 mMap 中
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
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;
}
}
mMap.put(k, v);
}
mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
}
// 清空 mModified
mModified.clear();
}
}
return mcr;
}
写进磁盘的工作
* commit 直接在当前线程中写磁盘,而 apply 是在线程池中执行写磁盘动作
* 如果是在 UI 中执行 commit ,则可能会造成 UI 的卡顿。所以,如果不需要返回
* 结果用 apply 方法
*/
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) { // commit 时候为空, apply 时执行
postWriteRunnable.run();
}
}
};
final boolean isFromSyncCommit = (postWriteRunnable == null);
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) { // commit 时执行
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
// 在 commitToMemory 中已经 +1 ,所以,wasEmpty = true;
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
// 执行写进磁盘任务
writeToDiskRunnable.run();
return;
}
}
// apply 时用线程池执行
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
说明:
1.commit() 方方法是会在当前线程中直接执行 writeToDiskRunnable 任务,而 apply() 则是放进线程池中执行。
从这里我们可以了解到,在UI线程中使用 commit() 方法可能会造成 UI 线程的卡顿;
2. applay() 方法在线程池中执行了写磁盘任务 writeToDiskRunnable,和等待任务 postWriteRunnable.
回调 notifiyListeners 是在主线程中回调的
// 确保在 UI 线程中进行回调
private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || mcr.keysModified == null ||
mcr.keysModified.size() == 0) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
} else {
// Run this function on the main thread.
ActivityThread.sMainThreadHandler.post(new Runnable() {
public void run() {
notifyListeners(mcr);
}
});
}
这样,整个存数据的工程就已经介绍了,我们看看存数据的过程。取数据的过程,这要看 EditorImpl.getXXX(…)
public String getString(String key, String defValue) {
synchronized (this) {
// 等待从文件中读取数据完成,并将数据放置到 mMap 中
awaitLoadedLocked();
// 等待完成,则可以从 mMap 中读取数据
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
awaitLoadedLoacked() 方法就是等待,等待前面的数据准备好
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
// 会等待 loadFromDiskLocked() 方法中 mLoaded 置为 true 就不会等待
while (!mLoaded) {
try {
wait();
} catch (InterruptedException unused) {
}
}
}
SharedPreferences 源码的分析过程完成了。