【Android 源码学习】SharedPreferences 源码学习

第一章:SharedPreferences 源码学习

文章目录

  • 第一章:SharedPreferences 源码学习
    • Android SharedPreferences的缺陷
    • MMKV、Jetpack DataStore、SharedPreferences对比
    • SharedPreferences源码阅读
      • 问题:
      • 1. SP涉及哪些类,类和类之间的关系?
        • SharedPreferences 作用
        • SharedPreferences.Editor 作用
        • OnSharedPreferenceChangeListener 作用
        • SharedPreferencesImpl
        • Context 获取SP
        • ContextWrapper Context 的代理类
        • ContextImpl Context 的实现类
      • 2. Content怎么创建SP?
        • SP的简单使用
        • 通过ContextWrapper 获取SharedPreferences对象。
        • Activity、ContextWrapper 、ContextImpl 、Context关联
        • ContextImpl 创建getSharedPreferences
        • ContextImpl 创建SharedPreferencesImpl
        • SharedPreferencesImpl初始化
      • 3. SP怎么加载文件?
        • SharedPreferencesImpl 开启线程加载文件
        • SharedPreferencesImpl 加载文件
      • 4. SP怎么保存数据到文件?
        • 获取Editor对象
        • 进行put操作
        • 进行commit操作 同步写入至内存中
        • 写入至内存 commitToMemory
        • 放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite
        • 写入内容至文件
        • 补充:apply流程
      • 5. SP怎么完成数据的序列化?
        • 传统IO读写 + XML解析
      • 6. SP是怎么读的?
      • 7. SP怎么保证线程安全?
        • 加载文件时
        • 读内容时
        • 写内容时
          • 获得Editor对象时
          • EditorImpl 进行put操作时。
          • commit/apply,写入内存 commitToMemory时。
          • commit时
          • 放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite时
          • writeToFile 时,调用setDiskWriteResult,修改结果
          • 总结
      • 8. SP怎么监听数据的变化?
        • 注册和添加监听器
        • commit/apply 时,通知监听器发生变化
        • 通知监听器发生变化
      • 9. SP为什么大规模写入,效率低,耗时?
      • 10. SP为什么会引起数据丢失?
      • 11. SP使用注意事项。
      • 12. SP怎么会引起ANR?
      • 13. SP 名字为空时,会引起异常吗?
      • 14. SP版本大于等于24 N时,不支持MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE模式
      • 15. ContextImpl 怎么做的缓存?
      • 16. SP 怎么支持文件备份的?
        • 初始化备份文件
        • 加载文件时,如果备份文件存在。删除当前文件,将备份文件改名为当前文件
        • 写入磁盘时
      • 17. SP怎么解决内存泄漏的?
    • SP源码学习总结
      • SP 优化点
        • SP 架构总结

Android SharedPreferences的缺陷

  • 引起ANR
  • 引起卡顿
  • 全部读写到内存
  • 数据丢失
  • 不能跨进程

MMKV、Jetpack DataStore、SharedPreferences对比

功能 MMKV Jetpack DataStore SharedPreferences
是否阻塞主线程
是否线程安全
是否支持跨进程
是否支持 protocol buffers
是否类型安全
是否能监听数据变化

DataStore:稳定性

MMKV:效率

SharedPreferences源码阅读

问题:

1. SP涉及哪些类,类和类之间的关系?

android.content.SharedPreferences
android.content.SharedPreferences.Editor
android.content.SharedPreferences.OnSharedPreferenceChangeListener
android.app.SharedPreferencesImpl
android.content.Context
android.content.ContextWrapper
android.app.ContextImpl

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HoteALe7-1644676177858)(E:\Android开发进阶:80集框架源码解析,深入分析Android底层原理\第一章:K-V数据持久化\SharedPreferences类图.png)]

SharedPreferences 作用

  • 读取键值对的值
getAll
getString
getStringSet
getInt
getLong
getFloat
getBoolean
contains
  • 获取SharedPreferences.Editor 对象,SharedPreferences.Editor进行键值的写
    Editor edit();
  • 注册和反注册SP内容改变的监听器
registerOnSharedPreferenceChangeListener
unregisterOnSharedPreferenceChangeListener

SharedPreferences.Editor 作用

  • 提供写入、修好、删除取键值对的值的接口
putString
putStringSet
putInt
putLong
putFloat
putBoolean
remove
clear
  • 提供将修改的后值写入文件接口
commit 同步写入
apply 异步写入

OnSharedPreferenceChangeListener 作用

  • SP内容改变的监听器
