Matrix 源码阅读笔记 —— TracePlugin(中)

183 阅读15分钟

20241027-34.jpg

书接上文,本篇文章继续介绍 MatrixTracePlugin 中其中没有介绍完的功能。

SignalAnrTracer

SignalAnrTracer 的主要功能是监控 ANR 信号,和获取系统的 ANRtrace 文件。直接先看 SignalAnrTracer#onAlive() 方法。

@Override
protected void onAlive() {
    super.onAlive();
    if (!hasInit) {
        nativeInitSignalAnrDetective(sAnrTraceFilePath, sPrintTraceFilePath);
        AppForegroundUtil.INSTANCE.init();
        hasInit = true;
    }

}

nativeInitSignalAnrDetective() 方法也是 C++ 的实现。

static void nativeInitSignalAnrDetective(JNIEnv *env, jclass, jstring anrTracePath, jstring printTracePath) {
    const char* anrTracePathChar = env->GetStringUTFChars(anrTracePath, nullptr);
    const char* printTracePathChar = env->GetStringUTFChars(printTracePath, nullptr);
    // ANR Trace 文件输出路径
    anrTracePathString = std::string(anrTracePathChar);
    // 手动触发的 ANR Trace 文件输出路径
    printTracePathString = std::string(printTracePathChar);
    // 初始化 AnrDumper 类
    sAnrDumper.emplace(anrTracePathChar, printTracePathChar);
}

看看 AnrDumper 的构造函数:

AnrDumper::AnrDumper(const char* anrTraceFile, const char* printTraceFile) {
    // must unblock SIGQUIT, otherwise the signal handler can not capture SIGQUIT
    mAnrTraceFile = anrTraceFile;
    mPrintTraceFile = printTraceFile;

    // 添加 SIGQUIT 的信号 Set
    sigset_t sigSet;
    sigemptyset(&sigSet);
    sigaddset(&sigSet, SIGQUIT);

    // 解锁 SIGQUIT 信号
    pthread_sigmask(SIG_UNBLOCK, &sigSet , &old_sigSet);
}

Android 中的 ANR 信号是 SIGQUIT,进程启动的时候会把这个信号锁住,上面的代码就是解除锁定。AnrDumper 继承于 SignalHandler,再来看看它的构造函数。

SignalHandler::SignalHandler() {
    std::lock_guard<std::mutex> lock(sHandlerStackMutex);

    if (!sHandlerStack)
        sHandlerStack = new std::vector<SignalHandler*>;

    // 设置 Stack 的大小
    installAlternateStackLocked();
    // 设置自定义的 QUIT 的信号的 handler.
    installHandlersLocked();
    installNativeBacktraceHandlersLocked();

    // 将当前对象添加到 QUIT 信号处理
    sHandlerStack->push_back(this);
}

看看 installAlternateStackLocked() 方法是如何设置 Stack 的大小。

static void installAlternateStackLocked() {
    if (sStackInstalled)
        return;

    memset(&sOldStack, 0, sizeof(sOldStack));
    memset(&sNewStack, 0, sizeof(sNewStack));
    static constexpr unsigned kSigStackSize = std::max(16384, SIGSTKSZ);

    // 获取旧的 StackSize,如果小于想要设置 Size,就设置成想要设置的 StackSize
    if (sigaltstack(nullptr, &sOldStack) == -1 || !sOldStack.ss_sp || sOldStack.ss_size < kSigStackSize) {
        sNewStack.ss_sp = calloc(1, kSigStackSize);
        sNewStack.ss_size = kSigStackSize;
        if (sigaltstack(&sNewStack, nullptr) == -1) {
            free(sNewStack.ss_sp);
            return;
        }
    }

    sStackInstalled = true;
}

继续看 installHandlersLocked() 方法如何替换 SIGQUIT 的处理函数的。

bool SignalHandler::installHandlersLocked() {
    if (sHandlerInstalled) {
        return false;
    }

    // 获取旧的信号 Handler
    if (sigaction(TARGET_SIG, nullptr, &sOldHandlers) == -1) {
        return false;
    }

    struct sigaction sa{};
    // QUIT 信号处理函数
    sa.sa_sigaction = signalHandler;
    sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;

    // 添加新的信号处理 Handler
    if (sigaction(TARGET_SIG, &sa, nullptr) == -1) {
        return false;
    }

    sHandlerInstalled = true;
    return true;
}

