深入理解SharedPrefences实现原理
SharedPreferences作为Android常用的持久化组件,很常用,在系统设置应用中就大量地被使用。相信大家基本都是用过,但是在使用过程中,大家是否又知道它的实现原理呢?
基本使用
SharedPreferences使用很简单,比如我们要它保存某个字符串,在Activity使用如下:
/** * save number * @param num */private void saveNumber(String num) { SharedPreferences sharedPreferences = getSharedPreferences("light_persist", MODE_PRIVATE); sharedPreferences.edit().putString("number", num).apply(); //sharedPreferences.edit().putString("number", number).commit();}
上面代码执行后,会将变量num保存在/data/data/应用包名/shared_prefs/light_persist.xml。SharedPreferences使用很简单,但这并不表示它本身也简单,下面从三个角度来深入了解SharedPreferences的实现原理
对象初始化
在上面的例子中,我们是在Activity通过调用getSharedPreferences()获取SharedPreferences对象,我们看下它的具体实现是怎样的。首先,我们要明白以下几点:
- Activity继承了ContextThemeWrapper,ContextThemeWrapper继承了ContextWrapper,而ContextWrapper继承了抽象类Context,getSharedPreferences()为Context中的抽象方法;ContextWrapper重写了getSharedPreferences()方法,所以Activity中调用getSharedPreferences()方法,实际上调用的是ContextWrapper里面的方法。
- ContextWrapper中getSharedPreferences()方法的实现是调用了Context对象mBase的getSharedPreferences()。mBase具体对象为ContextImpl,也就是说Context.getSharedPreferences()具体实现在ContextImpl中。
对于第一点,通过类的继承关系很容易知道;对于第二点,这个会涉及到Activity的启动过程,在ActivityThread的performLaunchActivity()方法中会初始化context,context初始化的时序图如下
Activity在启动的时候会创建ContextImpl对象,并调用Activity的attach()方法将ContextImpl对象传递下去,最终给ContextWrapper的Context对象mBase赋值。
在明白Activity中调用getSharedPreferences()方法实际上调用了CcontextImpl.getSharedPreferences()后,我们就很容易明白SharedPreferences对象是如何初始化的了。SharedPreferences对象初始化过程如下所示
下面我们就看下SharedPreference初始化的代码实现
@Overridepublic SharedPreferences getSharedPreferences(String name, int mode) { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } } File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); //从缓存集合中取name对应的file,如果集合中没有,则根据name创建对应的file并添加进集合 if (file == null) { file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode);}
@Overridepublic SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //getSharedPreferencesCacheLocked()根据应用包名从缓存集合中取ArrayMap集合 final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { //mode校验,在Android7.0开始不再支持MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,//也就是不支持其他应用读写该应用的SharedPreference文件。 checkMode(mode); if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) { if (isCredentialProtectedStorage() && !getSystemService(UserManager.class) .isUserUnlockingOrUnlocked(UserHandle.myUserId())) { throw new IllegalStateException("SharedPreferences in credential encrypted " + "storage are not available until after user is unlocked"); } }//创建SharedPreferences接口实现类对象SharedPreferencesImpl,并存储在集合中。 sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } }//如果mode设置为了MODE_MULTI_PROCESS且targetSdkVersion小于Android3.0,则从硬盘中加载SharedPreferences文件//MODE_MULTI_PROCESS是对应用多进程的一种支持,但是在Android3.0版本开始已经弃用了 if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior. sp.startReloadIfChangedUnexpectedly(); } return sp;}
至此SharedPreferencesImpl对象就创建完成了,至于SharedPreferencesImpl的构造方法,具体就不展开,它主要作用是变量初始化、创建备份文件和从硬盘中加载SharedPredference文件。最后我们看下SharedPreferences接口说明。
* Interface for accessing and modifying preference data returned by {@link * Context#getSharedPreferences}. For any particular set of preferences, * there is a single instance of this class that all clients share. * Modifications to the preferences must go through an {@link Editor} object * to ensure the preference values remain in a consistent state and control * when they are committed to storage. Objects that are returned from the * various <code>get</code> methods must be treated as immutable by the application. * * <p><em>Note: This class does not support use across multiple processes.</em>
有两点需要注意:一,对于任何特定的一组preferences,所有客户端都共享一个此接口实现类(也就是SharedPreferencesImpl)的单个实例,这个一点可以在ContextImpl.getSharedPreferencesCacheLocked()方法中得到验证,它是根据应用包名从缓存集合中取ArrayMap集合。二,不支持跨进程使用,这个在Android3.0已经弃用了。
写入实现
往SharedPreferences中保存数据,我们是通过Editor接口实现的,通过调用Editor.putXxx().apply()或Editor.putXxx().commit()方法将数据保存起来。我们先看下涉及到的类关系图
Editor是SharedPreferences接口的嵌套接口,它的实现类是SharedPreferencesImpl的嵌套类EditorImpl,所以要看数据是怎么写入的,我们就需要分析EditorImpl里面的putXxx() 和apply()或者commit()方法,大多数同学应该知道commit()和apply()方法的区别,前者是同步方法且运行在主线程中,后者是异步方法运行在子线程中,我们先看下apply()的实现方式,理解apply()之后,commit()也就不再话下。
首先创建Editor接口实现类EditorImpl实例对象,然后调用EditorImpl.putString(),最后通过apply()提交一个写入数据的异步任务。我们重点关注下apply()方法实现,它的实现分为以下几步:
- 创建MemoryCommitResult对象,该对象中存储了写入SharedPreferences的数据。
- 创建阻塞CountDownLatch(MemoryCommitReuslt.writtenToDiskLatch)的任务,调用QueueWork.addFinisher()方法缓存,缓存的任务不一定会在QueueWork执行。唯一可能会执行的情况是QueueWork.waitToFinish()方法中调用。而waitToFinish()方法将会在Activity的onPause()之后,BroadcastReceiver的onReceive之后,Service的stop的时候调用,具体调用的地方是在ActivityThread中,感兴趣的同学可查阅AcaptivityThread。
- 将写入硬盘的操作的任务入QueueWork队列,由QueueWork中的HandlerThread执行写入的任务。
- 通知监听
@Overridepublic void apply() { final long startTime = System.currentTimeMillis(); final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { try { //这里作用是如果写preference到硬盘没有结束,就阻塞QueueWork中执行写preference的线程,直到写入完成。如果preference已经写入硬盘,写入过程中会调用mcr.writtenToDiskLatch.countDown()方法,而此时下面的await()方法不会阻塞,因为writtenToDiskLatch初始化的count为1. mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } if (DEBUG && mcr.wasWritten) { Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + " applied after " + (System.currentTimeMillis() - startTime) + " ms"); } } }; //addFinisher方法的实现是将Runnable对象加入到一个名为sFinisher的集合,Finisher可以理解为执行最后操作的管理者。 QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); //将等待任务从Finisher中移除,因为此Runnable是在写入操作完成之后调用,所以就需要从 QueueWork.sFinisher集合中移除; QueuedWork.removeFinisher(awaitCommit); } }; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // Okay to notify the listeners before it's hit disk // because the listeners should always get the same // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(mcr);}private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final boolean isFromSyncCommit = (postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { synchronized (mWritingToDiskLock) { //将preference写入磁盘,并调用mcr.writtenToDiskLatch.countDown()释放latch //writtenToDiskLatch初始化的latch个数为1; writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; // Typical #commit() path with fewer allocations, doing a write on // the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } //QueueWork.queue()最终实现是通过Queue中的HandlerThread执行writeToDiskRunnable任务。 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);}
以上就是通过apply()方式写入preference的实现方式,相关说明已在代码中注释。补充一点,CountDownLatch是一种同步机制,作用类似于它的字面意思,可以理解它是一道门,门上有一个或者多个门闩,要想通过这道门只有所有门闩都打开后通过这道门,否则只能一直等待。
有一点需要注意,当我们通过Editor改变某个SharedPreferences文件的一项preference时,实际上最终会将该文件的所有preference重新写入文件,这一点可以从commitToMemory()方法中看出,所以建议不要用SharedPreferences存储数据量大的内容;另外频繁改变的数据应和其他数据放在不同的SharedPreferences文件中。
// Returns true if any changes were madeprivate MemoryCommitResult commitToMemory() { long memoryStateGeneration; List<String> keysModified = null; Set<OnSharedPreferenceChangeListener> listeners = null; Map<String, Object> mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { // We optimistically don't make a deep copy until // a memory commit comes in when we're already // writing to disk. //当前写入磁盘的操作大于0,则先拷贝 if (mDiskWritesInFlight > 0) { // We can't modify our mMap as a currently // in-flight write owns it. Clone it before // modifying it. // noinspection unchecked mMap = new HashMap<String, Object>(mMap); } mapToWriteToDisk = mMap;//mMap就是所有的preference mDiskWritesInFlight++;//此变量会在真正写入磁盘的操作完成后递减 boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList<String>(); listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (mEditorLock) { boolean changesMade = false; //是否调用了Edit.clear()方法 if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); } mClear = false; } for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); // "this" is the magic value for a removal mutation. In addition, // setting a value to "null" for a given key is specified to be // equivalent to calling remove on that key. //如果value为EditorImpl对象本身或者为null,则将它从map中移除 if (v == this || v == null) { if (!mapToWriteToDisk.containsKey(k)) { continue; } mapToWriteToDisk.remove(k); } else { if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mapToWriteToDisk.put(k, v); } changesMade = true; if (hasListeners) { keysModified.add(k); } } mModified.clear(); if (changesMade) { mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk);}
至于Editor.commit()方法就不展开了,commit实现比apply()更简单些。
读取实现
在SharedPrefrencesImpl的构造方法中会加载保存的preference,并存入集合mMap。当我们通过SharedPreferences.getXxx()方法是,就是从mMap中取出对应的值。
@Override@Nullablepublic String getString(String key, @Nullable String defValue) { synchronized (mLock) { //判断SharedPreferences文件是否加载,如果没加载则阻塞知道文件加载完成 awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; }}
总结
-
SharedPreferences是接口,它的实现类是SharedPreferencesImpl。在Activity中调用getSharedPreferences()最终调用的是ContextImpl.getSharedPreferences()方法;
-
Eidtor.apply()将preference写入磁盘的任务是在QueueWork中的HandlerThread线程中执行;而Eidtor.commit()将preference写入磁盘的任务是运行在主线程中。两者写入的过程中都会利用CountDownLatch同步机制来保证写入任务执行后才执行下一个任务。
更多相关文章
- Android(安卓)?Pixelflinger 研究
- android 中FragmentActivity中模拟返回键返回上一个Activity效果
- 解决Android(安卓)webview设置cookie和cookie丢失的问题
- Android之提示错误Can not perform this action after onSaveIns
- 【从源码看Android】01从Looper说起
- Android中多USB摄像头解决方案——UVCCamera
- 【学习笔记】Android中Service通信
- 最新Android面试题整理 5月
- android编译分析之10—config.mk