onSharedPreferenceChanged

SharedPreferencesImpl

SharedPreferences 的实现类

Context 获取SP

public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);

ContextWrapper Context 的代理类

ContextImpl Context 的实现类

实现getSharedPreferences方法。

    @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);
            if (file == null) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

2. Content怎么创建SP?

SP的简单使用

SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("test", "test111");
editor.putString("All", "All");
editor.commit();

查看SP的xml文件。

![img](file:///C:\Users\navy\AppData\Roaming\Tencent\Users\897304074\TIM\WinTemp\RichOle\ASLCEXEKaTeX parse error: Expected 'EOF', got '}' at position 7: F41WPZ}̲CIZ~U]X.png)

通过ContextWrapper 获取SharedPreferences对象。

    Context mBase;
    
    public ContextWrapper(Context base) {
        mBase = base;
    }
   @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        return mBase.getSharedPreferences(name, mode);
    }

Activity、ContextWrapper 、ContextImpl 、Context关联

【Android 源码学习】SharedPreferences 源码学习_第1张图片

ContextImpl 创建getSharedPreferences

    @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);
            if (file == null) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

ContextImpl 创建SharedPreferencesImpl

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
    	// 获取缓存
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) {
        	// 检查模式
            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");
                }
            }
            // 创建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;
}

SharedPreferencesImpl初始化

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        // 创建回滚文件
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        mThrowable = null;
        startLoadFromDisk();
    }

3. SP怎么加载文件?

SharedPreferencesImpl 开启线程加载文件

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void startLoadFromDisk() {
    synchronized (mLock) {
    	// 加对象锁,设置未加载
        mLoaded = false;
    }
    // 开启线程加载文件
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

SharedPreferencesImpl 加载文件

    private void loadFromDisk() {
    	// 
        synchronized (mLock) {
            if (mLoaded) {
                return;
            }
            // 文件回滚
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

        // Debugging
        if (mFile.exists() && !mFile.canRead()) {
            Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
        }

        Map<String, Object> map = null;
        StructStat stat = null;
        Throwable thrown = null;
        try {
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
            	// 使用BufferedInputStream、FileInputStream 读取文件
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16 * 1024);
              		// 解析XML 
                    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;
        }

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

4. SP怎么保存数据到文件?

获取Editor对象

    @Override
    public Editor edit() {
        // TODO: remove the need to call awaitLoadedLocked() when
        // requesting an editor.  will require some work on the
        // Editor, but then we should be able to do:
        //
        //      context.getSharedPreferences(..).edit().putString(..).apply()
        //
        // ... all without blocking.
        
        // 等待文件加载完毕
        synchronized (mLock) {
            awaitLoadedLocked();
        }

        return new EditorImpl();
    }

进行put操作

@Override
public Editor putString(String key, @Nullable String value) {
    synchronized (mEditorLock) {
        // Map mModified 要修改的值
        mModified.put(key, value);
        return this;
    }
}

进行commit操作 同步写入至内存中

@Override
public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
	// 修改 SharedPreferencesImpl mMap对象
    MemoryCommitResult mcr = commitToMemory();

   
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    } finally {
        if (DEBUG) {
            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                    + " committed after " + (System.currentTimeMillis() - startTime)
                    + " ms");
        }
    }
    // 通知值发生改变
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

写入至内存 commitToMemory

获得 要写入文件的K-V mapToWriteToDisk。

  • 使用mMap初始化mapToWriteToDisk
  • 将mModified的的K-V,填充到mapToWriteToDisk
     private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            boolean keysCleared = false;
            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.
                
                // 存在写冲突
                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
                    mMap = new HashMap<String, Object>(mMap);
                }
                mapToWriteToDisk = mMap;
                mDiskWritesInFlight++;

                boolean hasListeners = mListeners.size() > 0;
                if (hasListeners) {
                    keysModified = new ArrayList<String>();
                    listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                }

                synchronized (mEditorLock) {
                    boolean changesMade = false;

                    if (mClear) {
                        if (!mapToWriteToDisk.isEmpty()) {
                            changesMade = true;
                            mapToWriteToDisk.clear();
                        }
                        keysCleared = true;
                        mClear = false;
                    }
					
                    // 便利要修改的mModified Map,
                    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.
                        
                        // v 设置为null时,移除key
                        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, keysCleared, keysModified,
                    listeners, mapToWriteToDisk);
        }
    private static class MemoryCommitResult {
        final long memoryStateGeneration;
        final boolean keysCleared;
        @Nullable final List<String> keysModified;
        @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
        final Map<String, Object> mapToWriteToDisk;
        final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

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

放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite

            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
 private void enqueueDiskWrite(final MemoryCommitResult mcr,
                               final Runnable postWriteRunnable) {
     // 是否是同步 commit
     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 队列,等待执行
     QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
 }

QueuedWork

    /** {@link #getHandler() Lazily} created handler */
    @GuardedBy("sLock")
    private static Handler sHandler = null;  // HandlerThread的handle

    /** Work queued via {@link #queue} */
    @GuardedBy("sLock")
    private static LinkedList<Runnable> sWork = new LinkedList<>();
	
	// 添加至sWork 
    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);
            }
        }
    }