上面的代码也是比较简单,先是获取到默认的信号处理 Handler 保存到 sOldHandlers,然后再去替换掉默认的信号处理 Handler,新的信号处理函数是 signalHandler()

void SignalHandler::signalHandler(int sig, siginfo_t* info, void* uc) {
    std::unique_lock<std::mutex> lock(sHandlerStackMutex);

    // 回调 QUIT 信号
    for (auto it = sHandlerStack->rbegin(); it != sHandlerStack->rend(); ++it) {
        (*it)->handleSignal(sig, info, uc);
    }

    lock.unlock();
}

sHandlerStack 集合中添加了信号监听的 listener。在 SignalHandler 的构造函数中就会把它自己添加到监听中。

void AnrDumper::handleSignal(int sig, const siginfo_t *info, void *uc) {
    // 获取发送 QUIT 信号的进程 PID
    int fromPid1 = info->_si_pad[3];
    int fromPid2 = info->_si_pad[4];

    // 获取当前进程的 PID
    int myPid = getpid();
    // 是否是当前进程自己发送的
    bool fromMySelf = fromPid1 == myPid || fromPid2 == myPid;
    if (sig == SIGQUIT) {
        pthread_t thd;
        // 创建新的线程来处理 QUIT 信号
        if (!fromMySelf) {
            // AMS 发送的,也就是真正的 ANR
            pthread_create(&thd, nullptr, anrCallback, nullptr);
        } else {
            // 自己发送的
            pthread_create(&thd, nullptr, siUserCallback, nullptr);
        }
        pthread_detach(thd);
    }
}

这里的 ANR 信号分为两种,一种是自己发送的,可以通过这种方式来获取当前进程的各种信息。一种是真的发生了 ANR,是 AMS 发送的。判断这两种情况是通过发送信号的进程 PID 来判断。这两种情况的处理函数分别是 siUserCallback()anrCallback()。其实两个函数的处理方式大同小异,我们只分析 anrCallback() 方法。

/**
 * AMS ANR 信号处理
 * @param arg
 * @return
 */
static void *anrCallback(void* arg) {
    // 回调 Java 方法,通知 ANR
    anrDumpCallback();
    if (strlen(mAnrTraceFile) > 0) {
        // Hook SignalCatcher 线程写 ANR 信息,将 ANR 信息 Copy 一份到 mAnrTraceFile 文件中。
        hookAnrTraceWrite(false);
    }
    // 再将 ANR 信号发送给 SignalCatcher 线程
    sendSigToSignalCatcher();
    return nullptr;
}

收到信号后就做三件事:1. 回调 Java 层;2. 执行对 SignalCatcher 写入 ANR Trace 文件的 Hook,以便获取 Trace 文件的内容;3. 将信号发送给 SignalCatcher 线程。

看看 hookAnrTraceWrite() 方法是如何实现的:

void hookAnrTraceWrite(bool isSiUser) {
    int apiLevel = getApiLevel();
    if (apiLevel < 19) {
        return;
    }

    if (!fromMyPrintTrace && isSiUser) {
        return;
    }

    if (isHooking) {
        return;
    }

    isHooking = true;

    // Hook 文件打开方法
    if (apiLevel >= 27) {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libcutils\.so$",
                               "connect", (void *) my_connect, (void **) (&original_connect));
    } else {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libart\.so$",
                               "open", (void *) my_open, (void **) (&original_open));
    }

    // Hook 写方法
    if (apiLevel >= 30 || apiLevel == 25 || apiLevel == 24) {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libc\.so$",
                               "write", (void *) my_write, (void **) (&original_write));
    } else if (apiLevel == 29) {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libbase\.so$",
                               "write", (void *) my_write, (void **) (&original_write));
    } else {
        xhook_grouped_register(HOOK_REQUEST_GROUPID_ANR_DUMP_TRACE, ".*libart\.so$",
                               "write", (void *) my_write, (void **) (&original_write));
    }

    xhook_refresh(true);
}

这里会 hook 文件的打开和写方法。文件打开方法根据不同的版本 hook 后的处理函数分别是 my_connect()my_open() 方法,文件写方法 hook 后的处理方法是 my_write()

