SharedPreferences 源码解析

SharedPreferences 源码解析

今天给大家带来 SharedPreferences 的源码解析,也是本人第一次对 framework 层的代码进行源码解析,文中有不对的地方望指出

cxk.gif

本文中的源码基于安卓13 即 api 33,SharedPreferences 各版本源码主要流程差不多,如有不同请以自己的版本为主。

创建

context.getSharedPreferences(name, Context.MODE_PRIVATE)

大家都应该 context 只有一个 实体类,那就是 android.app.ContextImpl

看下 getSharedPreferences 如何实现的

getSharedPreferences(String, int) 调用重载的 getSharedPreferences(File, int)

...


    private ArrayMap<String, File> mSharedPrefsPaths
    // name和文件的映射
...


public SharedPreferences getSharedPreferences(String name, int mode) {
    // target小于19并且name是空的话,
    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);
}

getSharedPreferencesPath 获取SharedPreferences保存的路径,data/data/shared_prefs/name.xml

// 文件路径 data/data/shared_prefs/name.xml
public File getSharedPreferencesPath(String name) {
    return makeFilename(getPreferencesDir(), name + ".xml");
}


private File getPreferencesDir() {
    synchronized (mSync) {
        if (mPreferencesDir == null) {
            mPreferencesDir = new File(getDataDir(), "shared_prefs");
        }

        return ensurePrivateDirExists(mPreferencesDir);
    }


}



// name不能有分隔符,否则抛异常了
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");
}

getSharedPreferences(File, int) 获取 SharedPreferences ,获取的是实体类 SharedPreferencesImpl

public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        // 首次获取为null,创建SharedPreferencesImpl并保存到ArrayMap中
        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");
                }
            }
            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) {
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

getSharedPreferencesCacheLocked 按包名保存<File, SharedPreferencesImpl> 表

...


    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache
    // 每个包名对应一个ArrayMap,享元设计模式
...


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

看下 checkMode 做了什么

// Android 7.0以上不在支持 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");
        }
    }
}

回到 SharedPreferencesImpl 类中,先看下它的 构造函数

SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    startLoadFromDisk();
}

makeBackupFile 备份文件,备份文件的路径就是 data/data/shared_prefs/name.xml.bak

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

startLoadFromDisk 从磁盘将文件数据映射到内存,内部通过子线程去调用 loadFromDiskmLoaded 是为了防止多次映射,因为从文件去读取是异步执行的

private void startLoadFromDisk() {
    synchronized (mLock) {

        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

loadFromDisk 真正的从文件去解析数据然后映射到内存 mMap

private void loadFromDisk() {
    synchronized (mLock) {

        if (mLoaded) {
            return;
        }
        // 备份文件存在的话,将备份文件重命名为sp文件
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }

    }



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



    // IO流解析映射成Map,这里也是通过XmlUtils解析
    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;
    }

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

存储

// 默认是apply异步存储
SharedPreferences.edit {
    putBoolean("xxx", true)
    putString("uuu", "1231")
}

实际操作类是EditorImpl,每次调用 edit 都会返回一个 EditorImpl 的实例

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


    return new EditorImpl();
}

putXXXkeyvalue 保存到 HashMap

// 采用synchronized互斥同步,返回Editor
public Editor putString(String key, @Nullable String value) {
    synchronized (mEditorLock) {
        mModified.put(key, value);
        return this;
    }




}

        
public Editor putInt(String key, int value) {
    synchronized (mEditorLock) {
        mModified.put(key, value);
        return this;
    }

}        

apply

apply 的实现,异步存储

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


    // 将awaitCommit添加到任务队列QueuedWork
    // 这个很重要,这个是SharedPreference产生ANR的根本原因
    QueuedWork.addFinisher(awaitCommit);


    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                QueuedWork.removeFinisher(awaitCommit);
            }
        };


    // 将写入的操作放入任务队列QueuedWork
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    // 将SharedPreference修改或新增的key回调给所有SharedPreference修改监听器
    notifyListeners(mcr);
}

commitToMemory 将数据写入到内存

...


    private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
        new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
...


private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    boolean keysCleared = false;
    // 将新增或者修改的键回调给SharedPreference修改监听器
    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++;


        // 可以通过registerOnSharedPreferenceChangeListener注册SharedPreference改变监听
        // 通过unregisterOnSharedPreferenceChangeListener移除监听
        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }

        synchronized (mEditorLock) {
            boolean changesMade = false;


            // 调用了clear()
            if (mClear) {
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.clear();
                }
                keysCleared = true;
                mClear = false;
            }



            // 遍历HashMap,如果内存映射的HashMap已经存在该键,判断该键对应的值是否相等
            // changesMade是为了判断数据是否有修改
            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);
                }
            }


            // 清理我们调用putXXX中的HashMap,减少内存消耗
            mModified.clear();

            // 数据有修改时,自增mCurrentMemoryStateGeneration
            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }

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