private static class QueuedWorkHandler extends Handler {
        static final int MSG_RUN = 1;

        QueuedWorkHandler(Looper looper) {
            super(looper);
        }

        public void handleMessage(Message msg) {
            if (msg.what == MSG_RUN) {
                // 执行消息
                processPendingWork();
            }
        }
    }

	

写入内容至文件

  @GuardedBy("mWritingToDiskLock")
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
        long startTime = 0;
        long existsTime = 0;
        long backupExistsTime = 0;
        long outputStreamCreateTime = 0;
        long writeTime = 0;
        long fsyncTime = 0;
        long setPermTime = 0;
        long fstatTime = 0;
        long deleteTime = 0;

        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        boolean fileExists = mFile.exists();

        if (DEBUG) {
            existsTime = System.currentTimeMillis();

            // Might not be set, hence init them to a default value
            backupExistsTime = existsTime;
        }

        // Rename the current file so it may be used as a backup during the next read
        if (fileExists) {
            boolean needsWrite = false;

            // Only need to write if the disk state is older than this commit
            if (mDiskStateGeneration < mcr.memoryStateGeneration) {
                if (isFromSyncCommit) {
                    needsWrite = true;
                } else {
                    synchronized (mLock) {
                        // No need to persist intermediate states. Just wait for the latest state to
                        // be persisted.
                        if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                            needsWrite = true;
                        }
                    }
                }
            }

            if (!needsWrite) {
                mcr.setDiskWriteResult(false, true);
                return;
            }

            boolean backupFileExists = mBackupFile.exists();

            if (DEBUG) {
                backupExistsTime = System.currentTimeMillis();
            }
			// fileExists 存在,备份文件不存在,将fileExists改名为mBackupFile
            if (!backupFileExists) {
                if (!mFile.renameTo(mBackupFile)) {
                    Log.e(TAG, "Couldn't rename file " + mFile
                          + " to backup file " + mBackupFile);
                    mcr.setDiskWriteResult(false, false);
                    return;
                }
            } else {
                mFile.delete();
            }
        }

        // Attempt to write the file, delete the backup and return true as atomically as
        // possible.  If any exception occurs, delete the new file; next time we will restore
        // from the backup.
        try {
            // 创建FileOutputStream
            FileOutputStream str = createFileOutputStream(mFile);

            if (DEBUG) {
                outputStreamCreateTime = System.currentTimeMillis();
            }

            if (str == null) {
                mcr.setDiskWriteResult(false, false);
                return;
            }
            // 将mapToWriteToDisk 写入xml文件
            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);

            writeTime = System.currentTimeMillis();
            FileUtils.sync(str);

            fsyncTime = System.currentTimeMillis();

            str.close();
            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

            if (DEBUG) {
                setPermTime = System.currentTimeMillis();
            }

            try {
                final StructStat stat = Os.stat(mFile.getPath());
                synchronized (mLock) {
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                }
            } catch (ErrnoException e) {
                // Do nothing
            }

            if (DEBUG) {
                fstatTime = System.currentTimeMillis();
            }

            // Writing was successful, delete the backup file if there is one.
            mBackupFile.delete();

            if (DEBUG) {
                deleteTime = System.currentTimeMillis();
            }

            mDiskStateGeneration = mcr.memoryStateGeneration;
			
         	// 设置wasWritten 为true,result为true
            mcr.setDiskWriteResult(true, true);

            if (DEBUG) {
                Log.d(TAG, "write: " + (existsTime - startTime) + "/"
                        + (backupExistsTime - startTime) + "/"
                        + (outputStreamCreateTime - startTime) + "/"
                        + (writeTime - startTime) + "/"
                        + (fsyncTime - startTime) + "/"
                        + (setPermTime - startTime) + "/"
                        + (fstatTime - startTime) + "/"
                        + (deleteTime - startTime));
            }

            long fsyncDuration = fsyncTime - writeTime;
            mSyncTimes.add((int) fsyncDuration);
            mNumSync++;

            if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
                mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");
            }

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

        // Clean up an unsuccessfully written file
        if (mFile.exists()) {
            if (!mFile.delete()) {
                Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
            }
        }
        mcr.setDiskWriteResult(false, false);
    }