// 原来的文件打开方法
int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);
// Android 8.1 及其以后的版本,Hook 替换的文件打开方法
int my_connect(int __fd, const struct sockaddr* __addr, socklen_t __addr_length) {
    if (__addr!= nullptr) {
        // 如果是打开的 anr 信息写入文件
        if (strcmp(__addr->sa_data, HOOK_CONNECT_PATH) == 0) {
            // 记录 SignalCatcher 线程的 tid
            signalCatcherTid = gettid();
            // 标记正在写入 Anr 信息文件
            isTraceWrite = true;
        }
    }
    // 调用原来的打开方法
    return original_connect(__fd, __addr, __addr_length);
}
// 原来的文件打开方法
int (*original_open)(const char *pathname, int flags, mode_t mode);

// Android 8.1 以前的版本,Hook 替换的文件打开方法
int my_open(const char *pathname, int flags, mode_t mode) {
    if (pathname!= nullptr) {
        // 如果是打开的 anr 信息写入文件
        if (strcmp(pathname, HOOK_OPEN_PATH) == 0) {
            // 记录 SignalCatcher 线程的 tid
            signalCatcherTid = gettid();
            // 标记正在写入 Anr 信息文件
            isTraceWrite = true;
        }
    }
    return original_open(pathname, flags, mode);
}

它是根据文件的打开名称来判断是否是打开的 ANR Trace 文件,Android 8.1 及其以后的版本文件名是 /dev/socket/tombstoned_java_traceAndroid 8.1 以前的版本文件名是 /data/anr/traces.txt。如果是 ANR Trace 文件,那么就标记一下,供后面的 my_write() 方法处理。

// 原来的写方法
ssize_t (*original_write)(int fd, const void* const __pass_object_size0 buf, size_t count);
// Hook 的写方法
ssize_t my_write(int fd, const void* const buf, size_t count) {
    // 如果正在写入 ANR 信息文件,同时当前的线程是 SignalCatcher 线程
    if(isTraceWrite && gettid() == signalCatcherTid) {
        isTraceWrite = false;
        signalCatcherTid = 0;
        if (buf != nullptr) {
            std::string targetFilePath;
            // 判断是 AMS 发过来的信号,还是自己发的信号,写入到不同的文件。
            if (fromMyPrintTrace) {
                targetFilePath = printTracePathString;
            } else {
                targetFilePath = anrTracePathString;
            }
            if (!targetFilePath.empty()) {
                char *content = (char *) buf;
                // 写入到我们自己的文件中
                writeAnr(content, targetFilePath);

                // 回调 Java 层 ANR Trace 文件写入完毕
                if(!fromMyPrintTrace) {
                    anrDumpTraceCallback();
                } else {
                    printTraceCallback();
                }
                fromMyPrintTrace = false;
            }
        }
    }
    // 调用原来的写方法
    return original_write(fd, buf, count);
}

写入到我们自己的文件中以后,再通知 Java 层。在 Java 层中会收到 Native 的两个回调,一是收到 ANR 信号后的回调,另一个就是 ANR Trace 文件写入完毕后的回调。对应的方法分别是 onANRDumped()onANRDumpTrace()

先看看 onANRDumped() 方法的实现:

/**
 * 收到 ANR 信号回调,Native 调用
 */
