SharedPreferences内部工作原理:
1、初始化:调用getSharedPreferences()创建一个SharedPreferences对象。
在多进程模式或者目标sdk版本在HONEYCOMB以下版本每次读取缓存了的sp,Android会检查xml文件是否已经被重写了。首次使用 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文件名进行了组装:
SharedPreferences在第一次创建后会一直维持一个Singleton,每次调用getSharedPreferences()都返回唯一的一个实例。由于应用版本升级时并不会删除SharedPreferences文件,所以可以加个版本判断,来进行一些数据更新,从上面看来,由于每一次调用getSharedPreferences()都会有IO操作,当内容比较多时,那么就不适宜在Application的onCreate中进行SharedPreferences文件初始化了,最好的办法是开个子线程去完成它的创建和数据的预加载!!!通过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构造函数
makeBackupFile 用来定义备份文件,该文件在写入磁盘时会用到其中startLoadFromDisk就是将xml给读取出来的。
它使用了一个异步线程来读取xml,最终实现的函数是loadFromDiskLocked() 调用XmlUtils.readMapXml来调用,读取整个xml的内容,放到mMap当中会先判断是否存在对应xml文件。如果发现存在则会有一个预加载操作,这个操作是把xml文件的内容通过I/O操作和XmlUitl解析后存入一个map对象中。
2、get 操作:如getString
当调用SharedPreferences的getString()方法。get操作实际上是不会对文件做I/O操作,而是直接访问刚刚的map集合的内容(注意同步问题),这提高了效率,如果对应的xml不存在则重新创建一个对应的xml文件。
其他方法如getInt等类似首先取得 SharedPreferencesImpl 对象锁,然后同步等待从磁盘加载数据完成,最后返回数据。但如果 SP 存储的内容过多,导致我们使用 getXXX 方法的时候阻塞,特别是在主线程调用的时候,所以建议在单个 SP 中尽量少地保存数据,虽然操作时间是毫秒级别的,用户基本上感觉不到。
3、put 操作
SP 写入数据的操作是通过 Editor 完成的,它也是一个接口,实现类是 EditorImpl,是 SharedPreferencesImpl 的内部类。
通过 SP 的 edit 方法获取 Editor 实例,等到加载完毕直接返回一个 EditorImpl 对象。 比如我们要保存某个 String 的值,调用 putString 方法。mModified 是一个 editor 中的一个 Map,保存着要修改的数据,在将改动保存到 SP 的 Map(变量 mMap,里面保存着使用中的键值对 ) 后被清空。put 完成后就要调用 commit 或者 apply 进行保存。
写操作也有两步,一是把数据先写入内存中,即map集合,二是把数据写入硬盘文件中。这样才能保证数据的完整性,写操作有两个提交的方式:commit():线程安全,性能慢,一般来说在当前线程完成写文件操作 apply():线程不安全,性能高,异步处理IO操作,一定会把这个写文件操作放入一个SingleThreadExecutor线程池中处理。可以看到,commit 和 apply 操作首先执行了 commitToMemory,顾名思义就是提交到内存,返回值是 MemoryCommitResult 类型,里面保存着本次提交的状态。然后 commit 调用 enqueueDiskWrite 会阻塞当前线程,而 apply 通过封装 Runnable 把写磁盘之后的操作传递给 enqueueDiskWrite 方法。
for (Map.Entry e : mModified.entrySet()) { // 在这开始将修改的内容写入到mMap当中。 MemoryCommitResult 类,主要用于提交到内存后返回结果,然后在写入磁盘时作为参数传递保存到磁盘的操作:enqueueDiskWrite
参数有 MemoryCommitResult、Runnable,在commit 方法中调用 enqueueDiskWrite 方法是传入的 Runnable 是null,它会在当前线程直接执行写文件的操作,然后返回写入结果。而如果 Runnable 不是 null,那就使用 QueueWork 中的单线程执行。apply 和 commit 的根本区别:一个同步执行,有返回值;一个异步执行,没有返回值。官方推荐:使用 apply 。 writeToFile 方法:备份 → 写入 → 检查 → 善后,这样保证了数据的安全性和稳定性
网友评论