补充:apply流程

        @Override
        public void apply() {
            final long startTime = System.currentTimeMillis();
			// 写入内存
            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);
        }

5. SP怎么完成数据的序列化?

传统IO读写 + XML解析

IO技术

BufferedInputStream、FileInputStream 读
FileOutputStream 写

XML解析

XmlUtils.readMapXml(str)
mlUtils.writeMapXml(mcr.mapToWriteToDisk, str);

6. SP是怎么读的?

SharedPreferencesImpl

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

7. SP怎么保证线程安全?

private final Object mLock = new Object();

加载文件时

先获取SharedPreferencesImpl的mLock锁,设置未加载

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void startLoadFromDisk() {
    synchronized (mLock) {
    	// 加对象锁,设置未加载
        mLoaded = false;
    }
    // 开启线程加载文件
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

非主线程加载文件

private void loadFromDisk() {
	// 先获取SharedPreferencesImpl的mLock锁,将备份文件重命名为 mFile
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        // 文件回滚
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }

   	```
    // 文件内容加载完毕
    ```
   	
    // 再获取SharedPreferencesImpl的mLock锁,将mLoaded设置为true,表示加载成功。并且通知其他
    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();
        }
    }
}

读内容时

先获取SharedPreferencesImpl的mLock锁,并且等待文件加载完毕才能进行读操作。

    @Override
    @Nullable
    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
@GuardedBy("mLock")
private void awaitLoadedLocked() {
    if (!mLoaded) {
        // Raise an explicit StrictMode onReadFromDisk for this
        // thread, since the real read will be in a different
        // thread and otherwise ignored by StrictMode.
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    while (!mLoaded) {
        try {
        	// 如果没有加载成功,就让当期线程进入等待状态
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    if (mThrowable != null) {
        throw new IllegalStateException(mThrowable);
    }
}

写内容时

获得Editor对象时

先获取SharedPreferencesImpl的mLock锁,并且等待文件加载完毕才能 创建EditorImpl对象

    @Override
    public Editor edit() {
    	// 
        synchronized (mLock) {
            awaitLoadedLocked();
        }

        return new EditorImpl();
    }
EditorImpl 进行put操作时。

不对SharedPreferencesImpl 的mMap进行修改。将要修改的值存放在mModified中,不影响SP的读。使用mEditorLock保证put的线程安全。

public final class EditorImpl implements Editor {
        private final Object mEditorLock = new Object();

@Override
public Editor putString(String key, @Nullable String value) {
    synchronized (mEditorLock) {
        // Map mModified 要修改的值
        mModified.put(key, value);
        return this;
    }
}

commit/apply,写入内存 commitToMemory时。

先获取SharedPreferencesImpl.this.mLock锁,保证不影响get、put操作。得到mMap的值。

获取EditorImpl mEditorLock锁,保证mModified的值正确。

        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;
                mDiskWritesInFlight++;

                boolean hasListeners = mListeners.size() > 0;
                if (hasListeners) {
                    keysModified = new ArrayList<String>();
                    listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                }
				
                synchronized (mEditorLock) {
                    boolean changesMade = false;

                    if (mClear) {
                        if (!mapToWriteToDisk.isEmpty()) {
                            changesMade = true;
                            mapToWriteToDisk.clear();
                        }
                        keysCleared = true;
                        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.
                        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, keysCleared, keysModified,
                    listeners, mapToWriteToDisk);
        }
commit时

MemoryCommitResult 使用CountDownLatch 保证setDiskWriteResult 方法只被调用了一次。

    private static class MemoryCommitResult {
        final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

        void setDiskWriteResult(boolean wasWritten, boolean result) {
            this.wasWritten = wasWritten;
            writeToDiskResult = result;
            writtenToDiskLatch.countDown();
        }
@Override
public boolean commit() {
    
    MemoryCommitResult mcr = commitToMemory();

    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        // 
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    } finally {
        if (DEBUG) {
            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                    + " committed after " + (System.currentTimeMillis() - startTime)
                    + " ms");
        }
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}
放入磁盘写入队列 SharedPreferencesImpl.this.enqueueDiskWrite时

获取mLock说,修改mDiskWritesInFlight值。

  • mDiskWritesInFlight ++ 表示内存的次数
  • mDiskWritesInFlight-- 表示写入磁盘

isFromSyncCommi

    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) {
            	// 只调用一次commit 时,直接执行writeToDiskRunnable
                writeToDiskRunnable.run();
                return;
            }
        }
		
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }
writeToFile 时,调用setDiskWriteResult,修改结果
    @GuardedBy("mWritingToDiskLock")
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
总结
  • 使用技术synchronized、[notifyAll, wait]、CountDownLatch(countDown、await)分布式相关技术
  • 使用mLock保证SP 对象的值安全,如mLoaded、mMap
    @GuardedBy("mLock")
    private Map<String, Object> mMap;
    @GuardedBy("mLock")
    private Throwable mThrowable;

    @GuardedBy("mLock")
    private int mDiskWritesInFlight = 0;

    @GuardedBy("mLock")
    private boolean mLoaded = false;

    @GuardedBy("mLock")
    private StructTimespec mStatTimestamp;

    @GuardedBy("mLock")
    private long mStatSize;

    @GuardedBy("mLock")
    private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
            new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
  • 使用mWritingToDiskLock 保证SP 与磁盘相关的值得正确性
/** Latest memory state that was committed to disk */
@GuardedBy("mWritingToDiskLock")
private long mDiskStateGeneration;

/** Time (and number of instances) of file-system sync requests */
@GuardedBy("mWritingToDiskLock")
private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
  • 使用mEditorLock 保证EditorImpl的值得正确性
        @GuardedBy("mEditorLock")
        private final Map mModified = new HashMap<>();

        @GuardedBy("mEditorLock")
        private boolean mClear = false;
  • mLock.wait() 等待文件加载、mLock.notifyAll() 通知文件加载

  • 使用mWritingToDiskLock,保存一次只能进行一次磁盘写入。

  • 使用CountDownLatch 保证setDiskWriteResult 方法只被调用了一次。

8. SP怎么监听数据的变化?

注册和添加监听器

    @GuardedBy("mLock")
    private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
            new WeakHashMap<OnSharedPreferenceChangeListener, Object>();  


	@Override
    public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
        synchronized(mLock) {
            mListeners.put(listener, CONTENT);
        }
    }

    @Override
    public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
        synchronized(mLock) {
            mListeners.remove(listener);
        }
    }

