SharedPreferencesImpl源码浅析
最近拉取应用ANR日志时发现,大量卡顿发生在SharedPreferences的commit上,查看源码发现底层实现基于锁的实现甚为高明,在此叨一叨其主要实现。
基本方法分析
初始化流程为:在contextImp中的 public SharedPreferences getSharedPreferences(String name, int mode) 方法中会返回相应name的sp,里面具体实现为一个缓存hashmap,key为name,value为sp,缓存获取失败时会初始化spImp类,从文件中加载数据。
基本的get方法:初始化完成后会在内存中创建keyvalue缓存,所以各个activity中获取的sp,调用getValue方法时是很高效的。
Editor中的commit和apply
变更sp时,会通过Editor的commit或apply来处理,commit为阻塞式修改,不仅会刷新内存缓存,而且会在当前线程去写文件,可能硬气卡顿,apply为异步修改,刷新内存缓存后,启动异步执行器去写文件,不会引起卡顿。
commit()
() {
MemoryCommitResult mcr = commitToMemory()SharedPreferencesImpl..enqueueDiskWrite(
mcr){
mcr..await()} (InterruptedException e) {
}
notifyListeners(mcr)mcr.}
处理逻辑为,先修改内存缓存,再在当前线程写入文件,通过countdownlatch来实现同步等待。
apply()
() {
MemoryCommitResult mcr = commitToMemory()Runnable awaitCommit = Runnable() {
() {
{
..await()} (InterruptedException ignored) {
}
}
}QueuedWork.(awaitCommit)Runnable postWriteRunnable = Runnable() {
() {
.run()QueuedWork.()}
}SharedPreferencesImpl..enqueueDiskWrite(mcrpostWriteRunnable)notifyListeners(mcr)}
修改内存后,启动异步queuework处理写文件,通过对象锁写完文件后,这里通过postRunable完成,感觉有点绕。
总结
从源码来分析其实很简单,两者主要区别有两点:
1、commit()有返回值,apply()没有返回值。apply()失败了是不会报错的。
2、apply()写入文件的操作是异步的,会把Runnable放到线程池中执行,而commit()的写入文件的操作是在当前线程同步执行的。
因此当两者都可以使用的时候还是推荐使用apply(),因为apply()写入文件操作是异步执行的,不会占用主线程资源。
网友评论