@RequiresApi(api = Build.VERSION_CODES.M)
@Keep
private synchronized static void onANRDumped() {
    final CountDownLatch anrDumpLatch = new CountDownLatch(1);
    new Thread(new Runnable() {
        @Override
        public void run() {
            onAnrDumpedTimeStamp = System.currentTimeMillis();
            MatrixLog.i(TAG, "onANRDumped");
            // 获取主线程栈
            stackTrace = Utils.getMainThreadJavaStackTrace();
            MatrixLog.i(TAG, "onANRDumped, stackTrace = %s, duration = %d", stackTrace, (System.currentTimeMillis() - onAnrDumpedTimeStamp));

            // 获取 Cgroup 信息
            cgroup = readCgroup();
            MatrixLog.i(TAG, "onANRDumped, read cgroup duration = %d", (System.currentTimeMillis() - onAnrDumpedTimeStamp));
            // 前后台
            currentForeground = AppForegroundUtil.isInterestingToUser();
            MatrixLog.i(TAG, "onANRDumped, isInterestingToUser duration = %d", (System.currentTimeMillis() - onAnrDumpedTimeStamp));
            // 再次检查是否是真的 ANR 信号
            confirmRealAnr(true);
            anrDumpLatch.countDown();
        }
    }, ANR_DUMP_THREAD_NAME).start();

    try {
        // 等检查线程完事儿。
        anrDumpLatch.await(anrReportTimeout, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
        //empty here
    }
}

除了收集一些信息,还会通过 confirmRealAnr() 方法去检查是否真的发生了 ANR

@RequiresApi(api = Build.VERSION_CODES.M)
private static void confirmRealAnr(final boolean isSigQuit) {
    MatrixLog.i(TAG, "confirmRealAnr, isSigQuit = " + isSigQuit);
    // 判断主线程是否阻塞,判断方式是取出主线程的最新一条 Message,是否延迟执行,如果是前台 延迟 2s,如果是后台延迟 10s 就算 ANR
    boolean needReport = isMainThreadBlocked();
    if (needReport) {
        // 上报 ANR
        report(false, isSigQuit);
    } else {
        // 再次开启一个线程去检查 ANR 状态
        new Thread(new Runnable() {
            @Override
            public void run() {
                checkErrorStateCycle(isSigQuit);
            }
        }, CHECK_ANR_STATE_THREAD_NAME).start();
    }
}

首先检查主线程的最新的 Message 是否出现了延迟执行,如果是前台延迟 2s,如果是后台延迟 10s。如果主线程没有阻塞的 Message,再开启一个线程通过 checkErrorStateCycle() 再次检查进程的异常状态。

private static void checkErrorStateCycle(boolean isSigQuit) {
    int checkErrorStateCount = 0;
    // 最大检查 40 次
    while (checkErrorStateCount < CHECK_ERROR_STATE_COUNT) {
        try {
            checkErrorStateCount++;
            // 检查是否发生ANR
            boolean myAnr = checkErrorState();
            if (myAnr) {
                // 上报 ANR
                report(true, isSigQuit);
                break;
            }

            Thread.sleep(CHECK_ERROR_STATE_INTERVAL);
        } catch (Throwable t) {
            MatrixLog.e(TAG, "checkErrorStateCycle error, e : " + t.getMessage());
            break;
        }
    }
}

/**
 * 检查当前进程是否发生了 ANR
 * @return
 */
private static boolean checkErrorState() {
    try {
        MatrixLog.i(TAG, "[checkErrorState] start");
        Application application =
                sApplication == null ? Matrix.with().getApplication() : sApplication;
        ActivityManager am = (ActivityManager) application
                .getSystemService(Context.ACTIVITY_SERVICE);

        // 获取所有异常的进程
        List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
        if (procs == null) {
            MatrixLog.i(TAG, "[checkErrorState] procs == null");
            return false;
        }

        for (ActivityManager.ProcessErrorStateInfo proc : procs) {
            MatrixLog.i(TAG, "[checkErrorState] found Error State proccessName = %s, proc.condition = %d", proc.processName, proc.condition);

            // 不是当前进程的 ANR。
            if (proc.uid != android.os.Process.myUid()
                    && proc.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) {
                MatrixLog.i(TAG, "maybe received other apps ANR signal");
                return false;
            }

            // 不是当前进程,继续查找
            if (proc.pid != android.os.Process.myPid()) continue;

            // 不是 ANR,继续查找
            if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) {
                continue;
            }

            MatrixLog.i(TAG, "error sate longMsg = %s", proc.longMsg);

            // 坐实当前进程 ANR
            return true;
        }
        return false;
    } catch (Throwable t) {
        MatrixLog.e(TAG, "[checkErrorState] error : %s", t.getMessage());
    }
    return false;
}

判断当前进程是否 ANR 的代码挺简单的,看上面的代码注释应该能看懂。

再来看看 ANR Trace 文件写入完毕后会做一些什么操作。


/**
 * ANR Trace 文件写入完毕
 */
