在Android中保存数据的方式之一就是使用 SharedPreferences , 因为会用到这个类,所以想分析下它的源码实现。
1. 获取SharedPreference实例。
val sharedPreference = getSharedPreferences("main", Context.MODE_PRIVATE)
sharedPreference.edit().putBoolean("init", true).apply()
通常使用方式是通过 Context 获取一个 SharedPreferences 实例,下面看下具体的源码。
1.1 通过 ContextImpl 来获取SharedPreferencesImpl
class:: ContextImpl
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// 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.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
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 File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
- ContextImpl内部全局变量 mSharedPrefsPaths 是一个 ArrayMap<String, File> 的map容器,其中的key对应的是String类型的 name, value 对应的File是SharedPreferences保存数据的地方。
- SharedPreferences 中的文件是 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;
}
}
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();
}
return sp;
}
@GuardedBy("ContextImpl.class")
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类型,维护了 File 和 SharedPreferencesImpl 的对应关系。因为是静态变量,所以 SharedPreferencesImpl 创建后会一直缓存在 sSharedPrefsCache 这个ArrayMap中。
- 如果 sSharedPrefsCache 没有这个File对应的 SharedPreferencesImpl 对象,则直接创建。
name -> file -> SharedPreferences实例。 上面的代码描述了一种转换关系,把name转换成file, 再从缓存中取 SharedPreferences 实例。这比用户传 file 的方式更简便。
1.2 SharedPreferencesImpl 构造方法实现
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
构造方法中的一些参数需要描述一下他们的作用:
- mFile: 这个是SharedPreferences保存数据的地方,是一个xml类型的文件
- mLoaded: 这个和mMap有关,startLoadFromDisk() 方法会读取 mFile 里面的内容并保存到 mMap 中,加载过程中 mLoaded = false, 加载完之后 mLoaded = true.
@UnsupportedAppUsage
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
可以看到,调用 startLoadFromDisk() 方法时会先设置 mLoaded = false, 然后创建一个线程去读取 mFile 里面的内容。
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
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<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;
// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
mLock.notifyAll();
}
}
}
- loadFromDisk() 方法会先判断 mLoaded 是否为 true , 如果为 true 则直接返回,代表这个文件是已经加载完了的,因为SharedPreferences是线程共享的,所以存在已经被其他线程加载完的可能。
- 通过XmlUtils加载文件流,创建一个 map 对象,加载完之后设置 mLoaded = true. 并把这个 map 对象赋值给 mMap.
总结如下:
- SharedPreferences 是一种永久存储数据的方式,通过 xml 文件,保存key-value的对应关系。
- 创建SharedPreferencesImpl实例时会调用 startLoadDisk() 读取 mFile 中保存的数据,这个操作是在子线程中实现的.
- mMap保存 mFile 中的数据,可以提高 SharedPreferences 获取key对应的value的速率。
2. SharedPreferences 保存和读取数据
val sharedPreference = getSharedPreferences("main", Context.MODE_PRIVATE)
sharedPreference.getBoolean("init", false)
sharedPreference.edit().putBoolean("init", true).apply()
上面代码分别是 get 和 set 的使用方式现在看下源码实现
2.1 getXXX() 实现
@Override
public boolean getBoolean(String key, boolean defValue) {
synchronized (mLock) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
@GuardedBy("mLock")
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();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
这里就说下 mLoaded 为false的状态,这个状态下在构造方法中创建的线程还没有完成读取 mFile 的逻辑,这种情况下,mMap 还为 null, 所以是读取不到对应的 value 的。所以主线程会被阻塞,直到 SharedPreferencesImpl-load
线程完成加载逻辑。之后再从 mMap 中取 value.
2.2 setXXX() 实现
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
第一步是调用 sharedPreferences.edit() 获取一个 Editor 对象,这个对象的实现类是 EditorImpl , 而且这个过程也要等 SharedPreferencesImpl-load
加载完。
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
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");
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(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);
}
- putxxx() 方法会把新的一对 key-value 保存到 mModified 中。
- commitToMemory() 会获取 SharedPreferencesImpl 中的mMap对象,对比 mModified 中的key, 如果有 mMap 中没有的 key 或者 key相同但 value 不同的情况下,MemoryCommitResult 中的 memoryStateGeneration 就会加1,代表有变动。
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
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();
}
}
};
// Typical #commit() path with fewer allocations, doing a wri
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
- 如果 postWriteRunnable == null. 那么 isFromSyncCommit = true, 此时就会在当前线程调用 writeToDiskRunnable.run() 这是一个把 MemoryCommitResult 中的改动同步到 mFile 的过程,一个IO操作,当调用 commit() 时,postWriteRunnable = null.
- apply() 方法会传递一个 postWriteRunnable , 所以同步的 Runnable 就会交给 QueueWork 去处理。QueueWork 内部是一个 HandlerThread , 所有任务都会存放到一个 LinkedList 中,然后在handleMessage()方法中循环的取任务执行。