MemoryCommitResult 其实就是个数据类

private static class MemoryCommitResult {
    

    // 内存状态
    final long memoryStateGeneration;
    // 调用clear()后为true
    final boolean keysCleared;
    // 主要是为了将新增或者修改的键回调给SharedPreference修改监听器
    @Nullable final List<String> keysModified;
    // 所有的SharedPreference修改监听器
    @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
    // 这个是XML中需要写入的全部数据了,不仅包含了内存的数据,还包含了我们需要新增的数据
    final Map<String, Object> mapToWriteToDisk;
    // CountDownLatch线程同步类,只有当一个写入完成后,才能执行下一次写入
    final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

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


    private MemoryCommitResult(long memoryStateGeneration, boolean keysCleared,
            @Nullable List<String> keysModified,
            @Nullable Set<OnSharedPreferenceChangeListener> listeners,
            Map<String, Object> mapToWriteToDisk) {
        this.memoryStateGeneration = memoryStateGeneration;
        this.keysCleared = keysCleared;
        this.keysModified = keysModified;
        this.listeners = listeners;
        this.mapToWriteToDisk = mapToWriteToDisk;
    }


    void setDiskWriteResult(boolean wasWritten, boolean result) {
        this.wasWritten = wasWritten;
        writeToDiskResult = result;
        writtenToDiskLatch.countDown();
    }
}

enqueueDiskWrite 将数据写入操作放入队列

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    // 判断是否是同步写入,即是否是调用commit()
    final boolean isFromSyncCommit = (postWriteRunnable == null);


    // 将写入操作封装成Runnable,writeToFile是写入磁盘的终极操作
    // 写入完成后执行postWriteRunnable,主要是QueuedWork移除任务
    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

    // commit()的执行逻辑,直接在当前线程同步写入,所以不能在主线程调用
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }


    // 将写入的操作放入任务队列QueuedWork
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

writeToFile 最终将数据写入磁盘,通过解析 HashMap 将键值对写入 XML文件

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    

    ... 省略代码 ... 
        
    // 判断XML文件是否存在
    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();


        ... 省略代码 ...
        
        // 备份文件不存在时,将XML文件重命名为备份文件,否则删除XML文件
        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();
        }
    }
    
    // 通过XmlUtils解析HashMap,然后通过IO写入到文件,所以这里是全盘写入,不管数据的更改或者新增大小,时间消耗都很大
    try {
        FileOutputStream str = createFileOutputStream(mFile);


        // 释放锁
        if (str == null) {
            mcr.setDiskWriteResult(false, false);
            return;
        }
        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();
        }

        // 写入成功,删除备份文件
        mBackupFile.delete();

        mDiskStateGeneration = mcr.memoryStateGeneration;

        mcr.setDiskWriteResult(true, true);

        ... 省略代码 ...
                        
    } catch (XmlPullParserException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    }

    // 写入不成功,删除XML文件
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false, false);
}

commit

commit 是同步存储实现,源码其实跟 apply 差不多,它会在当前线程直接执行

public boolean commit() {
    long startTime = 0;





    if (DEBUG) {

        startTime = System.currentTimeMillis();

    }








    MemoryCommitResult mcr = commitToMemory();


    // 同步执行写入,然后将结果返回给MemoryCommitResult
    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");
        }
    }
    // 将SharedPreference修改或新增的key回调给所有SharedPreference修改监听器
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

SharedPreferences的问题

首先就是 commit 的实现,它是在当前线程同步执行,所以当前线程会阻塞直到写入完成。如果需要等待的时间过长甚至在主线程上面调用会发生什么问题呢?没错,会发生 ANR(Application Not Response)。apply 呢?是否会引起 ANR?同样的 apply 也是会引起 ANR,我们来分析下 apply 为啥会引起 ANR

apply 会将写入的操作放入到 QueuedWorkHandler 然后通过 Handler 将任务执行,QueuedWork 里的 Handler 是通过 HandlerThread 创建的。 有兴趣的可以去了解下 Handler 的工作原理以及 HandlerThread 实现原理

android.app.QueuedWork.java
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);
        }
    }

}

看下 QueuedWorkHandlerhandleMessage 做了什么

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

    }


}

也就是调用了下 processPendingWork,看下 processPendingWork 做了什么

private static void processPendingWork() {
    long startTime = 0;





    if (DEBUG) {

        startTime = System.currentTimeMillis();

    }








    synchronized (sProcessingWork) {
        LinkedList<Runnable> work;

        synchronized (sLock) {
            work = sWork;
            sWork = new LinkedList<>();


            // 移除MessageQueue的Message
            getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
        }

        // 这里是执行任务,HandlerThread是子线程的,所以apply是在子线程执行任务
        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");
            }
        }
    }
}


这样看着好像是没啥问题,因为 apply 是异步在子线程执行写入任务的。但为什么都是说 SharedPreferences 存在 ANR 问题?

还记得 apply 方法内部有这么一句代码吗

