SharedPreferences内部工作原理:
1、初始化:调用getSharedPreferences()创建一个SharedPreferences对象。
首次使用 getSharedPreferences 时,内存中不存在 SP 以及 SP Map 缓存,需要创建 SP 并添加到 ContextImpl 的静态成员变量(sSharedPrefs)中。下面分析上面这段代码:
首先判断sSharedPrefs是否为空,如果为空则给他初始化。sSharedPrefs是一个用来缓存SharedPreferences的ArrayMap,它的key为包名,它的value为ArrayMap,这个ArrayMap保存的键值对是SharedPreferences文件名和对应的SharedPreferencesImpl(是SharedPreferences的实现类)。如果SharedPreferencesImpl已经存在,它会直接返回已经存在的SharedPreferencesImpl。如果是在多进程模式下,或者目标版本低于HONEYCOMB的时候,会检查是否需要重新从磁盘中加载文件。但是需要说的是MODE_MULTI_PROCESS模式已经被deprecated了,官方建议使用ContentProvider来处理多进程访问。
在重新创建SharedPreferencesImpl的时候,getSharedPreferences会调用getSharedPrefsFile来获取存储的xml文件,这个函数对xml文件名进行了组装:
通过getPreferencesDir()来获取shared_prefs目录,然后根据文件名加上xml后缀。Android没有提供直接访问shared_prefs目录的API,getPreferencesDir是一个私有类,我们如果想要直接访问这个目录,可以通过下面这段代码访问:
String sharedPrefsDir = context.getCacheDir().getParent().getAbsolutePath()+"/shared_prefs";
SharedPreferences具体的实现者是SharedPreferencesImpl。我们都知道Android的SharedPreferences对XML操作是使用DOM方式解析的(一开始就把整个XML给读取出来)。下面看下SharedPreferencesImpl构造函数
其中startLoadFromDisk就是将xml给读取出来的。
会先判断是否存在对应xml文件。如果发现存在则会有一个预加载操作,这个操作是把xml文件的内容通过I/O操作和XmlUitl解析后存入一个map对象中。
2、get 操作:如getString
当调用SharedPreferences的getString()方法。get操作实际上是不会对文件做I/O操作,而是直接访问刚刚的map集合的内容(注意同步问题),这提高了效率,如果对应的xml不存在则重新创建一个对应的xml文件。
首先取得 SharedPreferencesImpl 对象锁,然后同步等待从磁盘加载数据完成,最后返回数据。但如果 SP 存储的内容过多,导致我们使用 getXXX 方法的时候阻塞,特别是在主线程调用的时候,所以建议在单个 SP 中尽量少地保存数据,虽然操作时间是毫秒级别的,用户基本上感觉不到。
3、put 操作
SP 写入数据的操作是通过 Editor 完成的,它也是一个接口,实现类是 EditorImpl,是 SharedPreferencesImpl 的内部类。
mModified 是一个 editor 中的一个 Map,保存着要修改的数据,在将改动保存到 SP 的 Map(变量 mMap,里面保存着使用中的键值对 ) 后被清空。put 完成后就要调用 commit 或者 apply 进行保存。
可以看到,commit 和 apply 操作首先执行了 commitToMemory,顾名思义就是提交到内存,返回值是 MemoryCommitResult 类型,里面保存着本次提交的状态。然后 commit 调用 enqueueDiskWrite 会阻塞当前线程,而 apply 通过封装 Runnable 把写磁盘之后的操作传递给 enqueueDiskWrite 方法。
保存到磁盘的操作:enqueueDiskWrite