您的位置:首页 > Web前端

SharedPreference的读写原理分析

2016-05-22 23:16 351 查看
本文由嵌入式企鹅圈原创团队成员-阿里工程师Hao分享。
一、commit和apply
apply是异步,commit是同步,在主线程中使用commit可能会影响性能,因为同步IO操作的耗时可能会比较长,两个方法都能保证value被正确的保存到磁盘上。两者都是Editor类的方法,它们的具体实现在EditorImpl类中,我们先大体比较一下这两个函数:
 




这两个函数一开始都调用了commitToMemory这个函数来得到MemoryCommitResult对象,接着又都调用了enqueueDiskWrite。commit和apply调用enqueueDiskWrite传入的第一个参数都是MemoryCommitResult的对象mcr,第二个参数commit传入的是null,apply传入的是一个Runnable,从这个Runnable的名字postWriteRunnable可以猜测它会被放在一个线程中去执行来异步的写入数据。
我们先来看commitToMemory。



commitToMemory先用到了SharedPreferencesImpl的锁,判断mDiskWritesInFlight大于0时,就拷贝一份mMap,把它存到MemoryCommitResult类的成员mapToWriteToDisk中,然后再把mDiskWritesInFlight加1。在把mapToWriteDisk写入到磁盘后,mDiskWritesInFlight会减1,所以mDiskWritesInFlight大于0说明之前已经有调用过commitToMemory了,并且还没有把map写入到磁盘。
这里mMap是SharedPreferencesImpl类的成员,读sharedPreference中的值时就是从它里面获取。



在写入sharedPreference时,在commitToMemory函数中,也是把mModified中的值更新到mMap里。这里的mModified是EditorImpl类的成员,在调用putInt、putString之类的函数时,就是把key-value放在它里面。在遍历mModified之前要先获得EditorImpl对象的锁,这样在比较mModified和mMap时就不能再执行editor的putXXX之类的方法了。
因为apply是个异步写入磁盘的过程,如果已经调用过一次commitToMemory,但还没真正写入磁盘,再调用commitToMemory时,mDiskWritesInFlight等于1,需要再拷贝一份mMap,这样前后两次要准备写入磁盘的mapToWriteToDisk是两个不同的内存对象,后一次调用commitToMemory时,在更新mMap中的值时不会影响前一次的mapToWriteToDisk的写入磁盘。
 



然后我们接着来看enqueueDiskWrite的实现。commit调用它时第二个参数postWriteRunnable为null,所以isFromSyncCommit为true,立即执行writeToDiskRunnable。在writeToDiskRunnable的run中,先获得mWritingToDiskLock锁,执行writeToFile,再把mDiskWritesInFlight减1。writeToFile方法会把MemoryCommitResult中的mapToWriteToDisk写入磁盘文件中。
apply调用enqueueDiskWrite时,第二个参数传入的是postWriteRunnable,所以不会走isFromSyncCommit的分支,在函数最后,在线程池中起一个线程执行writeToDiskRunnable。在writeToDiskRunnable的最后还会执行postWriteRunnable。



commit的实现中,在enqueueDiskWrite之后接着是mcr.writtenToDiskLatch.await();



writtenToDiskLatch是一个CountDownLatch,如果它的值大于0,那么commit在await上阻塞,在writtenToDiskLatch变为0时,才能继续往下走执行后面的notifyListeners。
writtenToDiskLatch初始值为1,在setDiskWriteResult执行完后,计数减1,await才会从阻塞中被唤醒继续往下执行。在writeToFile中,在真正完成写入后的地方会调用setDiskWriteResult。
再来看apply的实现,在另起一个线程把map中的数据写入到磁盘后,postWriteRunnable会被执行,它会去执行另一个Runnable -- awaitCommit。awaitCommit中同样执行的是mcr.writtenToDiskLatch.await();,但要注意现在是在另一个线程中被执行的。
可见,writtenToDiskLatch保证了无论是commit还是apply,都必须在前一个的writeToFile完成后,才能开始新一个的commit或apply操作。



 
二、读sharedPreference
前面有getInt的代码,可以发现它的实现很简单,直接从mMap中根据key来找value,那mMap中的数据是在什么时候被加载的呢?我们来看SharedPreferencesImpl的构造函数。



在startLoadFromDisk中会起一个线程调用loadFromDiskLocked从磁盘上加载数据。因此,如果一个应用中有好几个sharedPreferences时,每个sp对应的SharedPreferencesImpl都会各自起一个线程去把xml中的数据加载到map中,后面所有的getXXX方法实际上都是从内存中取数据,不会再有读磁盘的动作了。



每个SharedPreferencesImpl的实例一直被持有着不会释放,因为在第一次调用Context的getSharedPreferences后,它会被保存在一个static的map中,在应用的生命周期中一直存在,并且磁盘上的xml文件在加载后也会一直被保存在内存中。sSharedPrefs就是存放每个SharedPreferencesImpl实例的静态的ArrayMap。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息