众所周知Android开发,谷歌给提供了丰富的类库来提高开发效率,正是应为这些库的存在才能在快速的时间内实现我们的创意。不过事情始终是两面性的有好的一面也有不好的一面,就好比我的标题,谷歌给提供的保存一些配置信息的功能类,它方便了我们序列化存储程序状态,不用操心读写sd卡、内存缓存的问题,但是在我们的程序达到一定规模了,它的方便可就不方便了。
首先我贴出来初花化的代码一步一步分析和认识它。
protected void onCreate(@Nullable Bundle savedInstanceState) {
...忽略代码
final SharedPreferences prefs = getSharedPreferences("preferences", Context.MODE_PRIVATE);
}
这段代码很简单创建一个名字为preferences的xml文件,那我们就进入getSharedPreferences里面看看,应为这个函数实在Context里面的API,所以我们就去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);
}
我们看重点,mSharedPrefsPaths是个ArrayMap它里面存的是名字和文件的缓存,如果没有它就创建xml就读写了一次磁盘,而且它还加锁了 加锁了 加锁了 有木有 有木有 有木有,如果这时正是你的启动流程中可想而知卡你没商量,我们在往下走看return getSharedPreferences(file, 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;
}
}
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;
}
代码有点长,仔细看不要漏掉一点细节谷歌就爱这样干,在一个及不起眼的判断里面调用一个非常重要的函数,checkMode(mode);里面当你targetSdkVersion的版本大于N(24)时检查xml你传进去的模式是不是支持第三方App读写,又有锁再一次的想说卡你真的没商量,不过也不要慌执行下来时间也就在20 - 30毫秒之间,从代码中我们看见SharedPreferences的实现类是SharedPreferencesImpl,把实例加入缓存,如果支持多进程读取且已经有更改在读取一次sp.startReloadIfChangedUnexpectedly(),不好意思这段代码没有前面说的不起眼里面调用重要函数,哈哈。
创建过程其实很简单就是在存储卡上创建一个xml文件很不复杂,既然是个文件肯定有写入和读取我们先看看读取代码的实现逻辑
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
以此为例众多的获取方法只是参数不一样,我们知道SharedPreferences是采用key-value存储方式,所以SharedPreferencesImpl里面使用Map来管理,private Map
public void saveString(String key, String value, boolean isSync) {
if (isSync) {
mSharedPreferences.edit().putString(builder.toString(), value).commit();
} else {
mSharedPreferences.edit().putString(builder.toString(), value).apply();
}
}
我们先看commit的从上面也能看出他是同步方法,大家注意到没没只要写入操作都需要调用edit(),他里面很简单就是new了一个Editor,实现类是EditorImpl,而commit就是他里面的函数。
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
...
return mcr.writeToDiskResult;
}
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
...
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
...
}
首先调用commitToMemory()这里面比较有意思的是他支持批量写入,也就是说当put值时你可以不调用commit()和apply()只在空闲时写入也是可以的。
两个函数大体相似不同点是apply有个postWriteRunnable,先不说他的意思进入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();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
这回我们就知道postWriteRunnable的其中一个作用是异步还是同步的判断表示,这个函数一目了然,writeToFile把修改的值xml的所有值重新写了一遍,他不是增量的写入而是清空以前的把最新所有的写入文件 如果是同步的就直接调用Runnable的run函数,若果是异步的调用QueuedWork.queue函数,而这个函数也很简单
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
通过一个线程来执行Runnable,达到异步的过程。
到此我把SharedPreferences从创建到读写详细的分析了下,他其实挺简单的简单到我们分分钟钟就能写出一个类似功能的类来,但是越是简单的就越容易出问题的,他在保证线程安全时就会牺牲性能,不保证安全他就牺牲了灵活性,所以我们一定要了解他,找到它的优缺点这样我们用起来才能找到平衡点。