写入内存时。获取listeners集合

        // Returns true if any changes were made
        private MemoryCommitResult commitToMemory() {
            Set<OnSharedPreferenceChangeListener> listeners = null;
            
            synchronized (SharedPreferencesImpl.this.mLock) {

                boolean hasListeners = mListeners.size() > 0;
                if (hasListeners) {
                    keysModified = new ArrayList<String>();
                    listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                }

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

commit/apply 时,通知监听器发生变化

   @Override
        public boolean commit() {
   			...
            notifyListeners(mcr);
            return mcr.writeToDiskResult;
        }

通知监听器发生变化

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

9. SP为什么大规模写入,效率低,耗时?

  • SP要加载全部文件内容进入内存,才能进行读写操作。如果文件内容非常多时,就非常耗时。
  • SP采用传统的IO方式,进行文件读写。效率低
  • SP采用XMl的方式进行序列化,效率低。

10. SP为什么会引起数据丢失?

  • SP apply采用异步操作,当异步操作未完成时,程序出现崩溃,就会造成数据丢失。
  • SP writeToFile时,如果出现异常,会删除当前文件。
    @GuardedBy("mWritingToDiskLock")
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
        
        .....
        } catch (XmlPullParserException e) {
            Log.w(TAG, "writeToFile: Got exception:", e);
        } catch (IOException e) {
            Log.w(TAG, "writeToFile: Got exception:", e);
        }
        // Clean up an unsuccessfully written file
        if (mFile.exists()) {
            if (!mFile.delete()) {
                Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
            }
        }
        mcr.setDiskWriteResult(false, false);
    }