QueuedWork.addFinisher(awaitCommit);

我们看下 android.app.QueuedWork.java 内的addFinisher 方法做了什么

private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
@UnsupportedAppUsage
public static void addFinisher(Runnable finisher) {
    synchronized (sLock) {
        sFinishers.add(finisher);
    }




}

其实就是将 awaitCommit 添加到 List中,我们看下哪里会使用到 sFinishers 里面的 Runnable

android.app.QueuedWork.java 内的 waitToFinish会使用到 sFinishers 里面的 Runnable,看下 waitToFinish 做了什么

public static void waitToFinish() {
    long startTime = System.currentTimeMillis();
    boolean hadMessages = false;


    Handler handler = getHandler();


    synchronized (sLock) {
        // 从MessageQueue中移除所有Message
        if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
            handler.removeMessages(QueuedWorkHandler.MSG_RUN);

            if (DEBUG) {
                hadMessages = true;
                Log.d(LOG_TAG, "waiting");
            }
        }

        
        sCanDelay = false;
    }


    StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
    try {
        // 这里就是执行写入磁盘的操作了
        processPendingWork();
    } finally {
        StrictMode.setThreadPolicy(oldPolicy);
    }

    try {
        while (true) {
            Runnable finisher;
            // 这里就是取出awaitCommit然后执行,会使其他写入线程进入等待
            synchronized (sLock) {
                finisher = sFinishers.poll();
            }

            if (finisher == null) {
                break;
            }



            finisher.run();
        }
    } finally {
        sCanDelay = true;
    }

    synchronized (sLock) {
        long waitTime = System.currentTimeMillis() - startTime;

        if (waitTime > 0 || hadMessages) {
            mWaitTimes.add(Long.valueOf(waitTime).intValue());
            mNumWaits++;


            if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
                mWaitTimes.log(LOG_TAG, "waited: ");
            }
        }
    }
}

waitToFinish 其实就是执行 processPendingWork,将写入操作执行了,然后将 apply 中传入的 awaitCommit执行阻塞其他线程,那如果我们多次的调用 appy 呢?其他线程一直在等待状态,再来看一下 waitToFinish 方法的注释

/**
 * Trigger queued work to be processed immediately. The queued work is processed on a separate
 * thread asynchronous. While doing that run and process all finishers on this thread. The
 * finishers can be implemented in a way to check weather the queued work is finished.
 *
 * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
 * after Service command handling, etc. (so async work is never lost)
 */

英文看不懂?没关系,我英文也不怎么好,不过大概意思是知道的,这里就用有道翻译下

/** 触发要立即处理的排队工作。排队的工作在单独的线程上异步处理。在此过程中,运行并处理该线程上的所有结束程序。可以通过检查排队工作是否完成的方式来实现完成程序。从Activity基类的onPause()调用,在BroadcastReceiver的onReceive之后,在Service命令处理之后,等等(所以异步工作永远不会丢失) */

上面说了 ActivityonPause()BroadcastReceiveronReceive 会调用 waitToFinish 方法,那么实际是在哪里调用的? 是在 android.app.ActivityThread 内部调用,我们搜一下关键字就找到了 ActivityThread 内部调用的几个方法,包括 handleServiceArgshandleStopServicehandlePauseActivityhandleStopActivity。大家想一下,这几个方法都是在主线程调用的,假如说我们多次的调用 apply 方法,并且每次的写入操作都会阻塞当前线程,那么这中间的耗时是不可估计的,严重的就会产生 ANR。而且 SharedPreferences 的数据如果过大,那么 IO 写入也是耗时的。并且 SharedPreferences 写入磁盘是全量写入的,不管你新增或者修改的数据多与否,它都会将本地的数据先映射到 HashMap,然后通过 XmlUtils 解析 HashMap 之后写入到 XML 中。既然这样 SharedPreferences 的性能问题就不能忽略了,虽然说官方也对不同的版本进行了 SharedPreferences 的优化,但是 ANR 问题还是跑不掉。

有什么方式解决 SharedPreferences 的性能问题?

SharedPreferences 的替代方案

对于 Google 来说,他不可能对 SharedPreferences 的性能问题视而不见,不过他并没有继续针对 SharedPreferences 继续优化,而是在 Jetpack 中推出了 DataStore 去替代 SharedPreferences 。关于 DataStore ,你可以从官网 DataStore 去学习,后面我会对 DataStore 进行详细的讲解。DataStore 目前正式版还没支持多进程,不过你仍然可以使用 alpha 版本支持跨进程方式。

还有另一种方案就更常见了,那就是腾讯大佬开源的 MMKV ,听过腾讯是为了解决微信 key-value 写入缓慢甚至崩溃问题才开发了 MMKV ,由于是基于 mmap 内存映射,所以效率极高并且支持多进程使用。我对于 MMKV 还只停留在使用阶段,源码层往后会深入学习

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYiIV4u1' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片