Matrix源码分析(六)之 ActivityThreadHacker工作原理

在前面分析 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的初始化是必然已经完成了 ,并且这个方法只会执行一次,

相关推荐
ac-er88883 小时前
Yii框架中的队列:如何实现异步操作
android·开发语言·php
流氓也是种气质 _Cookie5 小时前
uniapp 在线更新应用
android·uniapp
zhangphil7 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲8 小时前
CentOS7使用源码安装PHP8教程整理
android
编程、小哥哥9 小时前
python操作mysql
android·python
Couvrir洪荒猛兽9 小时前
Android实训十 数据存储和访问
android
五味香12 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录12 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽14 小时前
Android实训九 数据存储和访问
android
aloneboyooo14 小时前
Android Studio安装配置
android·ide·android studio