11. SP使用注意事项。

  • 获取SP完对象时,文件存在没有加载完毕的可能性。如果马上进行读写操作时,会阻塞主线程。

  • 调用edit() 方法时,会创建新的EditorImpl。不要创建过多的EditorImpl对象,可能会出现内存抖动。

  • 不要频繁的调用commit、apply进入磁盘操作。commit会阻塞主线程。commit、apply时,会调用enqueueDiskWrite方法,会调用 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit),将写入磁盘任务放入QueuedWork中。ActivityThread会调用waitToFinish方法。

    waitToFinish会调用processPendingWork。processPendingWork对sWork(LinkedList)进行遍历执行。

    当writeToDiskRunnable过多时,或者writeToDiskRunnable非常耗时时,会造成主线程卡顿,影响启动速度,甚至引起ANR。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reFUlmMq-1644676177863)(C:\Users\navy\AppData\Roaming\Typora\typora-user-images\image-20220212201214044.png)]

        public static void waitToFinish() {
    
            try {
                processPendingWork();
            } finally {
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }
    
        private static void processPendingWork() {
            long startTime = 0;
    
            if (DEBUG) {
                startTime = System.currentTimeMillis();
            }
    
            synchronized (sProcessingWork) {
                LinkedList<Runnable> work;
    
                synchronized (sLock) {
                    work = sWork;
                    sWork = new LinkedList<>();
    
                    // Remove all msg-s as all work will be processed now
                    getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
                }
    
                if (work.size() > 0) {
                    for (Runnable w : work) {
                        w.run();
                    }
    
                    if (DEBUG) {
                        Log.d(LOG_TAG, "processing " + work.size() + " items took " +
                                +(System.currentTimeMillis() - startTime) + " ms");
                    }
                }
            }
        }
    

12. SP怎么会引起ANR?

参考SP使用注意事项。

13. SP 名字为空时,会引起异常吗?

不会。

  • 当传达的name时,版本号小于19时,会将name变成 "null"字符串。

    @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);
          if (file == null) {
              file = getSharedPreferencesPath(name);
              mSharedPrefsPaths.put(name, file);
          }
      }
      return getSharedPreferences(file, mode);
    

    }

  • 获取SharedPreferencesPath路径时,及时name为null,name + ".xml"会变成 null.xml

ContextImpl

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

14. SP版本大于等于24 N时,不支持MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE模式

MODE_WORLD_READABLE 跨应用读

MODE_WORLD_WRITEABLE 跨应用写

    private void checkMode(int mode) {
        if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
            if ((mode & MODE_WORLD_READABLE) != 0) {
                throw new SecurityException("MODE_WORLD_READABLE no longer supported");
            }
            if ((mode & MODE_WORLD_WRITEABLE) != 0) {
                throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
            }
        }
    }

15. ContextImpl 怎么做的缓存?

ContextImpl 获取SP,根据包名、File做了SP的缓存。如果存在就不用创建SP。

class ContextImpl extends Context {
	// packageName [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;
    }

16. SP 怎么支持文件备份的?

初始化备份文件

    @UnsupportedAppUsage
    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
static File makeBackupFile(File prefsFile) {
    return new File(prefsFile.getPath() + ".bak");
}

加载文件时,如果备份文件存在。删除当前文件,将备份文件改名为当前文件

    private void loadFromDisk() {
        synchronized (mLock) {
            if (mLoaded) {
                return;
            }
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

写入磁盘时

如果备份文件不存在,将当前文件重命名为备份文件。写入成功时,删除备份文件。

 @GuardedBy("mWritingToDiskLock")
    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
       
        // Rename the current file so it may be used as a backup during the next read
        if (fileExists) {
            boolean needsWrite = false;
            boolean backupFileExists = mBackupFile.exists();

            if (!backupFileExists) {
                if (!mFile.renameTo(mBackupFile)) {
                    Log.e(TAG, "Couldn't rename file " + mFile
                          + " to backup file " + mBackupFile);
                    mcr.setDiskWriteResult(false, false);
                    return;
                }
            } else {
                mFile.delete();
            }
        }
        
      // Writing was successful, delete the backup file if there is one.
      mBackupFile.delete();

17. SP怎么解决内存泄漏的?

@GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
        new WeakHashMap<OnSharedPreferenceChangeListener, Object>();

SP源码学习总结

SP 优化点

  • 使用NIO、mmap代码传统的IO操作
  • 使用protocol buffer代替xml

SP 架构总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roxZrZ3l-1644676177864)(E:\Android开发进阶:80集框架源码解析,深入分析Android底层原理\第一章:K-V数据持久化\SharedPreferences类图.png)]

  • 单一职责原则 读写分离,内存写、磁盘写分离。

SharedPreferencesImpl 辅助读。Editor负责临时内存写。

  • ContextWrapper、ContextImp、 Context 装饰者模式

你可能感兴趣的:(Android,源码学习,android,android,studio,java)