这是我参与更文挑战的第1天,活动详情查看: 更文挑战
1. ContextImpl作用
SharedPreferences是一个接口,具体实现类是
SharedPreferencesImpl
。在Android源码中该类属于隐藏类,因此具体的对象获取需要通过
Context
。Application、Service和Activity都间接继承自Context,通过装饰模式,具体的操作交给
ContextImpl
对象。ContextImpl提供了
getSharedPreferences
方法来获取一个SharedPreferences对象。
我们知道了
SharedPreferences
对象是通过
ContextImpl
获取的,那么App中是如何保证获取的对象是同一个呢?
其实每一个App中,
ContextImpl
的数量为Activity个数+Service个数+Application,为了确保
SharedPreferences
d的唯一性,在
ContextImpl
中通过一个静态集合来存储
SharedPreferences
对象。
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
复制代码
ArrayMap中通过包名来细分应用的
SharedPreferences
。然后通过SharedPreferences的文件地址来获取具体的
SharedPreferences
对象。具体的存储路径为:
/data/user/0/com.mdy.sp/shared_prefs/${name}.xml
复制代码
2. 数据 load 与 get
SharedPreferences的具体实现交给了
SharedPreferencesImpl
类。在创建SharedPreferencesImpl对象时,会调用
startLoadFromDisk
方法加载磁盘数据到内存中,内部通过创建一个单独的线程来执行
loadFromDisk
操作。
private void loadFromDisk() {
synchronized (mLock) {
// mLoaded = true表示数据已经被加载过了,所以直接返回
if (mLoaded) {
return;
}
//mBackupFile表示备份文件,备份文件存在的话,则删除源文件并替换
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
//通过IO的方式读取磁盘的xml文件解析到map集合中
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;
}
//解析成功的话,将mLoaded置为true,并将map引用地址赋值给mMap
synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
} catch (Throwable t) {
mThrowable = t;
} finally {
//唤醒主线程
mLock.notifyAll();
}
}
}
复制代码
loadFromDisk
方法加载数据并解析,最后将引用地址赋值给SharedPreferencesImpl的
mMap
。调用get系列的方法获取缓存值时,实际是从
mMap
中获取缓存值。
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
复制代码
get系列方法中会调用
awaitLoadedLocked
判断磁盘的读取是否结束,根据mLoaded的布尔值来确认,true表示完成,否则调用
mLock.wait()
方法暂停主线程。这里就和上述的
loadFromDisk
方法最后调用的
notifyAll
方法对应上了,磁盘读取结束唤醒主线程。
3. 数据 缓存
SharedPreferences中数据的缓存需要通过
Editor
的实现类
EditorImpl
来实现。通过对应的get方法来缓存数据并调用commit或者apply提交。
3.1 内存同步
SharedPreferences中数据存储到磁盘之前,会首先调用
commitToMemory
方法同步到内存中。
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
mMap = new HashMap<String, Object>(mMap);
}
//mMap引用赋值给mapToWriteToDisk
mapToWriteToDisk = mMap;
//mDiskWritesInFlight值+1
mDiskWritesInFlight++;
synchronized (mEditorLock) {
boolean changesMade = false;
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// value 属于Editor或者为null,直接抛弃