前言
SharedPreferences 是 Android 数据持久化的一种方式,它通过一个 xml 文件来存储键值对,适用于简单数据的存储,比如应用配置等。
虽然 SharedPreferences 用起来简单,但其源码实现还是有许多值得研究的地方,而且这部分内容也是面试时的高频考点,因此研读 SharedPreferences 的源码是非常有必要的。
在上一篇文章中,我主要分析了 SharedPreferences 的创建过程,也就是当调用 context.getSharedPreferences() 后发生了什么。今天这篇文章将会分析与 SharedPreferences 的键值对的读写相关的源码。
另外,为了简化描述,文章正文的 SharedPreferences 将使用缩写 Sp 来代替。
读取值
这里以 getString 方法为例,该方法源码如下:
//SharedPreferencesImpl.java
private final Object mLock = new Object();
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
在读取前,先调用了 awaitLoadedLocked 方法,在还没有完成 Sp 文件读取时进行等待:
private final Object mLock = new Object();
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
//没有读取成功,先等待
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
而在分析 Sp 读取时,我们分析了 loadFromDisk 方法在读取成功后会调用 mLock.notifyAll() 方法,从而通知读取操作可以继续。
当 Sp 准备就绪后,对于值的读取就是从生成的 HashMap 中根据 key 获取对应的值。
写入值
相比键值对的读取,写入过程就复杂很多了,在写入时,需要先调用 edit 方法,获取一个 Editor 对象。
创建 Editor
edit 方法源码如下:
// SharedPreferencesImpl.java
@Override
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
当 Sp 可用时,创建了一个 EditorImpl 对象并返回。EditorImpl 是 Editor 的实现类,同时是 SharedPreferencesImpl 的内部类。
EditorImpl 只有三个属性:
public final class EditorImpl implements Editor {
//写锁
private final Object mEditorLock = new Object();
//新增或者发生变更的键值对
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
//是否需要清空之前的值
@GuardedBy("mEditorLock")
private boolean mClear = false;
//....
}
执行修改主要是通过一些重载的 put 方法,remove 方法用于移除一个键值对,clear 方法用于清空修改。
putXxx
EditorImpl 中的对应的修改方法代码如下(其中 put 方法以 putString 为例):
//EditorImpl
@Override
public Editor putString(String key, @Nullable String 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;
}
}
put 和 remove 操作的是 EditorImpl 的 mModified,而 clear 方法只是将清除标记置为 true,而且这些方法都返回 Editor 对象,方便链式调用。
通过对 EditorImpl 操作后,所有要新增或发生修改的键值对都被记录在了 mModified 这个 map 中,而要使这些更改生效,就需要调用 apply 或者 commit 进行提交。
先来看下 apply 方法。
EditorImpl#apply()
apply 方法源码如下,我略去了一些 log 信息:
@Override
public void apply() {
//先提交到内存
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//等待,确保写入被执行
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// 写入内存后就可以通知监听者了
notifyListeners(mcr);
}
apply 方法的主要逻辑是:
- 通过 commitToMemory 方法把修改提交至内存中
- 通过 enqueueDiskWrite 将磁盘写入任务提交至任务队列
- 通知监听者
EditorImpl#commitToMemory()
这个方法用于将在 Editor 上执行所有更改提交到内存的 Sp 中,并返回一个 MemoryCommitResult 对象提交信息,代码如下:
private MemoryCommitResult commitToMemory() {
// 当前内存中sp的版本号
long memoryStateGeneration;
//所有发生修改的key,用于通知监听者
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
//要写入磁盘的map
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
// 此时有写入任务正在执行,所以不能直接修改 mMap,而是克隆它
mMap = new HashMap<String, Object>(mMap);
}
//mapToWriteToDisk 为所有键值对
mapToWriteToDisk = mMap;
//写入任务数+1
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
//如果有监听者,则需要记录发生更改的 key
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
//如果调用过 clear 先执行清空操作
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
//遍历通过 Editor 做出修改的键值对
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
//在 remove 方法中,移除一个key时,将它的值设为 EditorImpl.this
//所以如果 v==this,就代表移除一个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;
}
}
//将新增加或者放生变更的key和value放到即将写入的map中
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
//记录真正发生变更的key
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
//将内存中的 Sp 版本号自增
mCurrentMemoryStateGeneration++;
}
//记录当前内存中的版本
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
//创建 MemoryCommitResult 对象
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
该方法的主要逻辑是,根据 Editor 的 mModified 中记录的发生修改的key,来更新内存中的 map 对应的 key。如果Sp有监听者,还需要记录发生修改的key,以便通知监听者。
最后会返回一个 MemoryCommitResult 对象,它记录了当前内存中 Sp 的版本号、发生变更的key、监听者和最要写入文件的map(即内存中 Sp 的所有键值对)。
这里需要注意两点:
- 对于
Editor#clear()方法,如果执行过,会先将所有键值对清空,然后写入 mModified 中记录的键值对 - 内存中的 Sp 有一个版本号标识,每次提交新的改动到内存后,该版本号会加 1
该方法返回的是一个 MemoryCommitResult 对象,MemoryCommitResult 是 SharedPreferencesImpl 的静态内部类,它的定义如下:
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
//标识当前内存的版本
final long memoryStateGeneration;
//发生修改的key集合
@Nullable final List<String> keysModified;
//sp 的监听者
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
//要写入文件的map
final Map<String, Object> mapToWriteToDisk;
//lock
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
@Nullable Set<OnSharedPreferenceChangeListener> listeners,
Map<String, Object> mapToWriteToDisk) {
this.memoryStateGeneration = memoryStateGeneration;
this.keysModified = keysModified;
this.listeners = listeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
//countDown
writtenToDiskLatch.countDown();
}
}
其中 setDiskWriteResult 方法用来设置文件写入结果,后面会讲到。
SharedPreferencesImpl#enqueDiskWrite
将变更提交的内存后得到 MemoryCommitResult 对象后,调用了 enqueDiskWrite 进行文件写入,这个方法源码如下:
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();
}
}
};
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
// mDiskWritesInFlight 在提交至内存时会自增,
// 如果是 1 说明此时没有其他的任务要写入
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//在当前线程中直接执行写入
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
该方法中第二个参数是一个 Runnable ,用于在文件写入后执行。
如果这个参数为null,那么就说明此次调用来自 commit() 方法,否则此次调用就来自 apply ()方法,在上面的 apply 方法中,我们也看到它确实传了一个 Runnable。
上面方法中的 writeToDiskRunnable 定义了文件写入的过程:
- 先调用 writeToFile 将内存的Sp写入文件
- 将 mDiskWritesInFlight 减一
- 最后在执行 postWriteRunnable。
不过 writeToDiskRunnable 的执行时机却暗藏玄机——如果此调用来自 commit() 方法,并且目前只有这次写入任务需要执行,那么就会在当前线程执行这次文件写入(如果我们的调用来自主线程,就会直接在主线程中执行文件写入,这就可能导致性能问题);其他情况都会通过 QueuedWork 把写入任务加入队列中。
关于 QueuedWork 稍后再介绍,先来看看 writeToFile 方法如何执行文件写入的。
SharedPreferencesImpl#writeToFile()
writeToFile 是执行文件写入的方法,代码如下,我省略的一些日志输出代码:
@GuardedBy("mWritingToDiskLock")
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) {
//对于短时间内大量的 apply,没有必要每次都写入,而是只执行最新一次的提交对应的写入
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();
}
}//end if(fileExists)..
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);
//写入成功,删除备份文件
mBackupFile.delete();
//更新文件版本号
mDiskStateGeneration = mcr.memoryStateGeneration;
//设置写入结果
mcr.setDiskWriteResult(true, true);
long fsyncDuration = fsyncTime - writeTime;
mSyncTimes.add((int) fsyncDuration);
mNumSync++;
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 方法进行的提交,会将每次 mcr 的版本号与当前内存的最终版本号进行对比,只有相等时才会执行文件写入。这样一来,如果短时间内有多次 apply 文件写入请求,只有最后一次写入会被真正执行。
在执行写入前,会先创建一个备份文件,当写入过程中发生意外,下次读取时可以从备份文件中恢复。
在从文件加载 Sp 的 loadFromDisk 方法中就有如下代码:
synchronized (mLock) {
if (mLoaded) {
return;
}
//如果备份文件存在,则优先从备份文件中恢复
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
正式的文件写入过程主要分以下几步:
- 创建文件输出流
- 将 map 写入到 xml 文件
- 删除备份文件
- 更新磁盘Sp对应版本号 mDiskStateGeneration
- 设置写入结果
关于第五步,针对文件写入情况,会调用 MemoryCommitResult#setDiskWriteResult 方法设置结果,这个方法源码:
//lock
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
//countDown
writtenToDiskLatch.countDown();
}
设置完结果后,调用了 writtenToDiskLatch.countDown() 以通知正在等待的线程。
以上就是 apply 的过程,主要分为两步:
- 提交修改至内存,生成一个 MemoryCommitResult 对象mcr,这个 mcr 记录着当前内存中sp的版本号、所有键值对、发生修改的键等信息
- 通过 QueuedWork 将文件写入任务入队,等待执行
在分析 QueuedWork 之前,我们先对照着看一下 commit 方法。
EditorImpl#commit()
public boolean commit() {
//1.提交至内存
MemoryCommitResult mcr = commitToMemory();
//2.将文件写入任务入队列
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null);
try {
//3.等待文件写入结果
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
}
//4. 通知监听者
notifyListeners(mcr);
//5. 返回文件写入结果
return mcr.writeToDiskResult;
}
commit 方法也包括提交内存和将文件写入任务入队,但后面还增加了等待文件写入完成的过程,因为commit 方法的返回值就是文件写入的结果。
另外,调用 SharedPreferencesImpl 的 enqueDiskWrite方法时,第二个参数传的是 null,在分析该方法时我们也看到,该方法正是以这个参数是否为 null 来区分是 commit 还是 apply 的。
为了方便阅读,再把该方法源码贴一遍:
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();
}
}
};
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
// mDiskWritesInFlight 在提交至内存时会自增,
// 如果是 1 说明此时没有其他的任务要写入
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//在当前线程中直接执行写入
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
对于 commit 方法提交,如果当前并没有其他文件写入任务要执行,那么会在当前线程执行文件写入。
接下来终于要揭晓 QueuedWork 的神秘面纱了。
QueuedWork
在看代码细节之前,先来看看官方注释对 QueuedWork 有个初步了解:
Internal utility class to keep track of process-global work that's outstanding and hasn't been finished yet.
New work will be queued. It is possible to add 'finisher'-runnables that are guaranteed to be run.
This is used to make sure the work has been finished.
This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for other things in the future. The queued asynchronous work is performed on a separate, dedicated thread.
通过以上注释,可以获取以下信息:
- 用于维护进程级别的任务
- 新任务通过queue方法入队
- 可以增加 finisher 任务,它们肯定会被执行,可以用来确保任务已经被执行完成
- 目前主要用于执行 Sp 的文件同步并提供等待完成机制
- 队列的任务会在单独线程中执行。
下面就来看看 QueuedWork 的代码实现,先来看看它的几个重要的静态属性:
静态属性
private static final long DELAY = 100;
//类的锁
private static final Object sLock = new Object();
//处理工作的锁,确保只有一个线程在处理任务
private static Object sProcessingWork = new Object();
private static Handler sHandler = null;
//存储所有finisher
private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
//存储所有任务
private static final LinkedList<Runnable> sWork = new LinkedList<>();
//是否允许延迟
private static boolean sCanDelay = true;
public static void addFinisher(Runnable finisher) {
synchronized (sLock) {
sFinishers.add(finisher);
}
}
QueuedWork#queue()
前面在 SharedPreferencesImpl 的 enqueDiskWrite() 方法中调用了queue 方法将文件写入工作入队,该方法源码如下:
public static void queue(Runnable work, boolean shouldDelay) {
//1.获取 handler
Handler handler = getHandler();
synchronized (sLock) {
//2. 将work添加到任务列表
sWork.add(work);
//3. 发送消息触发执行
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
主要逻辑就是将任务加入队列中,然后通过 handler 发送消息来出发任务的执行。
这个方法的第二个参数标识是否需要延后一段时间(DELAY 的值是100),在enqueDiskWrite() 中是这样调用的:
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
也就是说,对于 commit ,shouldDelay 为 false;对于 apply ,shouldDelay 为true。
shouldDelay 决定了在通过 handler 发送消息时是否启用延时。
那为什么要 apply 进行延时呢?
前面在分析 SharedPreferencesImpl 的 writeToFile 方法中有提到,该方法中有如下判断:
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
//对于 apply,没有必要每次都写入,而是只执行最新一次的提交对应的写入
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
DELAY 的值是常量100,如果100ms 内有多次 apply 提交,这个延时可以确保当100ms后,任务队列中的文件写入任务被统一处理时,只有最新的apply提交对应的文件写入任务会真正被执行。因为只有它对应的mcr的版本号是和内存 一致的,其他的mcr版本都低于内存版本,这样可以有效去除冗余的文件写入,提升性能。
getHandler()
上面方法中 handler 的获取调用了 getHandler() 方法,其源码如下:
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
//创建新的线程
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
可以看到,初始化时,新建了一个 HanlderThread ,并基于它创建了一个 QueuedWorkHandler 赋值给 sHandler。
QueuedWorkHandler
QueuedWorkHandler 是 QueuedWork 的静态内部类,它的定义很简单,在收到MSG_RUN消息后,调用 processPendingWork() 处理所有待执行的任务。
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
private static void processPendingWork() {
synchronized (sProcessingWork) {
LinkedList<Runnable> work;
synchronized (sLock) {
work = (LinkedList<Runnable>) sWork.clone();
sWork.clear();
// 移除所有MSG_RUN消息
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
//按顺序执行任务
for (Runnable w : work) {
w.run();
}
}
}
}
processPendingWork 的逻辑也很简单,就是将任务列表中的任务按序执行。正常情况下,这个过程就是在 getHandler 中创建的 HandlerThread 中进行。
那不正常情况呢?
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)
*/
public static void waitToFinish() {
Handler handler = getHandler();
synchronized (sLock) {
//清空所有 MSG_RUN 消息,因为下面会进行处理
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
}
// 没有必要在延时了
sCanDelay = false;
}
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
try {
// 执行 finisher
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
//...
}
waitToFinish 会在当前线程立刻执行所有待执行的任务,任务执行完后会一并执行所有 finisher 来通知任务执行完成。
而通过方法的注释可以看出,这个方法主要在 Activity的 onPause 方法中、BroadcastReceiver 的 onReceive 方法后、以及Service 的 onCommand 后等地方调用(具体见下面代码),以确保所有任务都被执行没有丢失,但这会导致在主线程执行文件写入,是有可能造成性能问题的。
//Service 启动时执行 QueuedWork.waitToFinish();
private void handleServiceArgs(ServiceArgsData data) {
Service s = mServices.get(data.token);
if (s != null) {
try {
if (data.args != null) {
data.args.setExtrasClassLoader(s.getClassLoader());
data.args.prepareToEnterProcess();
}
int res;
if (!data.taskRemoved) {
//调用Service的 onStartCommand
res = s.onStartCommand(data.args, data.flags, data.startId);
} else {
s.onTaskRemoved(data.args);
res = Service.START_TASK_REMOVED_COMPLETE;
}
// waitToFinish
QueuedWork.waitToFinish();
//.....
}
}
}
//service 停止时执行 QueuedWork.waitToFinish();
private void handleStopService(IBinder token) {
Service s = mServices.remove(token);
if (s != null) {
try {
//调用Service的onDestroy方法
s.onDestroy();
s.detachAndCleanUp();
//.....
// waitToFinish
QueuedWork.waitToFinish();
//....
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
"Unable to stop service " + s
+ ": " + e.toString(), e);
}
Slog.i(TAG, "handleStopService: exception for " + token, e);
}
}
}
//在 Android 11 之前,在 Activity onPause调用后执行 QueuedWork.waitToFinish();
@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
int configChanges, PendingTransactionActions pendingActions, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
if (userLeaving) {
performUserLeavingActivity(r);
}
r.activity.mConfigChangeFlags |= configChanges;
performPauseActivity(r, finished, reason, pendingActions);
// Make sure any pending writes are now committed.
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
mSomeActivitiesChanged = true;
}
}
//在Android 11之后,在 Activity onStop 后调用 waitToFinish
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
reason);
//...
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
//...
}
建议
通过对 Sp 源码的分析,我们还可以知道在使用时有哪些需要注意的地方:
- 尽量使用apply
- 多次 put ,一次apply
- 拆分 sp 文件的大小,避免一个 app 只使用一个 Sp,这样文件将会变得很大,写入时间会变长。如果恰好卡在 waitToFinish 这样的时间点,有可能造成 ANR。
- 不要连续多次 edit(), 应该获取一次获取edit(),然后多次执行putxxx()
好了,与 Sp 的读写相关的源码就先分析到这里了,欢迎讨论~