大家都知道,当产生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_sigmaskSIGQUIT信号设置成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);
}
  1. 然后经过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;
}
  1. 当体系向进程发送SIGQUIT信号时,会进入咱们的signalHandler办法,能够在这里能够对信号进行进一步的处理。Matrix是直接交给AnrDumperhandleSignal办法处理。
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();
}
  1. 在处理完之后,将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的符号,一起生成shortMsglongMsg。假如有这个符号,能够立刻判别当时进程产生了一次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;
    }
  1. 主线程是否被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

下一篇文章会讲,怎么获取体系dump的trace文件,敬请期待。