Android SharedPreferences 源码分析

190 阅读8分钟

1. 获取 SharedPreferences 对象

SharedPreferences 是一个接口,其实现类是 SharedPreferencesImpl。通过 getSharedPreferences 获取到的是一个 SharedPreferencesImpl 对象:

// android/app/ContextImpl.java

// SharedPreferences file 标识名为 key
private ArrayMap<String, File> mSharedPrefsPaths;

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
    ...
    File file;
    synchronized (ContextImpl.class) {
        if (mSharedPrefsPaths == null) {// 首次获取都为空
            mSharedPrefsPaths = new ArrayMap<>();
        }
        file = mSharedPrefsPaths.get(name);
        if (file == null) {
            file = getSharedPreferencesPath(name);
            mSharedPrefsPaths.put(name, file);
            // file:/data/user/0/<包名>/shared_prefs/<包名>.xml
        }
    }
    return getSharedPreferences(file, mode);
}

首次获取都为空,接着调用了 getSharedPreferencesPath 方法,根据传入的 name,获取 SharedPreferences file 路径名:

public File getSharedPreferencesPath(String name) {
    return makeFilename(getPreferencesDir(), name + ".xml");
}

@UnsupportedAppUsage
private File getPreferencesDir() {
    synchronized (mSync) {
        if (mPreferencesDir == null) {
            mPreferencesDir = new File(getDataDir(), "shared_prefs");// /data/user/0/<包名>/shared_prefs
        }
        return ensurePrivateDirExists(mPreferencesDir);
    }
}

private File makeFilename(File base, String name) {
    if (name.indexOf(File.separatorChar) < 0) {
        final File res = new File(base, name);
        BlockGuard.getVmPolicy().onPathAccess(res.getPath());
        return res;
    }
    throw new IllegalArgumentException(
            "File " + name + " contains a path separator");
}

实际上存储 SharedPreferences file 的路径是 /data/user/0/<包名>/shared_prefs

具体文件名是 /data/user/0/<包名>/shared_prefs/name.xml。这里可以看出 SharedPreferences file 实际上是一个 xml 文件。

这样就获取到了 name 对应的 file,接着将其存到 mSharedPrefsPaths 中。接着调用 getSharedPreferences(file, mode):

// android/app/ContextImpl.java
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        // 返回一个 cache 对象,空初始化
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) {// sp 空
            checkMode(mode);// 简单判断,传入的 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 (id "
                            + UserHandle.myUserId() + ") is unlocked");
                }
            }
            // 创建 SharedPreferencesImpl 对象
            sp = new SharedPreferencesImpl(file, mode);
            cache.put(file, sp);
            return sp;
        }
    }
    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;
}

通过 getSharedPreferencesCacheLocked 方法获取到了一个 ArrayMap<File, SharedPreferencesImpl> 对象:

private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

@GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
    if (sSharedPrefsCache == null) {
        sSharedPrefsCache = new ArrayMap<>();// 创建
    }

    final String packageName = getPackageName();
    ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
    if (packagePrefs == null) {// 为空
        packagePrefs = new ArrayMap<>();// 创建
        sSharedPrefsCache.put(packageName, packagePrefs);
    }

    return packagePrefs;
}

最终创建并返回一个 SharedPreferencesImpl 对象。

2. SharedPreferencesImpl 的初始化,将磁盘文件缓存至内存中

看一下 SharedPreferencesImpl 的构造方法:

SharedPreferencesImpl(File file, int mode) {
    mFile = file;// 存储 SharedPreferences 对应的 XML 文件
    mBackupFile = makeBackupFile(file);// 创建备份文件(如 "file.xml.bak"),备份文件用于在写入失败时恢复数据
    mMode = mode;// 存储文件访问模式(如 MODE_PRIVATE)
    mLoaded = false;// 数据是否已经从磁盘加载到内存中
    mMap = null;// 内存中存储键值对的 Map,初始为 null
    mThrowable = null;// 存储加载过程中可能抛出的异常
    startLoadFromDisk();
}

首先 SharedPreferencesImpl 对象中存储了 SharedPreferences 对应的 XML 文件。并通过 makeBackupFile 方法创建了一个备份文件:

static File makeBackupFile(File prefsFile) {
    return new File(prefsFile.getPath() + ".bak");
}

