专栏名称: 走了
Android攻城狮
51好读  ›  专栏  ›  走了

SharedPreferencesde实现细节

走了  · 掘金  ·  · 2021-06-01 14:38

正文

阅读 60

SharedPreferencesde实现细节

这是我参与更文挑战的第1天,活动详情查看: 更文挑战

1. ContextImpl作用

SharedPreferences是一个接口,具体实现类是 SharedPreferencesImpl 。在Android源码中该类属于隐藏类,因此具体的对象获取需要通过 Context 。Application、Service和Activity都间接继承自Context,通过装饰模式,具体的操作交给 ContextImpl 对象。ContextImpl提供了 getSharedPreferences 方法来获取一个SharedPreferences对象。 我们知道了 SharedPreferences 对象是通过 ContextImpl 获取的,那么App中是如何保证获取的对象是同一个呢? 其实每一个App中, ContextImpl 的数量为Activity个数+Service个数+Application,为了确保 SharedPreferences d的唯一性,在 ContextImpl 中通过一个静态集合来存储 SharedPreferences 对象。

private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
复制代码

ArrayMap中通过包名来细分应用的 SharedPreferences 。然后通过SharedPreferences的文件地址来获取具体的 SharedPreferences 对象。具体的存储路径为:

/data/user/0/com.mdy.sp/shared_prefs/${name}.xml
复制代码

2. 数据 load 与 get

SharedPreferences的具体实现交给了 SharedPreferencesImpl 类。在创建SharedPreferencesImpl对象时,会调用 startLoadFromDisk 方法加载磁盘数据到内存中,内部通过创建一个单独的线程来执行 loadFromDisk 操作。

private void loadFromDisk() {
        synchronized (mLock) {
            // mLoaded = true表示数据已经被加载过了,所以直接返回
            if (mLoaded) { 
                return; 
            }
            //mBackupFile表示备份文件,备份文件存在的话,则删除源文件并替换
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }
        //通过IO的方式读取磁盘的xml文件解析到map集合中
        Map<String, Object> map = null;
        StructStat stat = null;
        Throwable thrown = null;
        try {
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16 * 1024);
                    map = (Map<String, Object>) XmlUtils.readMapXml(str);
                } catch (Exception e) {
                    Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
            // An errno exception means the stat failed. Treat as empty/non-existing by
            // ignoring.
        } catch (Throwable t) {
            thrown = t;
        }
        //解析成功的话,将mLoaded置为true,并将map引用地址赋值给mMap
        synchronized (mLock) {
            mLoaded = true;
            mThrowable = thrown;

            try {
                if (thrown == null) {
                    if (map != null) {
                        mMap = map;
                        mStatTimestamp = stat.st_mtim;
                        mStatSize = stat.st_size;
                    } else {
                        mMap = new HashMap<>();
                    }
                }
            } catch (Throwable t) {
                mThrowable = t;
            } finally {
                //唤醒主线程
                mLock.notifyAll();
            }
        }
    }
复制代码

loadFromDisk 方法加载数据并解析,最后将引用地址赋值给SharedPreferencesImpl的 mMap 。调用get系列的方法获取缓存值时,实际是从 mMap 中获取缓存值。

    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
复制代码

get系列方法中会调用 awaitLoadedLocked 判断磁盘的读取是否结束,根据mLoaded的布尔值来确认,true表示完成,否则调用 mLock.wait() 方法暂停主线程。这里就和上述的 loadFromDisk 方法最后调用的 notifyAll 方法对应上了,磁盘读取结束唤醒主线程。

3. 数据 缓存

SharedPreferences中数据的缓存需要通过 Editor 的实现类 EditorImpl 来实现。通过对应的get方法来缓存数据并调用commit或者apply提交。

3.1 内存同步

SharedPreferences中数据存储到磁盘之前,会首先调用 commitToMemory 方法同步到内存中。

 private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            List<String> keysModified = null;
            Set<OnSharedPreferenceChangeListener> listeners = null;
            Map<String, Object> mapToWriteToDisk;

            synchronized (SharedPreferencesImpl.this.mLock) {
                if (mDiskWritesInFlight > 0) {
                    mMap = new HashMap<String, Object>(mMap);
                }
                //mMap引用赋值给mapToWriteToDisk
                mapToWriteToDisk = mMap;
                //mDiskWritesInFlight值+1
                mDiskWritesInFlight++;

                synchronized (mEditorLock) {
                    boolean changesMade = false;
                    for (Map.Entry<String, Object> e : mModified.entrySet()) {
                        String k = e.getKey();
                        Object v = e.getValue();
                        // value 属于Editor或者为null,直接抛弃






请到「今天看啥」查看全文