码迷,mamicode.com
首页 > 其他好文 > 详细

SharedPreference的读写原理分析

时间:2016-05-23 15:27:25      阅读:750      评论:0      收藏:0      [点我收藏+]

标签:

本文由嵌入式企鹅圈原创团队成员-阿里工程师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。

技术分享

SharedPreference的读写原理分析

标签:

原文地址:http://blog.csdn.net/yueqian_scut/article/details/51477760

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!