@Keep
private static void onANRDumpTrace() {
    try {
        // 通过 ANR Trace 文件再做一个死锁检测
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(sAnrTraceFilePath)), "UTF-8"))) {
            String line;
            SimpleDeadLockDetector detector = new SimpleDeadLockDetector();
            while ((line = reader.readLine()) != null) {
                detector.parseLine(line);
                MatrixLog.i(TAG, line);
            }
            if (sSignalAnrDetectedListener != null) {
                if (detector.hasDeadLock()) {
                    sSignalAnrDetectedListener.onDeadLockAnrDetected(
                            detector.getMainThreadInfo(), detector.getLockHeldThread1Info(),
                            detector.getLockHeldThread2Info(), detector.getWaitingThreadsInfo());
                } else if (detector.getMainThreadInfo().contains("android.os.MessageQueue.nativePollOnce")) {
                    sSignalAnrDetectedListener.onMainThreadStuckAtNativePollOnce(detector.getMainThreadInfo());
                }
            }
        } catch (Throwable t) {
            MatrixLog.e(TAG, "printFileByLine failed e : " + t.getMessage());
        }
    } catch (Throwable t) {
        MatrixLog.e(TAG, "onANRDumpTrace error: %s", t.getMessage());
    }
}

通过 ANRTrace 文件再去做一个死锁的检测,这个死锁检测的代码我就不再分析了,感兴趣的可以自己再去看看。

FrameTracer

帧率检测,帧率检测在 Android 7 及其以后的版本,有专门的 API 来获取,但是在 Andorid 7 之前的版本就需要通过 Hook Choreographer 来计算帧率,学习 Choreographer 的代码对理解 Android 的绘制,动画等等还是非常重要的,我前面有写过相关的文章,没有看过的同学可以看看:[Framework] Android Choreographer 工作原理

Android 7 及其以后版本帧率检测

Android 7 及其以后版本的帧率检测直接使用 Android 提供的 API 就好了,没有太大的学习价值,简单看看就好了。

@Override
public void onAlive() {
    super.onAlive();
    if (config.isFPSEnable()) {
        forceEnable();
    }
}

public void forceEnable() {
    MatrixLog.i(TAG, "forceEnable");
    if (sdkInt >= Build.VERSION_CODES.N) {
        Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
        sceneFrameCollector = new SceneFrameCollector();
        addListener(sceneFrameCollector);
        register(new AllSceneFrameListener());
    } else {
        UIThreadMonitor.getMonitor().addObserver(looperObserver);
    }
}

Activity#onResume() 生命周期中添加帧率监听:


@RequiresApi(Build.VERSION_CODES.N)
@Override
public void onActivityResumed(Activity activity) {
    // 已经添加过监听了,跳过
    if (frameListenerMap.containsKey(activity.hashCode())) {
        return;
    }

    // 获取屏幕刷新率
    defaultRefreshRate = getRefreshRate(activity.getWindow());
    MatrixLog.i(TAG, "default refresh rate is %dHz", (int) defaultRefreshRate);

    // 帧渲染信息监听
    Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() {
        // FPS
        private float cachedRefreshRate = defaultRefreshRate;
        // 掉帧的阈值
        private float cachedThreshold = dropFrameListenerThreshold / 60f * cachedRefreshRate;
        private int lastModeId = -1;
        private int lastThreshold = -1;
        private WindowManager.LayoutParams attributes = null;

        private void updateRefreshRate(Window window) {
            if (attributes == null) {
                attributes = window.getAttributes();
            }
            if (attributes.preferredDisplayModeId != lastModeId || lastThreshold != dropFrameListenerThreshold) {
                lastModeId = attributes.preferredDisplayModeId;
                lastThreshold = dropFrameListenerThreshold;
                cachedRefreshRate = getRefreshRate(window);
                cachedThreshold = dropFrameListenerThreshold / 60f * cachedRefreshRate;
            }
        }

        /**
         * 帧更新
         * @param window The {@link Window} on which the frame was displayed.
         * @param frameMetrics the available metrics. This object is reused on every call
         * and thus <strong>this reference is not valid outside the scope of this method</strong>.
         * @param dropCountSinceLastInvocation the number of reports dropped since the last time
         * this callback was invoked.
         */
        @RequiresApi(api = Build.VERSION_CODES.N)
        @Override
        public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
            // 只监听前台
            if (isForeground()) {
                // 跳过错误的帧信息
                // skip not available metrics.
                for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal(); i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) {
                    long v = frameMetrics.getMetric(FrameDuration.indices[i]);
                    if (v < 0 || v >= HALF_MAX) {
                        // some devices will produce outliers, especially the Honor series, eg: NTH-AN00, ANY-AN00, etc.
                        return;
                    }
                }
                FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);

                // 刷新各种信息
                updateRefreshRate(window);

                // 获取帧渲染的总共时间
                long totalDuration = frameMetricsCopy.getMetric(FrameMetrics.TOTAL_DURATION);
                // 正常的帧间隔
                float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / cachedRefreshRate;
                // 掉帧计算
                float droppedFrames = Math.max(0f, (totalDuration - frameIntervalNanos) / frameIntervalNanos);

                droppedSum += droppedFrames;

                // 如果大于掉帧上限,回调
                if (dropFrameListener != null && droppedFrames >= cachedThreshold) {
                    dropFrameListener.onFrameMetricsAvailable(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), frameMetricsCopy, droppedFrames, cachedRefreshRate);
                }
                // 回调
                synchronized (listeners) {
                    for (IFrameListener observer : listeners) {
                        observer.onFrameMetricsAvailable(ProcessUILifecycleOwner.INSTANCE.getVisibleScene(), frameMetricsCopy, droppedFrames, cachedRefreshRate);
                    }
                }
            }
        }
    };

    this.frameListenerMap.put(activity.hashCode(), onFrameMetricsAvailableListener);
    // 添加帧信息监听
    activity.getWindow().addOnFrameMetricsAvailableListener(onFrameMetricsAvailableListener, MatrixHandlerThread.getDefaultHandler());
    MatrixLog.i(TAG, "onActivityResumed addOnFrameMetricsAvailableListener");
}

