最近有个任务是要做应用启动时间优化,然后记录系统启动的各个步骤所占用的时间,发现有一个方法是操作SharedPreferences的,里面仅仅是读了2个key,然后更新一下值,然后再写回去,耗时竟然在500ms以上(应用初次安装的时候),感到非常吃惊。以前只是隐约的知道SharedPreferences是跟硬盘上的一个xml文件对应的,具体的实现还真没研究过,下面我们就来看看SharedPreferences到底是个什么玩意,为什么效率会这么低?
SharedPreferences是存放在ContextImpl里面的,所以先看写ContextImpl这个类:
ContextImpl.java(https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/ContextImpl.java):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
getSharedPreferences()做的事情很简单,一目了然,我们重点看下SharedPreferencesImpl.java这个类:
SharedPreferencesImpl.java(https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/SharedPreferencesImpl.java)
首先是构造函数:
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
然后我们随便看一个读请求:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
看一下写操作,写是通过Editor来做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
看一下commit:
1 2 3 4 5 6 7 8 9 10 11 |
|
commitToMemory()这个方法主要是用来更新内存缓存的mMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
看下writeToDiskRunnable都干了些什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
下面是真正要写硬盘了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
1 2 3 4 5 6 7 8 9 10 |
|
这里面还有一个跟commit长得很像的方法叫apply():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
我们已经看过enqueueDiskWrite()这个方法了,因为参数postWriteRunnable不是null,最终会执行:
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
这是在单独的线程上做写硬盘的操作,写完以后会回调postWriteRunnable,等待写硬盘完成!
从上面的代码可以得出以下结论:
(1)SharedPreferences在第一次加载的时候,会从硬盘异步的读文件,然后会在内存做缓存。
(2)SharedPreferences的读都是读的内存缓存。
(3)如果是commmit()写,是先把数据更新到内存,然后同步到硬盘,整个过程是在同一个线程中同步来做的。
(4)如果是apply()写,首先也是写到内存,但是会另起一个线程异步的来写硬盘。因为我们在读的时候,是直接从内存读取的,因此,用apply()而不是commit()会提高性能。
(5)如果有多个key要写入,不要每次都commit或者apply,因为这里面会存在很多的加锁操作,更高效的使用方式是这样:editor.putInt("","").putString("","").putBoolean("","").apply();并且所有的putXXX()的结尾都会返回this,方便链式编程。
(6)这里面有三级的锁:SharedPreferences,Editor, mWritingToDiskLock。
mWritingToDiskLock是对应硬盘上的文件,Editor是保护mModified的,SharedPreferences是保护mMap的。
参考:
http://stackoverflow.com/questions/19148282/read-speed-of-sharedpreferences
http://stackoverflow.com/questions/12567077/is-sharedpreferences-access-time-consuming
原文:http://www.2cto.com/kf/201312/268547.html