Android SharedPreferences源码分析

Android SharedPreferences源码分析

概述

SharedPreferences 是 Android 平台上轻量级的 K-V 存储框架。

SharedPreferences 采用 XML 文件格式持久化键值对数据,文件的存储位置位于应用沙盒的内部存储 /data/data/<包名>/shared_prefs/ 位置,每个 XML 文件对应于一个 SharedPreferences 对象。

一个sp文件(XML文件) 对应一个SharedPreferences对象。

基本使用

SharedPreferences sp = context.getSharedPreferences("app", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("name", "小明")
        .putInt("age", 18)
        .putBoolean("sex", true)
        .apply();

String name = sp.getString("name", "");
int age = sp.getInt("age", 0);
boolean sex = sp.getBoolean("sex", false);

查看 /data/data/com.example.myapplication/shared_prefs/app.xml 文件:

在这里插入图片描述

源码分析

获取SP对象

ContextImpl 类是 Context 抽象类的实现类。

// ContextImpl.java

class ContextImpl extends Context {
   
    // sp文件的根目录
    private File mPreferencesDir;
    // 缓存name和file对象的对应关系
    private ArrayMap<String, File> mSharedPrefsPaths;
    // 缓存包名、file对象和SharedPreferencesImpl对象的对应关系
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

    // 通过name获取sp对象
    public SharedPreferences getSharedPreferences(String name, int mode) {
         
        File file;
        synchronized (ContextImpl.class) {
   
            // mSharedPrefsPaths缓存对象为null则创建
            if (mSharedPrefsPaths == null) {
   
                mSharedPrefsPaths = new ArrayMap<>();
            }
            // 从mSharedPrefsPaths缓存中获取file对象
            file = mSharedPrefsPaths.get(name);
            // 如果file对象为null,则创建file对象
            if (file == null) {
   
                // 通过路径获取file对象
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

    // 创建file对象
    public File getSharedPreferencesPath(String name) {
   
        return makeFilename(getPreferencesDir(), name + ".xml");
    }

    // 获取sp文件的根目录
    private File getPreferencesDir() {
   
        synchronized (mSync) {
   
            if (mPreferencesDir == null) {
   
                mPreferencesDir = new File(getDataDir(), "shared_prefs");
            }
            return ensurePrivateDirExists(mPreferencesDir);
        }
    }

    // 通过file对象获取sp对象
    public SharedPreferences getSharedPreferences(File file, int mode) {
   
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
   
            // 从sSharedPrefsCache缓存获取ArrayMap
            final ArrayMap<File, SharedPreferencesImpl> cache = 							 													getSharedPreferencesCacheLocked();
            // 从缓存中获取sp对象
            sp = cache.get(file);
            if (sp == null) {
   
                // 如果sp对象为null,则创建并缓存
                checkMode(mode);     
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        return sp;
    }

    // 从sSharedPrefsCache缓存获取sp对象
    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
   
        // sSharedPrefsCache缓存对象为null则创建
        if (sSharedPrefsCache == null) {
   
            sSharedPrefsCache = new ArrayMap<>();
        }
        // 获取包名
        final String packageName = getPackageName();
        // sSharedPrefsCache通过包名获取ArrayMap
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        // packagePrefs为null则创建
        if (packagePrefs == null) {
   
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }
        return packagePrefs;
    }
}

初始化和读取数据

// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {
   

    // 目标文件
    private final File mFile;
    // 锁
    private final Object mLock = new Object();
    // 文件是否加载
    private boolean mLoaded = false;

    SharedPreferencesImpl(File file, int mode) {
   
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        mThrowable = null;
        // 开始加载文件
        startLoadFromDisk();
    }

    // 开启线程加载文件
    private void startLoadFromDisk() {
   
        synchronized (mLock) {
   
            mLoaded = false;
        }
        // 开启线程
        new Thread("SharedPreferencesImpl-load") {
   
            public void run() {
   
                loadFromDisk();
            }
        }.start();
    }

    // 加载文件
    private void loadFromDisk() {
   
        synchronized (mLock) {
   
            if (mLoaded) {
   
                return;
            }
            // 如果有备份文件,则恢复备份文件
            if (mBackupFile.exists()) {
   
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

        Map<String, Object> map = null;  
        if (mFile.canRead()) {
   
            // 读取文件
            BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
            // 解析XML文件并转为Map
            map = (Map<String, Object>) XmlUtils.readMapXml(str);            
        }

        synchronized (mLock) {
   
            mLoaded = true;
            if (map != null) {
   
                // 使用新Map
                mMap = map;
            } else {
   
                mMap = new HashMap<>();
            }
            // 解析完文件后唤醒锁
            mLoaded = true;
            mLock.notifyAll();
        }
    }

    // 获取数据
    public String getString(String key, @Nullable String defValue) {
   
        synchronized (mLock) {
   
            // 查询数据时可能会阻塞等待
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

    // 等待
    private void awaitLoadedLocked() {
   
        while (!mLoaded) {
   
            try {
   
                mLock.wait();
            } catch (InterruptedException unused) {
   
            }
        }
    }
}    

写入数据

虽然 ContextImpl 中使用了内存缓存,但是最终数据还是需要执行磁盘 IO 持久化到磁盘文件中。如果每一次 “变更操作” 都对应一次磁盘 “写回操作” 的话,不仅效率低下,而且没有必要。所以 SharedPreferences 会使用 “事务” 机制,将多次变更操作聚合为一个 “事务”,一次事务最多只会执行一次磁盘写回操作。
SharedPreferences 的事务操作由 Editor 接口实现。

// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {
   

    public Editor edit() {
   
        // 等待文件加载完成
        synchronized (mLock) {
   
            awaitLoadedLocked();
        }
        // 创建编辑器
        return new EditorImpl();
    }

    // 编辑器
    public final class EditorImpl implements Editor {
   
        // 锁对象
        private final Object mEditorLock = new Object();
        // 修改记录
        private final Map<String, Object> mModified = new HashMap<>();
        // 清除全部数据的标记
        private boolean mClear = false;

        // 存放String类型数据
        @Override
        public Editor putString(String key, @Nullable String value) {
   
            synchronized (mEditorLock) {
   
                mModified.put(key, value);
                return this;
            }
        }

        // 存放int类型数据
        @Override
        public Editor putInt(String key, int value) {
   
            synchronized (mEditorLock) {
   
                mModified.put(key, value);
                return this;
            }
        }

        // 删除数据
        @Override
        public Editor remove(String key) {
   
            synchronized (mEditorLock) {
   
                mModified.put(key, this);
                return this;
            }
        }

        // 清除所有数据
        @Override
        public Editor clear() {
   
            synchronized (mEditorLock) {
   
                mClear = true;
                return this;
            }
        }

        // 异步提交
        @Override
        public void apply() {
   
            ...
        }

        // 同步提交,并返回操作结果是否成功
        @Override
        public boolean commit() {
   
            ...
        }
    }   
}
MemoryCommitResult

MemoryCommitResult 是一个事务对象,收集需要写入磁盘的数据。

// MemoryCommitResult.java

private static class MemoryCommitResult {
   
    // 内存版本号
    final long memoryStateGeneration;
    // 写入磁盘的数据
    final Map<String, Object> mapToWriteToDisk;
    // 同步计数器  
    final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

    @GuardedBy("mWritingToDiskLock")
    volatile boolean writeToDiskResult = false;
    boolean wasWritten = false;

    void setDiskWriteResult(boolean wasWritten, boolean result) {
   
        this.wasWritten = wasWritten;
        writeToDiskResult = result;
        // 唤醒等待锁
        writtenToDiskLatch.countDown();
    }
}
commitToMemory()
// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {
   
    // 每次提交数据自增 1,事务结束自减 1
    private int mDiskWritesInFlight = 0;
    // 内存版本
    private long mCurrentMemoryStateGeneration;
    // 磁盘版本
    private long mDiskStateGeneration;
    // 修改记录
    private final Map<String, Object> mModified = new HashMap<>();
    // 清除全部数据的标记
    private boolean mClear = false;

    // 全量提交到内存
    private MemoryCommitResult commitToMemory() {
   
        long memoryStateGeneration;
        boolean keysCleared = false;
        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);
            }
            // 需要写入磁盘的数据
            mapToWriteToDisk = mMap; // 全量Map
            // 自增加1
            mDiskWritesInFlight++;

            synchronized (mEditorLock) {
   
                // 是否发生有效修改
                boolean changesMade = false;

                // 清除全部数据
                if (mClear) {
   
                    if (!mapToWriteToDisk.isEmpty()) {
   
                        changesMade = true;
                        mapToWriteToDisk.clear();
                    }
                    keysCleared = true;
                    mClear = false;
                }

                // 将内存中的mModified都数据整合到mapToWriteToDisk中
                for (Map.Entry<String, Object> e : mModified.entrySet()) {
   
                    String k = e.getKey();
                    Object v = e.getValue();
                    if (v == this || v == null) {
   
                        // 如果value是this,表示删除这个key
                        if (!mapToWriteToDisk.containsKey(k)) {
   
                            continue;
                        }
                        mapToWriteToDisk.remove(k);
                    } else {
   
                        // 修改key-value值
                        if (mapToWriteToDisk.containsKey(k)) {
   
                            Object existingValue = mapToWriteToDisk.get(k);
                            if (existingValue != null && existingValue.equals(v)) {
   
                                continue;
                            }
                        }
                        mapToWriteToDisk.put(k, v);
                    }

                    // 标记修改完成
                    changesMade = true;
                    // 记录发生修改的key
                    if (hasListeners) {
   
                        keysModified.add(k);
                    }
                }

                // 重置内存中的mModified
                mModified.clear();

                // 修改完成,自增加1
                if (changesMade) {
   
                    mCurrentMemoryStateGeneration++;
                }
                // 修改内存版本号
                memoryStateGeneration = mCurrentMemoryStateGeneration;
            }
        }
        return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
                                      listeners, mapToWriteToDisk);
    }
}
commit()
// SharedPreferencesImpl.EditorImpl#commit()

public boolean commit() {
   
    // 写入到内存,并获取事务对象
    MemoryCommitResult mcr = commitToMemory();
    // 写入到磁盘
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
    // 写入等待
    mcr.writtenToDiskLatch.await();       
    // 触发回调监听
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}
apply()
// SharedPreferencesImpl.EditorImpl#apply()

public void apply() {
   
    // 写入到内存,并获取事务对象
    final MemoryCommitResult mcr = commitToMemory();
    // 创建一个Runnable
    final Runnable awaitCommit = new Runnable() {
   
        @Override
        public void run() {
   
            // 阻塞线程,直到磁盘操作执行完毕
            mcr.writtenToDiskLatch.await();
        }
    };
    // 将Runnable提交到QueuedWork中
    QueuedWork.addFinisher(awaitCommit);
    // 创建一个Runnable,写入成功后QueuedWork删除awaitCommit任务
    Runnable postWriteRunnable = new Runnable() {
   
        @Override
        public void run() {
   
            awaitCommit.run();
            QueuedWork.removeFinisher(awaitCommit);
        }
    };
    // 提交写入磁盘任务
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    // 触发回调监听
    notifyListeners(mcr);
}
enqueueDiskWrite()
// SharedPreferencesImpl#enqueueDiskWrite()

// postWriteRunnable为null表示commit同步提交,不为null表示apply异步提交
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) {
   
                // 自减1
                mDiskWritesInFlight--;
            }
            if (postWriteRunnable != null) {
   
                postWriteRunnable.run();
            }
        }
    };

    // 如果是同步操作
    if (isFromSyncCommit) {
   
        // 判断mDiskWritesInFlight变量,如果存在并发写入则交给QueuedWork
        boolean wasEmpty = false;
        synchronized (mLock) {
   
            wasEmpty = mDiskWritesInFlight == 1;
        }
        // wasEmpty为true,表示没有并发写入,则直接执行写入任务
        if (wasEmpty) {
   
            writeToDiskRunnable.run();
            return;
        }
    }

    // 如果是异步操作,提交到QueuedWork执行
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
writeToFile()
// SharedPreferencesImpl#writeToFile()

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
   
    // 判断旧文件是否存在	
    boolean fileExists = mFile.exists();

    if (fileExists) {
   
        // 是否执行写入操作
        boolean needsWrite = false;
        // 如果磁盘版本小于内存版本,执行磁盘写入操作
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
   
            if (isFromSyncCommit) {
   
                // 如果是同步执行,一定执行写入操作
                needsWrite = true;
            } else {
   
                // 如果是异步执行,只有最新的内存版本菜执行写入操作
                synchronized (mLock) {
   
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
   
                        needsWrite = true;
                    }
                }
            }
        }

        // 无效的异步写回,直接结束
        if (!needsWrite) {
   
            mcr.setDiskWriteResult(false, true);
            return;
        }

        // 文件备份
        boolean backupFileExists = mBackupFile.exists();

        if (!backupFileExists) {
   
            // 如果没有备份文件,将旧文件改为备份文件
            if (!mFile.renameTo(mBackupFile)) {
   
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
   
            // 如果有备份文件,则删除旧文件
            mFile.delete();
        }
    }

    try {
   
        // 执行写入操作,写入到xml文件中
        FileOutputStream str = createFileOutputStream(mFile);     
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        FileUtils.sync(str);
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

        // 写入成功,直接删除备份文件
        mBackupFile.delete();
        // 同步磁盘版本号
        mDiskStateGeneration = mcr.memoryStateGeneration;
        // 写入成功
        mcr.setDiskWriteResult(true, true);

        return;
    } catch (XmlPullParserException e) {
   
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
   
        Log.w(TAG, "writeToFile: Got exception:", e);
    }

    // 写入失败会执行这里
    if (mFile.exists()) {
   
        if (!mFile.delete()) {
   
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false, false);
}