Activity#onDestroy() 生命周期移除监听:

@RequiresApi(Build.VERSION_CODES.N)
@Override
public void onActivityDestroyed(Activity activity) {
    try {
        // 移除帧信息监听
        activity.getWindow().removeOnFrameMetricsAvailableListener(frameListenerMap.remove(activity.hashCode()));
    } catch (Throwable t) {
        MatrixLog.e(TAG, "removeOnFrameMetricsAvailableListener error : " + t.getMessage());
    }
}

默认的帧绘制的异常上限是 10s,超过 10s 触发上报。

Android 7 以前版本帧率检测

Andorid 7 以前的版本帧率检测需要借助 UIThreadMonitor 类。
UIThreadMonitor#init() 方法作为分析的入口函数:

public void init(TraceConfig config) {
    this.config = config;

    if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
        throw new AssertionError("must be init in main thread!");
    }

    boolean historyMsgRecorder = config.historyMsgRecorder;
    boolean denseMsgTracer = config.denseMsgTracer;

    // 监听主线程 Looper 中的 Message 执行开始前后结束后。
    // Choreographer 在收到 Vsync 信号后会通过 Handler 转到主线程执行它的任务,通过监控主线程 Looper 的 Message 执行回调,就能够获取到 Choreographer 所有任务的开始和结束
    LooperMonitor.register(new LooperMonitor.LooperDispatchListener(historyMsgRecorder, denseMsgTracer) {
        @Override
        public boolean isValid() {
            return isAlive;
        }

        @Override
        public void dispatchStart() {
            super.dispatchStart();
            // 主线程 Message 开始执行,它会先于 Choreographer 的所有任务执行
            UIThreadMonitor.this.dispatchBegin();
        }

        @Override
        public void dispatchEnd() {
            super.dispatchEnd();
            // 主线程 Message 结束执行,在所有 Choreographer 任务完成后才执行
            UIThreadMonitor.this.dispatchEnd();
        }

    });
    this.isInit = true;
    choreographer = Choreographer.getInstance();
    // 获取帧间隔,如果没有获取到按 60FPS 算,间隔为 16.7ms
    frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
    //获取锁对象
    callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
    // 获取所有任务队列
    callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
    if (null != callbackQueues) {
        // 获取添加 Input 任务的方法
        addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
        // 获取添加动画任务的方法
        addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
        // 获取添加绘制任务的方法
        addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
    }
    // 获取 Vsync 信号接收对象
    vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);

    MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null,
            addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, vsyncReceiver == null, frameIntervalNanos);

    if (config.isDevEnv()) {
        addObserver(new LooperObserver() {
            @Override
            public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
                MatrixLog.i(TAG, "focusedActivity[%s] frame cost:%sms isVsyncFrame=%s intendedFrameTimeNs=%s [%s|%s|%s]ns",
                        focusedActivity, (endNs - startNs) / Constants.TIME_MILLIS_TO_NANO, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
            }
        });
    }
}

