天天看点

深入理解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初始化的时序图如下

深入理解SharedPrefences实现原理

Activity在启动的时候会创建ContextImpl对象,并调用Activity的attach()方法将ContextImpl对象传递下去,最终给ContextWrapper的Context对象mBase赋值。

在明白Activity中调用getSharedPreferences()方法实际上调用了CcontextImpl.getSharedPreferences()后,我们就很容易明白SharedPreferences对象是如何初始化的了。SharedPreferences对象初始化过程如下所示

深入理解SharedPrefences实现原理

下面我们就看下SharedPreference初始化的代码实现

@Override
public 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);
}
           
@Override
public 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()方法将数据保存起来。我们先看下涉及到的类关系图

深入理解SharedPrefences实现原理

Editor是SharedPreferences接口的嵌套接口,它的实现类是SharedPreferencesImpl的嵌套类EditorImpl,所以要看数据是怎么写入的,我们就需要分析EditorImpl里面的putXxx() 和apply()或者commit()方法,大多数同学应该知道commit()和apply()方法的区别,前者是同步方法且运行在主线程中,后者是异步方法运行在子线程中,我们先看下apply()的实现方式,理解apply()之后,commit()也就不再话下。

深入理解SharedPrefences实现原理

首先创建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执行写入的任务。
  • 通知监听
@Override
public 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 made
private 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
@Nullable
public 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同步机制来保证写入任务执行后才执行下一个任务。