这些ANR捕获要点你必须知道
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这些ANR捕获要点你必须知道相关的知识,希望对你有一定的参考价值。
当发生ANR后,App会弹窗提示”应用失去响应,是否重启“,然后系统会dump一份trace文件,存在data/anr目录下。
普通应用如何监控ANR的发生呢?
这个时候,系统肯定是知道ANR发生了,所以像Console和Firebase这些工具都能很好的拿到ANR发生的时间和trace文件的内容。
但是,作为面向普通应用的监控sdk,很多系统应用有的权限都没有,我们怎么才能判断ANR的发生呢?另外高版本的android系统,限制了普通应用读取trace文件的权限,我们又如何拿到ANR发生时dump出来的trace文件呢?
ANR捕获的基本原理
发生ANR的时候,系统的system_server进程会发送SIGQUIT信号给一些进程,包括发生ANR的进程、占用系统资源较多的进程,让这些进程进行dump操作。
我们可以通过在应用内拦截这个信号,来判断当前进程是否需要dump。但是收到信号并不表示当前进程一定发生了ANR,也可能是其他进程发生了ANR,刚好我们的进程运行在后台,且资源消耗比较多,system_server也发送信号让我们进程去做dump。所以光凭这个逻辑,无法判断是否是当前进程发生的ANR,还需要加一些其他的判断条件。接下来就让我们从Matrix的源码角度,看看业界通用的比较成熟的通用方案吧。
监听SIGQUIT信号
1.首先Matrix在AnrDumper方法里,将sigquit设置成SIG_UNBLOCK。
因为Android默认把SIGQUIT信号设置成BLOCKED,所以只会响应sigwait,而不会进入我们设置的handler中。所以需要通过pthread_sigmask将SIGQUIT信号设置成UNBLOCK,才能进入我们的handler方法。
AnrDumper::AnrDumper(const char* anrTraceFile, const char* printTraceFile)
// must unblock SIGQUIT, otherwise the signal handler can not capture SIGQUIT
mAnrTraceFile = anrTraceFile;
mPrintTraceFile = printTraceFile;
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &sigSet , &old_sigSet);
2.然后通过sigaction,将方法地址设置成我们的signalHandler。
bool SignalHandler::installHandlersLocked()
// 构造一个sigaction,将方法地址设置成我们的signalHandler
struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
// 设置一个新的sigaction
if (sigaction(TARGET_SIG, &sa, nullptr) == -1)
return false;
sHandlerInstalled = true;
return true;
3.当系统向进程发送SIGQUIT信号时,会进入我们的signalHandler方法,可以在这里可以对信号进行进一步的处理。Matrix是直接交给AnrDumper的handleSignal方法处理。
void SignalHandler::signalHandler(int sig, siginfo_t* info, void* uc)
std::unique_lock<std::mutex> lock(sHandlerStackMutex);
for (auto it = sHandlerStack->rbegin(); it != sHandlerStack->rend(); ++it)
(*it)->handleSignal(sig, info, uc);
lock.unlock();
4.在处理完之后,将SIGQUIT重新发成系统的SignalCatcher处理。
static void sendSigToSignalCatcher()
int tid = getSignalCatcherThreadId();
syscall(SYS_tgkill, getpid(), tid, SIGQUIT);
判断是否真的发生ANR
收到SIGQUIT只能说明进程要处理dump,并不能说明当前应用程序发生了ANR,如果直接上报ANR,会造成多报。所以还需要加更多的判断条件,来判断当前进程是否真正发生ANR。
1.当前程序是否有NOT_RESPONDING标记
如果应用发生了ANR,AMS会给进程加一个NOT_RESPONDING的标记,同时生成shortMsg和longMsg。如果有这个标记,可以马上判断当前进程发生了一次ANR。
但是用NOT_RESPONDING标记来判断ANR,也有几个缺陷:
- 生成比较慢,需要轮询去查,如果用户提前杀掉app,可能导致上报失败
- 无法监控后台ANR,因为后台ANR,除非了【开发者选项】中打开【显示所有“应用程序无响应】,否则App不会进入NOT_RESPONDING状态。
Matrix的做法是开启一个子线程,去循环查是否有NOT_RESPONDING标记,500ms查一次,一共查20s。
new Thread(new Runnable()
@Override
public void run()
checkErrorStateCycle(isSigQuit);
, CHECK_ANR_STATE_THREAD_NAME).start();
循环检查进程的Error State。
private static void checkErrorStateCycle(boolean isSigQuit)
int checkErrorStateCount = 0;
while (checkErrorStateCount < CHECK_ERROR_STATE_COUNT)
try
checkErrorStateCount++;
boolean myAnr = checkErrorState();
// 如果有NOT_RESPONDING标记,马上上报
if (myAnr)
report(true, isSigQuit);
break;
Thread.sleep(CHECK_ERROR_STATE_INTERVAL);
catch (Throwable t)
MatrixLog.e(TAG, "checkErrorStateCycle error, e : " + t.getMessage());
break;
单词检查的逻辑,通过getProcessInErrorState获取。
private static boolean checkErrorState()
try
Application application =
sApplication == null ? Matrix.with().getApplication() : sApplication;
ActivityManager am = (ActivityManager) application
.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
for (ActivityManager.ProcessErrorStateInfo proc : procs)
if (proc.uid != android.os.Process.myUid()
&& proc.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING)
MatrixLog.i(TAG, "maybe received other apps ANR signal");
return false;
// 当前进程,且状态是NOT_RESPONDING,才返回true
if (proc.pid != android.os.Process.myPid()) continue;
if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING)
continue;
return true;
return false;
catch (Throwable t)
MatrixLog.e(TAG, "[checkErrorState] error : %s", t.getMessage());
return false;
2.主线程是否被block
通过反射,可以获取主线程Looper中的消息头mMessages。因为MessageQueue中的消息是根据Message.when的时间升序排列的,所以反射获取的消息头就是第一个待处理的消息。
判断主线程是否被block的方法:判断当前时间和when的差值。
- 前台:如果差值大于2s,算block
- 后台:差值大于10s,算block
private static final long FOREGROUND_MSG_THRESHOLD = -2000;
private static final long BACKGROUND_MSG_THRESHOLD = -10000;
@RequiresApi(api = Build.VERSION_CODES.M)
private static boolean isMainThreadBlocked()
try
MessageQueue mainQueue = Looper.getMainLooper().getQueue();
Field field = mainQueue.getClass().getDeclaredField("mMessages");
field.setAccessible(true);
final Message mMessage = (Message) field.get(mainQueue);
if (mMessage != null)
anrMessageString = mMessage.toString();
long when = mMessage.getWhen();
if (when == 0)
return false;
long time = when - SystemClock.uptimeMillis();
anrMessageWhen = time;
long timeThreshold = BACKGROUND_MSG_THRESHOLD;
if (currentForeground)
timeThreshold = FOREGROUND_MSG_THRESHOLD;
return time < timeThreshold;
else
MatrixLog.i(TAG, "mMessage is null");
catch (Exception e)
return false;
return false;
主动模拟SIGQUIT信号
另外,我们自己进程也可以手动触发一个SIGQUIT信号,来获取trace文件里额外的信息。比如在发生卡顿的时候,也可以使用SIGQUIT来让我们进程dump一个当前的堆栈。
不过这个方法比较重,因为dump堆栈的时候,需要暂停所有的线程,会影响程序的运行效率。所以一般不会在线上轻易使用,可以在线下监控的时候用。
static void nativePrintTrace()
fromMyPrintTrace = true;
kill(getpid(), SIGQUIT);
总结
综上所述,判断当前进程是否发生ANR的业界通用做法如下:
- 通过拦截SIGQUIT来判断系统是否向进程发送信号
- 收到SIGQUIT后,判断主线程是否block,如果主线程block,上报ANR
- 循环获取App ErrorState,如果状态变成NOT_RESPONDING,上报ANR
可能有些人上文中提到Android 性能监控框架 Matrix 使用不是很熟,针对这块知识点做了一些整理《Android 性能监控框架 Matrix 解析》,大家可以参考着学习:https://qr18.cn/FVlo89
以上是关于这些ANR捕获要点你必须知道的主要内容,如果未能解决你的问题,请参考以下文章