初始化的代码主要做了以下几件事:

  1. 监控主线程 Message 的开始执行和结束执行,Choreographer 在收到 Vsync 信号后会通过 Handler 转到主线程执行它的任务,通过监控主线程 LooperMessage 执行回调,就能够获取到 Choreographer 所有任务的开始和结束。
  2. 反射获取 Choreographer 任务队列实例和获取添加任务需要的方法,以供后续将自己的任务添加到 Choreographer 中。

再来看看 UIThreadMonitor#onStart() 方法的实现:

@Override
public synchronized void onStart() {
    if (!isInit) {
        MatrixLog.e(TAG, "[onStart] is never init.");
        return;
    }
    if (!isAlive) {
        this.isAlive = true;
        synchronized (this) {
            MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
            // 用于描述是否正在执行的 Choreographer 任务
            callbackExist = new boolean[CALLBACK_LAST + 1];
        }
        // Choreographer 的任务状态
        queueStatus = new int[CALLBACK_LAST + 1];
        // Choreographer 的任务耗时,如果任务还没有执行完毕存放的是开始的时间戳
        queueCost = new long[CALLBACK_LAST + 1];
        // 添加 Choreographer Input 任务
        addFrameCallback(CALLBACK_INPUT, this, true);
    }
}

上面的主要是添加了一个 ChoreographerInput 类型的任务,Input 任务是排在 Choreographer 所有的任务最前面,其次是动画任务,最后是绘制任务。也就是说这里添加一个 Input 任务后就等待下次任务的开始。

再简单看看 addFrameCallback() 方法添加自定义的 Choreographer 任务。

/**
 *  添加特定类型的 Choreographer 任务
 */
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
    if (callbackExist[type]) {
        MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist! isAddHeader:%s", type, isAddHeader);
        return;
    }

    if (!isAlive && type == CALLBACK_INPUT) {
        MatrixLog.w(TAG, "[addFrameCallback] UIThreadMonitor is not alive!");
        return;
    }
    try {
        synchronized (callbackQueueLock) {
            Method method = null;
            switch (type) {
                case CALLBACK_INPUT:
                    method = addInputQueue;
                    break;
                case CALLBACK_ANIMATION:
                    method = addAnimationQueue;
                    break;
                case CALLBACK_TRAVERSAL:
                    method = addTraversalQueue;
                    break;
            }
            if (null != method) {
                // 添加特定类型的 Choreographer 任务
                method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
                callbackExist[type] = true;
            }
        }
    } catch (Exception e) {
        MatrixLog.e(TAG, e.toString());
    }
}

我们先理一下 Choreographer 的相关任务执行顺序,主线程 Message 消息开始执行 -> Input 任务执行 -> 动画任务执行 -> 绘制任务执行 -> 主线程 Message 消息执行完毕。我们的分析代码的顺序也按照上面的顺序开始分析。

主线程 Message 消息开始执行:

/**
 * 主线程 Message 开始
 */
private void dispatchBegin() {
    // 记录开始执行的时间, 也当成 token
    token = dispatchTimeMs[0] = System.nanoTime();
    // 记录线程时间
    dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
    if (config.isAppMethodBeatEnable()) {
        // 记录方法开始,用于记录方法耗时,该功能还依赖插件插桩,标记开始
        AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);
    }
    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (!observer.isDispatchBegin()) {
                observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
            }
        }
    }
    if (config.isDevEnv()) {
        MatrixLog.d(TAG, "[dispatchBegin#run] inner cost:%sns", System.nanoTime() - token);
    }
}

将当前时间戳当成消息对应的 token;记录当前线程的时间;如果需要记录耗时的方法,手动调用 AppMethodBeat.i() 方法(后面单独写文章介绍方法耗时监控的实现)。

再来看看 Input 任务的处理:

/**
 * Choreographer Input 任务回调,过程中会添加其他的任务
 */
@Override
public void run() {
    final long start = System.nanoTime();
    try {
        // 所有的 Choreographer 任务开始
        doFrameBegin(token);

        // Choreographer Input 任务开始
        doQueueBegin(CALLBACK_INPUT);

        // 添加 Choreographer 的动画任务
        addFrameCallback(CALLBACK_ANIMATION, new Runnable() {

            @Override
            public void run() {
                // Choreographer Input 任务结束
                doQueueEnd(CALLBACK_INPUT);
                // Choreographer 动画任务开始
                doQueueBegin(CALLBACK_ANIMATION);
            }
        }, true);

        // 添加 Choreographer 绘制任务
        addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {

            @Override
            public void run() {
                // Choreographer 动画任务结束
                doQueueEnd(CALLBACK_ANIMATION);
                // 绘制任务开始
                doQueueBegin(CALLBACK_TRAVERSAL);
            }
        }, true);

    } finally {
        if (config.isDevEnv()) {
            MatrixLog.d(TAG, "[UIThreadMonitor#run] inner cost:%sns", System.nanoTime() - start);
        }
    }
}


