在前面分析 AppMethodBeat 的过程中有介绍过 ActivityThreadHacker ,但是在那篇文章中没有仔细的介绍,但是接下来如果要分析StartupTracer 又用到了 ActivityThreadHacker ,那这篇文章就先来说说 ActivityThreadHacker 的工作原理,
在讲 ActivityThreadHacker 他的工作原理之前,需要先了解一个因为 SharedPreferences 的工作原理引起的一个系统性的bug, 那就是为什么 SP 的apply方法分明就是异步的缺会导致主线程卡住引ANR的问题
先来复现一下事故现场看看 SharedPreferencesImpl 的 apply 的方法
API 21
java
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
// 将结果写入到内存,比较重要的,在这里创建了一个 CountDownLatch ,每次写入都会创建
// 所以这个 CountDownLatch 给的等待个数是1
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 内的链表中
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);
}
在apply方法中,先是将创建的事务写入到了内存中,并返回了修改后的结果,利用这个结果中的 CountDownLatch 创建了一个任务,放入到了 QueuedWork 中的链表里面,然后开始写磁盘
API 21
java
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//创建写磁盘的任务
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
// 使用同步锁写文件
synchronized (mWritingToDiskLock) {
writeToFile(mcr);
}
//类锁改次数
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
//通知 上层 CountDownLatch 开始等待
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
final boolean isFromSyncCommit = (postWriteRunnable == null);
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
这里的思路也比较简单,那就是在使用线程池工作前,先使用写文件的锁来写文件,在使用类锁修改写的次数,最后在通知到上层的 CountDownLatch 让他等待,只有在设置结果后才会释放CountDownLatch 的等待
ini
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
我们再去看看引起 ANR 的地方 ActivityThread
java
private void handleStopActivity(IBinder token, boolean show, int configChanges) {
ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
StopInfo info = new StopInfo();
performStopActivityInner(r, info, show, true);
if (localLOGV) Slog.v(
TAG, "Finishing stop of " + r + ": show=" + show
+ " win=" + r.window);
updateVisibility(r, show);
//关键逻辑
if (!r.isPreHoneycomb()) {
//罪魁祸首
QueuedWork.waitToFinish();
}
info.activity = r;
info.state = r.state;
info.persistentState = r.persistentState;
mH.post(info);
mSomeActivitiesChanged = true;
}
// sdk 的 targetVersion 是否小于11,很明显现在都大于11
public boolean isPreHoneycomb() {
if (activity != null) {
return activity.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.HONEYCOMB;
}
return false;
}
我这里截取的是 handleStopActivity 中的逻辑,但并不代表着 只有他在使用,这里放一下在 ActivityThread 中谁在用
那就来看看这个waitToFinish都干了什么东西
csharp
public static void waitToFinish() {
Runnable toFinish;
while ((toFinish = sPendingWorkFinishers.poll()) != null) {
toFinish.run();
}
}
很简单的一段代码,如果 sPendingWorkFinishers 这个 Queue 中有任务先执行这个任务,这就导致了主线程中进行了等待了
那么这个任务是什么任务呢
csharp
public static void add(Runnable finisher) {
sPendingWorkFinishers.add(finisher);
}
没错就是我们在sp apply 的时候添加进去的 CountDownLatch 的等待任务,如果这个任务被行了那么主线程就卡死在这里了,如果我们同时提交的sp任务比较多或者 由于当时环境问题导致 写文件速度比较慢,都会导致 ANR
API29
再来看看API 29的代码
ini
public static void waitToFinish() {
long startTime = System.currentTimeMillis();
boolean hadMessages = false;
Handler handler = getHandler();
synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
// 这里移除了部分延迟工作的任务
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
if (DEBUG) {
hadMessages = true;
Log.d(LOG_TAG, "waiting");
}
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
// 允许在主线程中写文件,并返回旧的策略
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
//把任务取出来直接在当前线程处理
processPendingWork();
} finally {
//恢复旧的策略
StrictMode.setThreadPolicy(oldPolicy);
}
try {
while (true) {
Runnable finisher;
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: ");
}
}
}
}
很明显这里的代码量一下子增加了非常多,但是基础的逻辑没有太大的变化,显示移除了由于 handler 中的 QueuedWorkHandler.MSG_RUN 消息,修改严格模式的策略将任务在当前线程中执行,在修改后再恢复到之前的模式,最后还是遍历这个队列 ,按次执行
接下来我们的主角就入场了 ActivityThreadHacker
在 ActivityThread 所有的生命周期事件都是通过 Handler 来处理的,并且 Handler 有一个比较重要的逻辑
less
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
那就是如果在 mCallback 不为空的情况下,所有的消息是需要先经过 mCallback.handleMessage(msg) 来预处理一下的, 而 ActivityThreadHacker 就是通过 Hook 替换原始Handler ,判断对应的生命周期事件,清空 sFinishers 中的任务,来达到优化ANR 的目的
下面来看一下代码
ini
public static void hackSysHandlerCallback() {
try {
sApplicationCreateBeginTime = SystemClock.uptimeMillis();
sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThreadValue = field.get(forName);
Field mH = forName.getDeclaredField("mH");
mH.setAccessible(true);
Object handler = mH.get(activityThreadValue);
Class<?> handlerClass = handler.getClass().getSuperclass();
if (null != handlerClass) {
Field callbackField = handlerClass.getDeclaredField("mCallback");
callbackField.setAccessible(true);
Callback originalCallback = (Callback)callbackField.get(handler);
ActivityThreadHacker.HackCallback callback = new ActivityThreadHacker.HackCallback(originalCallback);
callbackField.set(handler, callback);
}
MatrixLog.i("Matrix.ActivityThreadHacker", "hook system handler completed. start:%s SDK_INT:%s", new Object[]{sApplicationCreateBeginTime, VERSION.SDK_INT});
} catch (Exception var9) {
MatrixLog.e("Matrix.ActivityThreadHacker", "hook system handler err! %s", new Object[]{var9.getCause().toString()});
}
}
使用自己的 ActivityThreadHacker.HackCallback 包装一下 ActivityThread 中的 mH 中的 Callback来打包目的,在来看看他是如何清空这个队列的
typescript
private void fix() {
try {
Class cls = Class.forName("android.app.QueuedWork");
Field field = cls.getDeclaredField("sPendingWorkFinishers");
if (field != null) {
field.setAccessible(true);
ConcurrentLinkedQueue<Runnable> runnables = (ConcurrentLinkedQueue)field.get((Object)null);
runnables.clear();
MatrixLog.i("Matrix.ActivityThreadHacker", "Fix SP ANR sPendingWorkFinishers.clear successful", new Object[0]);
}
} catch (ClassNotFoundException var4) {
MatrixLog.e("Matrix.ActivityThreadHacker", "Fix SP ANR ClassNotFoundException = " + var4.getMessage(), new Object[0]);
var4.printStackTrace();
} catch (IllegalAccessException var5) {
MatrixLog.e("Matrix.ActivityThreadHacker", "Fix SP ANR IllegalAccessException =" + var5.getMessage(), new Object[0]);
var5.printStackTrace();
} catch (NoSuchFieldException var6) {
MatrixLog.e("Matrix.ActivityThreadHacker", "Fix SP ANR NoSuchFieldException = " + var6.getMessage(), new Object[0]);
var6.printStackTrace();
} catch (Exception var7) {
MatrixLog.e("Matrix.ActivityThreadHacker", "Fix SP ANR Exception = " + var7.getMessage(), new Object[0]);
var7.printStackTrace();
}
}
反射 QueuedWork 中的 sPendingWorkFinishers 并将他清空,
但是到了 26以后由于属性名字就变了,而且需要在特定的生命周期事件才能清空,其他的清空了也没有太大的意义
ini
if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT <= 25) {
if (msg.what == SERIVCE_ARGS || msg.what == STOP_SERVICE
|| msg.what == STOP_ACTIVITY_SHOW || msg.what == STOP_ACTIVITY_HIDE
|| msg.what == SLEEPING) {
MatrixLog.i(TAG, "Fix SP ANR is enabled");
fix();
}
}
在查看这个问题的处理方式时还找了今日头条的解决方案,他们的解决方案有点类似 Matrix 中的 IdleHandlerLagTracer 的方式,他们是替换了这个 QueueWrok 中的 queue ,让他的 poll 方法返回null
对于 sp 的apply 问题导致的anr 的产生,真的是由于 sp 的设计缺陷导致的吗,个人感觉应该是过度使用sp而导致的,而且在使用sp的过程中为了减少文件大小,sp的key一定要精简,并且针对sp 的分包也是非常关键的
其实这篇文章真正想要引出来的主角就是 以下代码
ini
if (!isCreated) {
if (isLaunchActivity || msg.what == CREATE_SERVICE
|| msg.what == RECEIVER) { // todo for provider
ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
ActivityThreadHacker.sApplicationCreateScene = msg.what;
isCreated = true;
sIsCreatedByLaunchActivity = isLaunchActivity;
MatrixLog.i(TAG, "application create end, sApplicationCreateScene:%d, isLaunchActivity:%s", msg.what, isLaunchActivity);
synchronized (listeners) {
for (IApplicationCreateListener listener : listeners) {
listener.onApplicationCreateEnd();
}
}
}
}
这段代码的就引出了我们下一篇文章的故事 StartUpTracker ,Callback 中接收到 actvity service receiver等的生命皱起了,证明 ActivityThread 对于Application的初始化是必然已经完成了 ,并且这个方法只会执行一次,