主动等待写回任务结束

在 apply() 方法中,在执行 enqueueDiskWrite() 前创建了 awaitCommit 任务并加入到 QueudWork 等待队列,直到磁盘写回结束才将 awaitCommit 移除。这个 awaitCommit 任务是做什么的呢?

mcr.writtenToDiskLatch.await();

可以看到,在主线程的 Activity#onPause、Activity#onStop、Service#onStop、Service#onStartCommand 等生命周期状态变更时,会调用 QueudeWork.waitToFinish():

// ActivityThread.java

public void handlePauseActivity(...) {
   
    performPauseActivity(r, finished, reason, pendingActions);
    if (r.isPreHoneycomb()) {
   
        QueuedWork.waitToFinish();
    }
}

private void handleStopService(IBinder token) {
   
    QueuedWork.waitToFinish();
    ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}

waitToFinish() 会执行所有 sFinishers 等待队列中的 aWaitCommit 任务,主动等待所有磁盘写回任务结束。在写回任务结束之前,主线程会阻塞在等待锁上,这里也有可能发生 ANR。

总结

SharedPreferences 是一个轻量级的 K-V 存储框架。从源码分析中,可以看到 SharedPreferences 在读写性能、可用性方面都有做一些优化,例如:锁细化、事务化、事务过滤、文件备份等。

建议:

  • 因为SP初始化时会加载全部sp文件和全量提交,因此大文件尽可能的拆分多个文件,修改多个数据时尽可能一起提交。
  • 因为 commit() 是同步操作,apply() 是异步操作,因此推荐使用 apply()。

相关推荐

  1. SDWebImage分析

    2024-01-28 02:12:01       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-28 02:12:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-28 02:12:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-28 02:12:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-28 02:12:01       20 阅读

热门阅读

  1. 【js大坑】苹果ios的日期时间兼容问题

    2024-01-28 02:12:01       33 阅读
  2. 《Leetcode》动态规划-求不相邻数的最大和

    2024-01-28 02:12:01       30 阅读
  3. 加固安全防线:解决常见漏洞的实用指南

    2024-01-28 02:12:01       37 阅读
  4. ubuntu 编译使用 liblas 读取点云

    2024-01-28 02:12:01       40 阅读
  5. Scikit-Learn 高级教程——高级特征工程

    2024-01-28 02:12:01       31 阅读
  6. 【算法题】67. 二进制求和

    2024-01-28 02:12:01       36 阅读
  7. Unity UIBasePanel 简单的ui基类

    2024-01-28 02:12:01       32 阅读