该文件路径为 /data/user/0/<包名>/shared_prefs/name.xml.bak

接着调用 startLoadFromDisk 方法,将 xml 文件从磁盘加载至内存中:

private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;// 重置加载状态
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();// 开启新线程加载数据
        }
    }.start();
}

开启子线程开始加载数据:

private final Object mLock = new Object();

private void loadFromDisk() {
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        if (mBackupFile.exists()) {
            mFile.delete();// 删除主文件,可能已损坏
            mBackupFile.renameTo(mFile);// 将备份文件重命名为主文件
        }
    }
    ...
    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);// 解析 XML 到 Map
            } 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;
    }

    synchronized (mLock) {
        mLoaded = true;
        mThrowable = thrown;

        // It's important that we always signal waiters, even if we'll make
        // them fail with an exception. The try-finally is pretty wide, but
        // better safe than sorry.
        try {
            if (thrown == null) {
                if (map != null) {// 解析成功
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    mMap = new HashMap<>();
                }
            }
            // In case of a thrown exception, we retain the old map. That allows
            // any open editors to commit and store updates.
        } catch (Throwable t) {
            mThrowable = t;
        } finally {
            mLock.notifyAll();// 唤醒所有等待的线程,如 edit
        }
    }
}

如果备份文件存在(可能因上次写入失败遗留),删除主文件并重命名备份文件为主文件,确保数据完整性。

以上代码创建 BufferedInputStream 读取数据,并将 XML 解析存储至 map 中。解析成功后调用 notifyAll 唤醒你所有等待的线程。

3. 获取 SharedPreferences.Editor 对象

通过调用 edit 方法获取 SharedPreferences.Editor 对象:

@Override
public Editor edit() {
    synchronized (mLock) {
        awaitLoadedLocked();// 确保 SharedPreferences 数据已从磁盘加载完成,否则阻塞当前线程。
    }

    // 创建内部类 `EditorImpl` 实例,用于批量操作键值对。
    return new EditorImpl();
}

SharedPreferences 数据已从磁盘加载完成之前,不能创建 Editor 对象。首先通过 awaitLoadedLocked 方法,确保 SharedPreferences 数据已从磁盘加载完成,否则阻塞当前线程:

