概要:
SharedPreference属于轻量级的键值存储方式,以xml文件保存。作为Android存储数据的
一个重要的方式,值得透彻分析一下。
SharedPreference的获取方式:
首先SharedPreference的获取方式,有两种Activity的getSharedPreference(int mode)与Context的
getSharedPreference(String name, int mode);
先看Activity的getSharedPreference(int mode):
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
//getLocalClassName 就是Activity的类名
return getSharedPreferences(getLocalClassName(), mode);
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);//mBase是Context实例
}
所以最后还是调用了Context.getSharedPreference(String name, int mode),但是Context是抽象类,
它的的实现是ContextImp(这涉及到Activity的启动过程,不在这里赘述了)。
现在我们来看一下ContextImp的getSharedPreference(String name, int mode)(为了方便展示
我会去掉一些多余的代码)
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);//创建file
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);//获取SharedPreference。
}
这里会先根据name判断相应的File是否存在,如果不存在就利用getSharedPreferencesPath(name)创建一个新
的File,然后再回调getSharedPreferences(File file, int mode)。
其中创建File的函数:getSharedPreferencesPath(name):
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");//getPreferencesDir父目录的确认
}
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
}
其中getPreferencesDir的确认涉及到了PackageInfo的datadir,和process.myUid();
我们继续看getSharedPreference(File file,int mode);
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
return sp;
}
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;
}
我们先拿到ArrayMap<File, SharedPreferencesImp>类型的map,再从map中检查,没有则重新创建
其中SharedPreferencesImp就是SharedPreference的实现类。
最后我们查看SharedPreferencesImp类:
final class SharedPreferencesImpl implements SharedPreferences {
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
startLoadFromDisk();//加载数据
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
}
可以看到,SharedPreferencesImp的构造中,开启线程去加载文件xml的数据。
总结:SharedPreference获取的实例是一个单例,且在创建的时候开线程去解析加载文件数据。
SharedPreference.edit()获取Editor。
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
可以看到返回了一个Editor的实现类EditorImpl,但是前面有个方法awaitLoadedLocked()方法,采用的
是await和notify,处理并发,实现逻辑:loadFromDisk数据加载完成前,awaitLoadedLocked处于阻塞状态。
Editor.putxxxx方法,这里举例putString(String key, String value)
public Editor putString(String key, @Nullable String value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
很简单,就是将数据放到内存中。
Editor.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;
}
真正重要的方法就两个commitToMemory将改变数据和原先的数据整理在一起(Map中,待写入数据),
封装到MemoryCommitResult,然后enqueueDiskWrite正式写到磁盘。
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);
}
mapToWriteToDisk = mMap;
synchronized (mLock) {//整合数据
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);
}
}
mModified.clear();
}
}//封装到MemoryCommitResultzhong
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
enqueueDiskWrite:
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);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
if (isFromSyncCommit) {
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();//关键点,说明是同步进行的,在主线调用,在主线执行。
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);//apply(),则是放在任务队列中,异步执行
}
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
try {此方法最重要的函数,调用XmlUtils写入操作。
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
}
Eidtor.apply()
与Commit类似,只是它是异步的。在最后一步将任务放在任务队列中,多个任务线性一次执行。
又因为SharePreferenceImp单例原因,使用apply异步操作也是安全操作。推荐使用。