private void doFrameBegin(long token) {
    // 标记这是一个 Choreographer 任务
    this.isVsyncFrame = true;
}

以上代码主要做以下事情:

  1. 标记这次的 Message 是一个 Choreographer 任务。
  2. 标记 Input 任务已经开始。
  3. 添加动画任务和绘制任务。

当动画任务开始执行时首先标记 Input 任务已经执行完毕了,然后标记动画任务执行开始;在绘制任务开始执行时,标记动画任务已经执行完毕,标记绘制任务执行开始。

我们看看任务标记开始和结束的方法,doQueueBegin()doQueueEnd()


private void doQueueBegin(int type) {
    queueStatus[type] = DO_QUEUE_BEGIN;
    queueCost[type] = System.nanoTime();
}

private void doQueueEnd(int type) {
    queueStatus[type] = DO_QUEUE_END;
    queueCost[type] = System.nanoTime() - queueCost[type];
    synchronized (this) {
        callbackExist[type] = false;
    }
}

代码非常简单,就是简单记录每个任务的执行状态,和每个任务的执行耗时。

再看看主线程 Message 执行完成:


/**
 * 主线程 Message 结束
 */
private void dispatchEnd() {
    long traceBegin = 0;
    if (config.isDevEnv()) {
        traceBegin = System.nanoTime();
    }

    if (config.isFPSEnable()) {
        // Message 开始执行时的时间
        long startNs = token;
        long intendedFrameTimeNs = startNs;
        if (isVsyncFrame) {
            // 如果是 Choreographer 的 Message 处理帧结束
            doFrameEnd(token);
            // 获取 Vsync 的时间戳
            intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);
        }

        // 结束的时间戳
        long endNs = System.nanoTime();

        synchronized (observers) {
            // 回调
            for (LooperObserver observer : observers) {
                if (observer.isDispatchBegin()) {
                    observer.doFrame(AppActiveMatrixDelegate.INSTANCE.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
                }
            }
        }
    }

    if (config.isEvilMethodTraceEnable() || config.isDevEnv()) {
        dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
        dispatchTimeMs[1] = System.nanoTime();
    }

    // 标记结束
    AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);

    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (observer.isDispatchBegin()) {
                observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isVsyncFrame);
            }
        }
    }

    this.isVsyncFrame = false;

    if (config.isDevEnv()) {
        MatrixLog.d(TAG, "[dispatchEnd#run] inner cost:%sns", System.nanoTime() - traceBegin);
    }
}
  1. 通过 doFrameEnd() 方法标记 Choreographer 任务结束。
  2. 然后计算每个任务的耗时信息,然后回调给 Observer

看看 doFrameEnd() 方法的实现:

private void doFrameEnd(long token) {

    // Choreographer 绘制任务结束
    doQueueEnd(CALLBACK_TRAVERSAL);

    // 错误检查
    for (int i : queueStatus) {
        if (i != DO_QUEUE_END) {
            queueCost[i] = DO_QUEUE_END_ERROR;
            if (config.isDevEnv) {
                throw new RuntimeException(String.format("UIThreadMonitor happens type[%s] != DO_QUEUE_END", i));
            }
        }
    }
    // 重制 Choreographer 任务状态
    queueStatus = new int[CALLBACK_LAST + 1];

    // 再添加一个 Choreographer Input 任务,当下次有 Choreographer 任务被触发时这个任务也会被触发。因为这里通过反射添加任务不会触发接收 Vsync 信号,但是当正常触发绘制等等任务时,除了添加任务外,还会触发 Vsync 信号。
    addFrameCallback(CALLBACK_INPUT, this, true);

}

最后

本篇文章到这里就结束了,最后还有一篇文章去介绍 TracePlugin 的剩余内容,包括方法调用的耗时监控和冷热启动耗时监控。