@GuardedBy("mLock")
private void awaitLoadedLocked() {
    if (!mLoaded) {// 未加载完成
        // StrictMode 标记磁盘读操作
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    while (!mLoaded) {
        try {
            mLock.wait();// 等待数据加载完
        } catch (InterruptedException unused) {
        }
    }
    if (mThrowable != null) {
        throw new IllegalStateException(mThrowable);
    }
}

StrictMode 标记,在数据未加载时触发 onReadFromDisk(),帮助开发者发现潜在的主线程磁盘操作。通过 mLock.wait() 释放锁并等待,直到其他线程调用 mLock.notifyAll()(数据加载完成后触发)。不断循环判断是否已完成数据加载,若加载过程中发生异常(如文件解析错误),抛出 IllegalStateException

若加载完成,创建并返回一个 EditorImpl 对象,该对象用来将数据写入至文件中。

4. Editor put

以 putString 方法为例:

private final Object mEditorLock = new Object();

private final Map<String, Object> mModified = new HashMap<>();

@Override
public Editor putString(String key, @Nullable String value) {
    synchronized (mEditorLock) {
        mModified.put(key, value);
        return this;
    }
}

向 Editor 的 Map mModified 中存入想要保存的值,并返回了一个 Editor 对象。

5. Editor commit

commit 方法是一个同步方法,返回值为 boolean:

@Override
public boolean commit() {
    ...

    // 将 SharedPreferences 修改提交至内存,返回提交结果
    MemoryCommitResult mcr = commitToMemory();

    // 将内存提交结果 `mcr` 加入磁盘写入队列。第二个参数为 `null` 表示这是一个同步提交(`commit`),需要立即执行。
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        // 同步等待:通过 `CountDownLatch` 阻塞当前线程,直到磁盘写入完成。
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;// 若等待过程中线程被中断,返回 `false`
    } finally {
        if (DEBUG) {
            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                    + " committed after " + (System.currentTimeMillis() - startTime)
                    + " ms");
        }
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

调用 commitToMemory 方法,将 SharedPreferences 修改提交至内存,返回提交结果:

// EditorImpl
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
    ...

    synchronized (SharedPreferencesImpl.this.mLock) {
        if (mDiskWritesInFlight > 0) {
            // 如果当前有未完成的磁盘写入(`mDiskWritesInFlight > 0`),克隆 `mMap` 避免并发修改。
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;
        mDiskWritesInFlight++;// `mDiskWritesInFlight` 记录正在进行的磁盘写入操作次数。

        // 如果有注册的监听器,初始化 `keysModified` 和 `listeners` 集合
        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }

        synchronized (mEditorLock) {// 加锁
            boolean changesMade = false;

            // 若 `mClear` 为 `true`,清空 `mapToWriteToDisk`,标记 `keysCleared`
            if (mClear) {
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.clear();
                }
                keysCleared = true;
                mClear = false;
            }

            // 遍历 `mModified`,移除值为 `this` 或 `null` 的键,否则更新值。记录变更的键到 `keysModified`
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                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`
                mCurrentMemoryStateGeneration++;
            }

            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
            listeners, mapToWriteToDisk);
}

mDiskWritesInFlight > 0 时,表示当前有未完成的磁盘写入,克隆 mMap 避免并发修改。遍历 mModified,移除值为 this 或 null 的键,否则更新值。记录变更的键到 keysModified。最后新建并返回了一个 MemoryCommitResult 对象,该对象封装了提交数据时的相关参数。

接着调用了 SharedPreferencesImpl 的 enqueueDiskWrite 方法,commit 中传入的第二个参数为 null,将内存提交结果 mcr 加入磁盘写入队列。第二个参数为 null 表示这是一个同步提交(commit),需要立即执行。

若为同步提交且无其他写入任务,直接执行写入;否则通过 QueuedWork 加入队列。

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) {
                    writeToFile(mcr, isFromSyncCommit);// 实际写入文件
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;// 写入完成后,减少进行中的写入计数
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();// 执行后续回调(如用于 apply())
                }
            }
        };

    if (isFromSyncCommit) {// commit 方法这里为 true
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;// 判断是否无其他写入任务
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();// 直接在当前线程执行写入任务
            return;
        }
    }

    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);// 加入队列异步执行
}

最后调用 notifyListeners 通知变化,保证 onSharedPreferenceChanged 的回调在主线程,将 keysModified 回调至主线程:

private void notifyListeners(final MemoryCommitResult mcr) {
    if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
        return;
    }
    if (Looper.myLooper() == Looper.getMainLooper()) {
        if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
            for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                if (listener != null) {
                    listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
                }
            }
        }
        for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
            final String key = mcr.keysModified.get(i);
            for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                if (listener != null) {
                    listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
                }
            }
        }
    } else {
        // Run this function on the main thread.
        ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
    }
}

6. Editor apply

@Override
public void apply() {
    ...

    // 将 SharedPreferences 修改提交至内存,返回提交结果
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }

                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };

    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                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);
}

enqueueDiskWrite 方法将任务加入队列:

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) {
                    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;
        }
    }

    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

通过 HandlerThread 在子线程中执行:

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static void queue(Runnable work, boolean shouldDelay) {
    Handler handler = getHandler();

    synchronized (sLock) {
        sWork.add(work);

        if (shouldDelay && sCanDelay) {
            handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
        } else {
            handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
        }
    }
}

@UnsupportedAppUsage
private static Handler getHandler() {
    synchronized (sLock) {
        if (sHandler == null) {
            HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                    Process.THREAD_PRIORITY_FOREGROUND);
            handlerThread.start();

            sHandler = new QueuedWorkHandler(handlerThread.getLooper());
        }
        return sHandler;
    }
}

确保监听器回调在主线程执行,避免 UI 操作问题,可能在数据写入磁盘之前就调用了 notifyListeners:

private void notifyListeners(final MemoryCommitResult mcr) {
    if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
        return;
    }
    if (Looper.myLooper() == Looper.getMainLooper()) {
        // 主线程直接触发监听器
        if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
            for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                if (listener != null) {
                    listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
                }
            }
        }
        for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
            final String key = mcr.keysModified.get(i);
            for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                if (listener != null) {
                    listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
                }
            }
        }
    } else {
        // 非主线程通过 Handler 切换到主线程
        // Run this function on the main thread.
        ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
    }
}