书接上文,本篇文章继续介绍 Matrix
的 TracePlugin
中其中没有介绍完的功能。
SignalAnrTracer
SignalAnrTracer
的主要功能是监控 ANR
信号,和获取系统的 ANR
的 trace
文件。直接先看 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_trace
;Android 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());
}
}
通过 ANR
的 Trace
文件再去做一个死锁的检测,这个死锁检测的代码我就不再分析了,感兴趣的可以自己再去看看。
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);
}
});
}
}
初始化的代码主要做了以下几件事:
- 监控主线程
Message
的开始执行和结束执行,Choreographer
在收到Vsync
信号后会通过Handler
转到主线程执行它的任务,通过监控主线程Looper
的Message
执行回调,就能够获取到Choreographer
所有任务的开始和结束。 - 反射获取
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);
}
}
上面的主要是添加了一个 Choreographer
的 Input
类型的任务,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;
}
以上代码主要做以下事情:
- 标记这次的
Message
是一个Choreographer
任务。 - 标记
Input
任务已经开始。 - 添加动画任务和绘制任务。
当动画任务开始执行时首先标记 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);
}
}
- 通过
doFrameEnd()
方法标记Choreographer
任务结束。 - 然后计算每个任务的耗时信息,然后回调给
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
的剩余内容,包括方法调用的耗时监控和冷热启动耗时监控。