大家都知道,當(dāng)發(fā)生ANR后,App會彈窗提示”應(yīng)用失去響應(yīng),是否重啟“,然后系統(tǒng)會dump一份trace文件,存在data/anr目錄下。
普通應(yīng)用如何監(jiān)控ANR的發(fā)生呢?
這個時候,系統(tǒng)肯定是知道ANR發(fā)生了,所以像Console和Firebase這些工具都能很好的拿到ANR發(fā)生的時間和trace文件的內(nèi)容。
但是,作為面向普通應(yīng)用的監(jiān)控sdk,很多系統(tǒng)應(yīng)用有的權(quán)限都沒有,我們怎么才能判斷ANR的發(fā)生呢?另外高版本的Android系統(tǒng),限制了普通應(yīng)用讀取trace文件的權(quán)限,我們又如何拿到ANR發(fā)生時dump出來的trace文件呢?
ANR捕獲的基本原理
發(fā)生ANR的時候,系統(tǒng)的system_server進(jìn)程會發(fā)送SIGQUIT信號給一些進(jìn)程,包括發(fā)生ANR的進(jìn)程、占用系統(tǒng)資源較多的進(jìn)程,讓這些進(jìn)程進(jìn)行dump操作。
我們可以通過在應(yīng)用內(nèi)攔截這個信號,來判斷當(dāng)前進(jìn)程是否需要dump。但是收到信號并不表示當(dāng)前進(jìn)程一定發(fā)生了ANR,也可能是其他進(jìn)程發(fā)生了ANR,剛好我們的進(jìn)程運(yùn)行在后臺,且資源消耗比較多,system_server也發(fā)送信號讓我們進(jìn)程去做dump。所以光憑這個邏輯,無法判斷是否是當(dāng)前進(jìn)程發(fā)生的ANR,還需要加一些其他的判斷條件。接下來就讓我們從Matrix的源碼角度,看看業(yè)界通用的比較成熟的通用方案吧。
監(jiān)聽SIGQUIT信號
- 首先Matrix在
AnrDumper方法里,將sigquit設(shè)置成SIG_UNBLOCK。
因?yàn)锳ndroid默認(rèn)把SIGQUIT信號設(shè)置成BLOCKED,所以只會響應(yīng)sigwait,而不會進(jìn)入我們設(shè)置的handler中。所以需要通過pthread_sigmask將SIGQUIT信號設(shè)置成UNBLOCK,才能進(jìn)入我們的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);
}
- 然后通過
sigaction,將方法地址設(shè)置成我們的signalHandler。
bool SignalHandler::installHandlersLocked() {
// 構(gòu)造一個sigaction,將方法地址設(shè)置成我們的signalHandler
struct sigaction sa{};
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
// 設(shè)置一個新的sigaction
if (sigaction(TARGET_SIG, &sa, nullptr) == -1) {
return false;
}
sHandlerInstalled = true;
return true;
}
- 當(dāng)系統(tǒng)向進(jìn)程發(fā)送
SIGQUIT信號時,會進(jìn)入我們的signalHandler方法,可以在這里可以對信號進(jìn)行進(jìn)一步的處理。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();
}
- 在處理完之后,將
SIGQUIT重新發(fā)成系統(tǒng)的SignalCatcher處理。
static void sendSigToSignalCatcher() {
int tid = getSignalCatcherThreadId();
syscall(SYS_tgkill, getpid(), tid, SIGQUIT);
}
判斷是否真的發(fā)生ANR
收到SIGQUIT只能說明進(jìn)程要處理dump,并不能說明當(dāng)前應(yīng)用程序發(fā)生了ANR,如果直接上報ANR,會造成多報。所以還需要加更多的判斷條件,來判斷當(dāng)前進(jìn)程是否真正發(fā)生ANR。
- 當(dāng)前程序是否有
NOT_RESPONDING標(biāo)記
如果應(yīng)用發(fā)生了ANR,AMS會給進(jìn)程加一個NOT_RESPONDING的標(biāo)記,同時生成shortMsg和longMsg。如果有這個標(biāo)記,可以馬上判斷當(dāng)前進(jìn)程發(fā)生了一次ANR。
但是用NOT_RESPONDING標(biāo)記來判斷ANR,也有幾個缺陷:
- 生成比較慢,需要輪詢?nèi)ゲ?,如果用戶提前殺掉app,可能導(dǎo)致上報失敗
- 無法監(jiān)控后臺ANR,因?yàn)楹笈_ANR,除非了【開發(fā)者選項(xiàng)】中打開【顯示所有“應(yīng)用程序無響應(yīng)】,否則App不會進(jìn)入
NOT_RESPONDING狀態(tài)。
Matrix的做法是開啟一個子線程,去循環(huán)查是否有NOT_RESPONDING標(biāo)記,500ms查一次,一共查20s。
new Thread(new Runnable() {
@Override
public void run() {
checkErrorStateCycle(isSigQuit);
}
}, CHECK_ANR_STATE_THREAD_NAME).start();
循環(huán)檢查進(jìn)程的Error State。
private static void checkErrorStateCycle(boolean isSigQuit) {
int checkErrorStateCount = 0;
while (checkErrorStateCount < CHECK_ERROR_STATE_COUNT) {
try {
checkErrorStateCount++;
boolean myAnr = checkErrorState();
// 如果有NOT_RESPONDING標(biāo)記,馬上上報
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;
}
// 當(dāng)前進(jìn)程,且狀態(tài)是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;
}
- 主線程是否被block
通過反射,可以獲取主線程Looper中的消息頭mMessages。因?yàn)?code>MessageQueue中的消息是根據(jù)Message.when的時間升序排列的,所以反射獲取的消息頭就是第一個待處理的消息。
判斷主線程是否被block的方法:判斷當(dāng)前時間和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信號
另外,我們自己進(jìn)程也可以手動觸發(fā)一個SIGQUIT信號,來獲取trace文件里額外的信息。比如在發(fā)生卡頓的時候,也可以使用SIGQUIT來讓我們進(jìn)程dump一個當(dāng)前的堆棧。
不過這個方法比較重,因?yàn)閐ump堆棧的時候,需要暫停所有的線程,會影響程序的運(yùn)行效率。所以一般不會在線上輕易使用,可以在線下監(jiān)控的時候用。
static void nativePrintTrace() {
fromMyPrintTrace = true;
kill(getpid(), SIGQUIT);
}
總結(jié)
綜上所述,判斷當(dāng)前進(jìn)程是否發(fā)生ANR的業(yè)界通用做法如下:
- 通過攔截
SIGQUIT來判斷系統(tǒng)是否向進(jìn)程發(fā)送信號 - 收到
SIGQUIT后,判斷主線程是否block,如果主線程block,上報ANR - 循環(huán)獲取App
ErrorState,如果狀態(tài)變成NOT_RESPONDING,上報ANR
下一篇文章會講,如何獲取系統(tǒng)dump的trace文件,敬請期待。