Android开发中,咱们常常会用到SharedPreferences,它是一种轻量的数据存储方式,一般用来存储一些简单的配置信息。看了网络上的一些文章,感受都不是特别满意,所以但愿能结合本身的经验和理解写一篇分析SharedPreferences的文章。本文不会讲解SharedPreferences的基本用法,而是会结合源码来分析SharedPreferences的工做原理,以及使用中存在的一些问题。缓存
经过这篇文章,你能够了解到:安全
SharedPreferences是怎么工做的微信
SharedPreferences使用中有哪些坑网络
怎么来避免SharedPreferences的那些问题app
首先,咱们要搞清楚SharedPreferences的本质是什么。它的本质是基于xml文件存储的key-value键值对数据,其存储位置在/data/data/包名/shared_prefs目录下。因为它是存储在应用程序的私有目录下,外部是没法直接访问的。也就是说它实际上就是一个xml文件,和普通的xml没有本质区别,内容也和咱们工程代码里的strings.xml文件的内容相似。异步
下面咱们经过对源码的分析,讲解一下它的工做原理。先来看一下SharePreferences的基本用法。源码分析
SharedPreferences sp = context.getSharedPreferences(“file1”, Context.MODE_PRIVATE);post
sp.edit().putBoolean(“key1”, false).commit();线程
sp.getBoolean(“key1”)orm
那么咱们就从getSharedPreferences()方法开始讲起,实际上Context最终调用的是ContextImpl中的getSharedPreferences方法,咱们看下这个方法。
其中包含一个mSharedPrefsPaths对象,它是ArrayMap类型,咱们能够在App中建立多个sp文件,mSharedPrefsPaths中就是存储了不一样sp文件名和sp文件的对应关系。这里的getSharedPreferencesPath方法实际上就是在磁盘上建立了一个xml文件。查看上图最后一行的getSharedPreferences方法。
咱们看到这个方法实际返回了一个SharedPreferencesImpl对象,看下SharedPreferencesImpl的构造方法。
其中调用了startLoadFromDisk方法,startLoadFromDisk在子线程里调用了loadFromDisk,执行线程以前将mLoaded设置为false,再来看下这个loadFromDisk方法。
这个方法的代码不少,咱们只看最核心的部分,它经过XmlUtils.readMapXml()将文件读取到mMap中,mMap是一个HashMap,而且将mLoaded设置为true,你们记住mLoaded这个变量,后面还会遇到它。也就是说,sp文件的内容被读取到内存而且缓存到mMap中了,后续对sp的操做都与内存中的缓存有关。既然sp文件的内容会缓存到内存中,若是文件中存储了大量数据,就会占用很大的内存空间,这点须要特别注意。
SharedPreferences的建立过程讲完了,下面咱们来看一下put过程。put操做首先要调用edit()方法,
又见到了mLoaded这个变量,咱们回忆一下以前的逻辑,在开始开启线程读取sp文件到内存的时候,这个变量被置为false,等线程执行完会置为true,在上图的awaitLoadedLocked方法中,若是发现mLoaded为false,则调用wait方法,此时会阻塞当前线程,直到sp文件读取完成,才调用notifyAll()通知这里被阻塞的线程继续执行。也就是说,若是读取sp文件的操做执行时间很长的话,这里就可能会阻塞主线程致使ANR。
怎么才能尽量的避免这个问题呢?首先,咱们须要将sp文件根据功能和特色分解为多个小文件,好比根据不一样的功能模块进行划分,或者根据读写的频率,也能够根据是否App启动的时候就须要加载。若是每一个文件足够小,那么在读取文件到内存的时候,耗时天然也就少了。尤为是在App启动的时候,只须要加载启动时须要的sp配置,能够必定程度上减小启动时间。
下面继续看源码。edit()方法返回了一个Editor对象,实际的类型是EditorImpl。
EditorImpl中包含一个HashMap类型成员mModified,调用Editor的方法如putString以后,都只是将数据存储在mModified中。这里只是数据的暂存区,所以若是忘记调用commit或者apply方法,数据其实并无写入磁盘。有一点须要注意的是咱们每次调用edit方法,都会建立一个mModified对象,所以,有必要减小edit方法的调用。
最后,就是调用commit或者apply方法了。咱们知道commit是同步写入,会返回执行结果;而apply方法是异步写入,并不会返回执行结果。下面经过源码来分析下它们的实现。
commit方法中前后调用了commitToMemory和enqueueDiskWrite。commitToMemory方法的做用是将前面提到的mModified中缓存的数据更新到前面提到的mMap中,这个mMap会被最终写入文件。咱们看enqueueDiskWrite方法,它的第二个参数传了null,所以,isFromSyncCommit为true,而后直接执行了writeToDiskRunnable.run()方法,其中经过调用writeToFile将mMap中的配置内容写入sp文件。
从源码中咱们能够看出,commit的执行是同步的,并且是全量的写入。若是不是必要的状况,尽可能不要使用commit去保存sp的配置,以防止写文件阻塞主线程。
咱们再来看apply方法的实现有什么不一样。
这里所不一样的是enqueueDiskWrite的第二个参数不为null,因此方法内部将写入文件的操做放入了单线程的线程池异步执行:
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable)。
因为是单线程,来不及执行的Runnable都被放在队列中等待执行 。writeToDiskRunnable里面执行了writeToFile将sp写入文件,而后调用了postWriteRunnable的run()方法,这里面又调用了awaitCommit的run()方法,最后调用了mcr.writtenToDiskLatch.await()。那么这个writtenToDiskLatch又有什么做用呢?经过代码,咱们发现writeToFile方法里面最终会调用writtenToDiskLatch的countDown方法,也就是说,若是sp文件的写入一直没有执行完,writtenToDiskLatch.await()这个调用就会阻塞在这里,但从实际的执行时序上来看writtenToDiskLatch的countDown的调用又确定是在await以前的,那么这个await的调用到底有什么做用呢?咱们又注意到,这里有一行代码:QueuedWork.add(awaitCommit)。 咱们看下这个QueuedWork是什么?
图中略去了部分代码,add方法实际上就是将runnable加入到一个ConcurrentLinkedQueue中。下面的waitToFinish方法里会去遍历queue中的每一个Runnable,并执行它的run方法。那么waitToFinish方法又是在哪里调用的呢?咱们根据注释找到了ActivityThread类的handlePauseActivity、handleStopActivity方法,咱们来看其中的一个。
咱们看到,在Activity调用onStop的时候,会调用QueuedWork.waitToFinish(),遍历执行其中的runnable。假设咱们频繁的调用了apply方法,并紧接着调用了onStop,那么就可能会发生onStop一直等待QueuedWork.waitToFinish执行完成而产生ANR。也就是说,即便是调用了apply方法去异步提交,也不是彻底安全的。若是apply方法使用不当,也许会遇到与下图相似的问题。
上面讲了put操做,因为get操做相对简单一些,这里就不单独分析了。
从上面的分析咱们发现SharedPreferences的使用并非那么简单的,使用不当可能会致使程序异常,咱们对上面提到的一些问题进行一下总结:
sp配置不要所有都写在一个文件中,这样不只第一次加载会很慢,也会占用大量内存。最好是根据必定规则分红多个sp文件。好比频繁和不频繁写入的配置就分别存储在两个不一样的文件中。
sp文件的写入是全量写入,即便改了一条配置,写入的时候也会对整个文件进行操做,所以最好能批量操做,不要每次都commit。
启动的时候须要读取sp的配置最好异步进行,若是必定要同步读取,启动的sp文件要尽量的小。
不要将太大的配置项(包括key和value)存储在sp中,不然会占用大量内存。
获取SharedPreferences对象的时候会读取sp文件,若是文件没有读取完,就执行了get和put操做,可能会出现须要等待的状况,所以最好提早获取SharedPreferences对象。
每次调用edit方法都会建立一个新的EditorImpl对象,不要频繁调用edit方法。
apply方法虽然是在线程中异步将配置写入文件,可是若是任务不少,并且每一个任务执行时间很长,也可能会致使Activity或Service在stop的时候出现ANR。
欢迎关注个人微信公众号,收到最新的推送文章