背景:
近来有学员朋友有向我请教一个面试题:
大概意思就是如果只是一个第三方App比如微信、淘宝等普通第三方app,没有任何特权情况下,请问你有什么好的方案来监听app自身产生了ANR,及如何dump出相关的ANR信息堆栈等。
针对这种ANR日志监测导出等,其实对于做整机的设备厂商来说都不是啥事情,因为源码都有,要各种权限都可以有。但是对于普通第三方app来说要监测自己是否ANR,而且还有获取相关trace那就不容易,主要原因还是因为权限问题。
1、正常ANR产生可能都是日志直接抓取看到比较方便,但是第三方app根本没有权限来获取整机日志
2、正常anr都会产生有相关的trace在/data/anr/路径下,但是这个目录在高版本对于普通第三方app没有权限。
因为没有权限,网络上以前说的可以通过FileObserve监听/data/anr/下是否增加文件,然后读取文件是否有自己包名和ANR关键子方案也不行。
所以这个第三方app检测anr问题,确实还不是那么简单哈,那么下面我们来看看各个大厂一些开源库是如何做到的这种ANR检测的。
相关经典成熟开源库中获取相关思路
腾讯的matrix:
https://github.com/Tencent/matrix
老东家爱奇艺xcrash:https://github.com/iqiyi/xCrash
xcrash功能很多就不一一介绍了,我们感兴趣他的ANR监测的原理部分,xcrash刚好有如下一张图:
图中其实就可以看出监测ANR原理:自己创建对应的Signal Catcher来捕获信号进行相关业务trace导出等处理。
大家有兴趣可以去认真看看这两个经典开源库哈,这里我们只提取关于ANR检测部分的,然后做成demo给大家。
总结开源库检测ANR原理
正常在产生ANR时候,都会发送一个SIGNAL_QUIT信号给app进程来实现退出,所以就是抓住这个信号的发送与接受作为切入点来实现监测ANR。
马哥实战第三app监测ANR的demo
具体源码直接开源给大家,一般大家使用下面这个文件就可以测试用了
//
// Created by test on 25-3-18.
//
#ifndef MYNATIVECPP_SIGNAL_HOOK_H
#define MYNATIVECPP_SIGNAL_HOOK_H
#include <jni.h>
#include <string.h>
#include <pthread.h>
#include <syslog.h>
#include <android/log.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#define TAG "马哥wx号:androidframework007"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
static const char* TARGET_NAME = "Signal Catcher";
//获取Signal Catcher线程id,目的是后续给它发相关信号
int getSignalCatcherThreadId() {
DIR* dir = opendir("/proc/self/task");
if (!dir) return -1;
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (entry->d_type != DT_DIR) continue;
char path[256];
snprintf(path, sizeof(path), "/proc/self/task/%s/comm", entry->d_name);
int fd = open(path, O_RDONLY);
if (fd == -1) continue;
char name[100];
ssize_t len = read(fd, name, sizeof(name)-1);
close(fd);
if (len <= 0) continue;
name[len] = '\0';
if (strncmp(name, TARGET_NAME, strlen(TARGET_NAME)) == 0) {
closedir(dir);
return atol(entry->d_name);
}
}
closedir(dir);
return -1;
}
void signalHandler(int sig, siginfo_t *info, void *uc) {
if (sig == SIGQUIT) {
//第3部分监听到了信号进行相关的处理
LOGD("sig == SIGQUIT ");
//TODO 监听到ANR进行相关处理、
//第4部分重新向Signal Catcher线程发送一个SIGQUIT
int tid = getSignalCatcherThreadId();
tgkill(getpid(), tid, SIGQUIT);
}
}
void monitor() {
// 第一部分 pthread_sigmask把SIGQUIT设置为UNBLOCK,因为如果不设置会导致无法接受到SIGQUIT,因为zygote
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr);
// 第二部分 signalHandler信号触发处理方法
struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
sigaction(SIGQUIT, &sa, nullptr);
}
#endif //MYNATIVECPP_SIGNAL_HOOK_H
代码中注释很详细,主要分为以下几个部分:
第一部分
主要SIGQUIT信号设置成SIG_UNBLOCK,因为Android默认把SIGQUIT设置成了BLOCKED,所以只会响应sigwait而不会进入到我们设置的handler方法中。
第二部分 主要针对SIGQUIT信号设置对应的signalHandler信号处理函数,这样有信号来了就可以在signalHandler
第三部分 主要针对SIGQUIT信号来了,在signalHandler进行相关的逻辑处理,这块就给大家空出来哈,比如保存相关的堆栈等异常等
第四部分 Signal Handler抢到了SIGQUIT后,原本的Signal Catcher线程中的sigwait就不再能收到SIGQUIT了
如果缺少了重新向SignalCatcher发送SIGQUIT的步骤,AMS就一直等不到ANR进程写堆栈,直到20秒超时后,才会被迫中断,而继续之后的流程。直接的表现就是ANR弹窗非常慢(20秒超时时间),并且/data/anr目录下无法正常生成完整的 ANR Trace文件。
更多framework实战开发,关注下面“千里马学框架”