SharedPreferences 源码解析
今天给大家带来 SharedPreferences
的源码解析,也是本人第一次对 framework
层的代码进行源码解析,文中有不对的地方望指出
本文中的源码基于安卓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
从磁盘将文件数据映射到内存,内部通过子线程去调用 loadFromDisk
,mLoaded
是为了防止多次映射,因为从文件去读取是异步执行的
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();
}
putXXX 将 key
和 value
保存到 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);
}
}
}
看下 QueuedWorkHandler
的 handleMessage
做了什么
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命令处理之后,等等(所以异步工作永远不会丢失) */
上面说了 Activity
的 onPause()
和 BroadcastReceiver
的 onReceive
会调用 waitToFinish
方法,那么实际是在哪里调用的? 是在 android.app.ActivityThread
内部调用,我们搜一下关键字就找到了 ActivityThread
内部调用的几个方法,包括 handleServiceArgs
、handleStopService
、handlePauseActivity
和 handleStopActivity
。大家想一下,这几个方法都是在主线程调用的,假如说我们多次的调用 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
还只停留在使用阶段,源码层往后会深入学习