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同步机制来保证写入任务执行后才执行下一个任务。

更多相关文章

  1. Android(安卓)?Pixelflinger 研究
  2. android 中FragmentActivity中模拟返回键返回上一个Activity效果
  3. 解决Android(安卓)webview设置cookie和cookie丢失的问题
  4. Android之提示错误Can not perform this action after onSaveIns
  5. 【从源码看Android】01从Looper说起
  6. Android中多USB摄像头解决方案——UVCCamera
  7. 【学习笔记】Android中Service通信
  8. 最新Android面试题整理 5月
  9. android编译分析之10—config.mk

随机推荐

  1. Android通知栏点击无法启动Activity的问
  2. 最简便实现Android(安卓)ListView选中ite
  3. Android(安卓)Studio中使用开源库volley
  4. Android(安卓)设计模式之原型模式
  5. Android中实现长按照片弹出右键菜单
  6. Android(安卓)计算方法运行时间
  7. 在AndroidStudio中出现android no debugg
  8. Android-ListView中嵌套(ListView)控件时
  9. Uni-app Android(安卓)离线打包集成 uni-
  10. Android(安卓)Studio 2.2 预览版 - 全新