SharedPreferences源码分析
今天偶然看见一篇讲解SharedPreferences(以下简称SP)原理的文章,于是怀着前人种树后人乘凉的心情进去看了,发现写得不是很清楚,很多细节一笔带过,于是为了巩固一下基础就去百度了一下,发现百度上也没有让自己满意的文章。虽然SP很简单,但是为了锻炼一下写博客的技巧,还是决定看看源码记录一下分析结果。此处分析的SDK版本为android-27。
1.SP的使用
我们都知道,SP是以XML文件方式存储的,我们先来看看SP的基本使用方法。如下所示
SharedPreferences mSp = getSharedPreferences("filename", Context.MODE_PRIVATE);
SharedPreferences mSp2 = getPreferences(Context.MODE_PRIVATE);
// 监听
SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// 监听值变化的key
}
};
mSp.registerOnSharedPreferenceChangeListener(listener);
mSp.unregisterOnSharedPreferenceChangeListener(listener);
// 取
mSp.getBoolean("key1", false);
mSp.getInt("key2", 0);
mSp.getFloat("key3", 0);
mSp.getLong("key4", 0);
mSp.getString("key5", "");
mSp.getStringSet("key6", null);
Map<String, ?> datas = mSp.getAll();
// 存
SharedPreferences.Editor editor = mSp.edit();
editor.putBoolean("key1", true);
editor.putInt("key2", 0);
editor.putFloat("key3", 0);
editor.putLong("key4", 0);
editor.putString("key5", "");
editor.putStringSet("key6", null);
// boolean isSuccess = editor.commit();
// editor.apply();
根据以上代码,我们就产生了以下两个问题:
(1) getSharedPreferences(String filename, int mode)和getPreferences(int mode)有什么区别。
(2) Editor的apply()和commit()有什么区别。
2.源码分析
1.SP实例的获取
首先我们来看SP的获取方式:
Activity.java
/**
* Retrieve a {@link SharedPreferences} object for accessing preferences
* that are private to this activity. This simply calls the underlying
* {@link #getSharedPreferences(String, int)} method by passing in this activity's
* class name as the preferences name.
*
* @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default
* operation.
*
* @return Returns the single SharedPreferences instance that can be used
* to retrieve and modify the preference values.
*/
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
ContextWrapper.java
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
mBase为ContextImp的实例,是在由Activity创建时传入的,而ContextImp的源文件在…/android-sdk/sources/android-27/android/app目录下,我们来看看其代码实现:
ContextImp.java
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
/*
省略不相关代码
*/
File file;
file = getSharedPreferencesPath(name);
return getSharedPreferences(file, mode);
}
其中getSharedPreferencesPath()方法为获取对应的xml文件,具体实现如下
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
@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) {
checkMode(mode);
/* 省略部分代码 */
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
/*
省略部分代码
*/
return sp;
}
代码逻辑很清晰,就是从缓存里找到SP对象的实例再返回,一个文件对应一个SP实例。这里的checkMode()方法主要是Android N以后不再支持MODE_WORLD_WRITEABLE和MODE_WORLD_READABLE两种mode。我们也可以看出SP的唯一实现为SharedPreferencesImpl,稍后将解析。我们先来看看getSharedPreferencesCacheLocked()这个方法。
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
sSharedPrefsCache是一个ArrayMap<String,ArrayMap<File, SharedPreferencesImpl>>类型的变量,并且sSharedPrefsCache的key总是应用包名,这就使应用间不能共享SP文件。以上就是关于SP获取的全部内容,下面我们来看看SP的实现SharedPreferencesImpl和SP.Editor的实现EditorImp,来分析一下数据时怎样被记录的。
2.SharedPreferencesImpl和EditorImp
实现文件…/android-sdk/sources/android-27/android/app/SharedPreferencesImpl.java
首先来看看SP的主要代码,首先mMap是对XML文件里的数据的缓存,File即对应的XML文件,mMode对应为文件的读写权限。而mListener对象是监听key值变化的回调的集合,其Object值没有任何实际意义。
final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
@GuardedBy("mLock")
private Map<String, Object> mMap;
private final File mFile;
private final int mMode;
@GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
// 创建时将XML文件里的数据加载到mMap里,掉用getXXX方法时,获得的是缓存值
startLoadFromDisk();
}
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.put(listener, CONTENT);
}
}
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.remove(listener);
}
}
public Editor edit() {
// 省略部分代码
return new EditorImpl();
}
/*
省略getXXXX()部分代码
*/
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
/* 写入文件部分省略 */
}
}
好了我们再来看看EditorImp的主要代码,EditorImp是SP的内部类,EditorImp就比较简单了,主要是对编辑过的key-value数据做保存并封装成MemoryCommitResult(以下简称mcr)对象,再最终掉用到外部类一系列方法将mcr写入到文件。
public final class EditorImpl implements Editor {
@GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
@GuardedBy("mLock")
private boolean mClear = false;
/* 省略一系列putXXX方法 */
public void apply() { ... }
private MemoryCommitResult commitToMemory() { ... }
public boolean commit() { ... }
private void notifyListeners(final MemoryCommitResult mcr) { ... }
}
EditorImp主要就以上四个方法,其中commitToMemory()方法是将数据保存到SP的缓存对象mMap中,并封装成相应的结果mcr,这里我们并不关心其具体实现,我们最关心的是apply()和commit()两个方法到底做了什么。
先来看看commit()方法:
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;
} finally {
....
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
commit()方法首先将数据写入到缓存,再将封装后的mcr传给外部类写入内存,最后再返回一个boolean值,这个值代表是否成功写入到文件。那么我们看看apply()方法又有什么不同呢
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
....
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
....
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
我们发现,apply()和commit()的区别除了返回值以外,就是掉用外部类SharedPreferencesImpl.this.enqueueDiskWrite(…)传的值不一样,关于mcr.writtenToDiskLatch这是一个CountDownLatch对象的实例,主要作用是控制线程同步,保证文件读写安全。SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable)具体代码如下:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
}
....
};
if (isFromSyncCommit) {
....
writeToDiskRunnable.run();
return;
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
这里很显然,如果是commit方式,就直接同步执行外部类的writeToFile(…)方法并return,如果是apply()方式,就将writeToDiskRunnable传入QueuedWork.queue()方法继续执行,关于QueuedWork.queue()我们已经没有必要再跟踪了,后面发生的事情基本就是把writeToDiskRunnable放到一个HandlerThread中去执行,也就是异步执行。到此我们就是分析完了SP操作数据的整个过程,此处还应该注意的是notifyListeners(mcr),代码如下:
private void notifyListeners(final MemoryCommitResult mcr) {
....
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);
}
});
}
}
显然,监听的回调是在主线程中完成的。
3.总结
SP的整个过程就是,再创建时加载XML文件中的数据并缓存到一个Map对象中,读取数据时直接在mMap中读取,当写入数据时,先将数据写入mMap缓存中,写入成功后再将数据更新到文件。由以上的源码分析,我们可以得出以下结论:
- (1) SP是通过XML文件方式进行存储的。
- (2) 应用间不能共享SP。
- (3) getPreferences(…)本质是调用的getSharedPreferences(…),并且只能在调用的Activity内共享数据。
- (4) Editor负责将数据保存到缓存,写入文件操作由SP完成。
- (5) Editor的commit()和apply()的本质区别是,commit()为同步操作并返回是否成功写入文件的结果,而apply()是异步执行写入文件操作没有返回值,而写入缓存的操作两者没有区别。
- (6) 监听事件在主线程中回调。
以上,就是SP的基本内容。第一次写分析源码的博文,有错误欢迎指出。
上一篇: 复制内容到剪切板