最近看了几篇关于ANR机制的文章,本篇文章来源码跟踪一下ANR消息的产生、消费与dump的全过程,彻底理解ANR出现的原因,并且讨论如何监控ANR的产生。
ANR机制原理 ANR(Application Not Responding)机制是Android系统中一种监控用户App是否长时间未响应的一种机制,类似于Windows系统中的“程序未响应”。
既然是监控耗时,类似于我们监控一个方法的执行时间,一定会在方法执行前做一些记录,然后与方法执行结束后进行对比,ANR中也是类似的。
触发场景 那么哪些场景下会导致ANR呢?主要有以下几种情况:
KEY_DISPATCHING_TIMEOUT :输入事件分发超过5秒,包括触摸事件与按键事件。
BROADCAST_FG_TIMEOUT :前台广播接收者在10秒内未执行完成。
BROADCAST_BG_TIMEOUT :后台广播接收者在60秒内未执行完成。
SERVICE_TIMEOUT :前台服务在20秒内未执行完某一生命周期。
SERVICE_BACKGROUND_TIMEOUT :后台服务在200秒内未执行完某一生命周期。
CONTENT_PROVIDER_PUBLISH_TIMEOUT :内容提供者在publish时超过10秒。
以上时间是基于Android 10.0源码的,具体时间定义在ActivityManagerService.java
、ActivityTaskManagerService
和ActiveServices.java
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [ActivityManagerService.java]static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10 *1000 ;static final int BROADCAST_FG_TIMEOUT = 10 *1000 ;static final int BROADCAST_BG_TIMEOUT = 60 *1000 ; [ActivityTaskManagerService.java]public static final int KEY_DISPATCHING_TIMEOUT_MS = 5 * 1000 ; [ActiveServices.java]static final int SERVICE_TIMEOUT = 20 *1000 ;static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10 ;
下面以后台服务为例,分析ANR机制的触发流程。
触发流程 我们从服务的启动过程来寻找ANR相关代码,由于代码比较多,并且启动Service的过程与Activity的启动很类似,而前面已经写了篇文章来详细讲解Activity的启动过程了,所以这里选择跳过部分代码。
ActiveServices#realStartServiceLocked 直接来到ActiveServices中的realStartServiceLocked
方法中:
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
1 2 3 4 5 6 7 8 9 10 11 12 private final void realStartServiceLocked (ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ··· final boolean newService = app.services.add(r); bumpServiceExecutingLocked(r, execInFg, "create" ); mAm.updateLruProcessLocked(app, false , null ); updateServiceForegroundLocked(r.app, false ); mAm.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); ··· }
此方法首先调用了bumpServiceExecutingLocked
,这里有我们寻找的代码,跟进。
ActiveServices#bumpServiceExecutingLocked 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private final void bumpServiceExecutingLocked (ServiceRecord r, boolean fg, String why) { ··· long now = SystemClock.uptimeMillis(); if (r.executeNesting == 0 ) { r.executeFg = fg; ServiceState stracker = r.getTracker(); if (stracker != null ) { stracker.setExecuting(true , mAm.mProcessStats.getMemFactorLocked(), now); } if (r.app != null ) { r.app.executingServices.add(r); r.app.execServicesFg |= fg; if (timeoutNeeded && r.app.executingServices.size() == 1 ) { scheduleServiceTimeoutLocked(r.app); } } } else if (r.app != null && fg && !r.app.execServicesFg) { r.app.execServicesFg = true ; if (timeoutNeeded) { scheduleServiceTimeoutLocked(r.app); } } ··· }
这里无论是哪个if分支都调用了scheduleServiceTimeoutLocked
方法,看名字猜测其余Service的超时有关,继续跟进。
ActiveServices#scheduleServiceTimeoutLocked 1 2 3 4 5 6 7 8 9 10 11 void scheduleServiceTimeoutLocked (ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null ) { return ; } Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); }
可以看到这里通过ActivityManager 的Handler创建一个消息,指定类型为ActivityManagerService.SERVICE_TIMEOUT_MSG ,并且根据服务是否前后台来设置了不同的Delay值。
接下来我们可以直接去Handler的handleMessage方法中寻找对应的消息是如何处理的。
MainHandler#handleMessage 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public void handleMessage (Message msg) { switch (msg.what) { case GC_BACKGROUND_PROCESSES_MSG: { synchronized (ActivityManagerService.this ) { performAppGcsIfAppropriateLocked(); } } break ; case SERVICE_TIMEOUT_MSG: { mServices.serviceTimeout((ProcessRecord)msg.obj); } break ; case SERVICE_FOREGROUND_TIMEOUT_MSG: { mServices.serviceForegroundTimeout((ServiceRecord)msg.obj); } break ; ··· }
mServices为ActiveServices对象,可以看到这里又交回给ActiveServices来处理。
ActiveServices#serviceTimeout 1 2 3 4 5 6 7 8 9 10 11 void serviceTimeout (ProcessRecord proc) { String anrMessage = null ; ··· if (anrMessage != null ) { proc.appNotResponding(null , null , null , null , false , anrMessage); } }
在这里调用了ProcessRecord的appNotResponding
方法来消费ANR事件,执行后续的弹框、dump堆栈等操作。
ProcessRecord#appNotResponding appNotResponding
方法的代码较多,这里我们分为多个部分来进行讲解。
先来看第一部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void appNotResponding (String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, String annotation) { ··· synchronized (mService) { if (mService.mAtmInternal.isShuttingDown()) { Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation); return ; } else if (isNotResponding()) { Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation); return ; } else if (isCrashing()) { Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation); return ; } else if (killedByAm) { Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation); return ; } else if (killed) { Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation); return ; } setNotResponding(true ); if (!isSilentAnr()) { ··· } } }
先是一长串if else,给出了几种比较极端的情况,会直接return,而不会产生一个ANR,这些情况包括:
系统正在重启
进程处于正在关闭的状态
进程处于正在crash的状态
进程处于被kill的状态
相同进程已经处在ANR的流程中
另外很重要的一个逻辑就是判断当前ANR是否是一个SilentAnr,所谓“静默ANR”,其实就是后台ANR,后台ANR跟前台ANR会有不同的表现:前台ANR会弹无响应的Dialog,后台ANR会直接杀死进程 。
前后台ANR的判断的原则是:如果发生ANR的进程对用户来说是有感知的,就会被认为是前台ANR,否则是后台ANR。 另外,如果在开发者选项中勾选了“显示后台ANR”,那么全部ANR都会被认为是前台ANR。
我们继续分析这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 if (!isSilentAnr()) { int parentPid = pid; if (parentProcess != null && parentProcess.getPid() > 0 ) { parentPid = parentProcess.getPid(); } if (parentPid != pid) firstPids.add(parentPid); if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID); for (int i = getLruProcessList().size() - 1 ; i >= 0 ; i--) { ProcessRecord r = getLruProcessList().get(i); if (r != null && r.thread != null ) { int myPid = r.pid; if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) { if (r.isPersistent()) { firstPids.add(myPid); if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r); } else if (r.treatLikeActivity) { firstPids.add(myPid); if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r); } else { lastPids.put(myPid, Boolean.TRUE); if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r); } } } } } ··· String[] nativeProcs = null ;if (isSilentAnr()) { for (int i = 0 ; i < NATIVE_STACKS_OF_INTEREST.length; i++) { if (NATIVE_STACKS_OF_INTEREST[i].equals(processName)) { nativeProcs = new String[] { processName }; break ; } } } else { nativeProcs = NATIVE_STACKS_OF_INTEREST; }int [] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs); ArrayList<Integer> nativePids = null ;if (pids != null ) { nativePids = new ArrayList<>(pids.length); for (int i : pids) { nativePids.add(i); } }
发生ANR后,为了能让开发者知道ANR的原因,方便定位问题,会dump很多信息到ANR Trace文件里, 上面的逻辑就是选择需要dump的进程。
ANR Trace文件是包含许多进程 的Trace信息的,因为产生ANR的原因有可能是其他的进程抢占了太多资源,或者IPC到其他进程(尤其是系统进程)的时候卡住导致的。
需要被dump的进程被分为了firstPids、nativePids以及extraPids三类:
firstPIds :firstPids是需要首先dump的重要进程,发生ANR的进程无论如何是一定要被dump的,也是首先被dump的,所以第一个被加到firstPids中。如果是SilentAnr(即后台ANR),不用再加入任何其他的进程。如果不是,需要进一步添加其他的进程:
如果发生ANR的进程不是system_server 进程的话,需要添加system_server进程
接下来轮询AMS维护的一个LRU的进程List,如果最近访问的进程包含了persistent的进程,或者带有BIND_TREAT_LIKE_ACTVITY
标签的进程,都添加到firstPids中
LRU进程List中的其他进程,都会首先添加到lastPids中,然后lastPids会进一步被选出最近CPU使用率高的进程,进一步组成extraPids
nativePids最为简单,是一些固定的native的系统进程,定义在WatchDog.java 中
拿到需要dump的所有进程的pid后,AMS开始按照firstPids、nativePids、extraPids的顺序dump这些进程的堆栈:
1 2 3 4 5 File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids, nativePids);
这个方法是我们需要重点关注的,接下来跟进到AMS里。
ActivityManagerService#dumpStackTraces
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static File dumpStackTraces (ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, ArrayList<Integer> nativePids) { ArrayList<Integer> extraPids = null ; ··· final File tracesDir = new File(ANR_TRACE_DIR); maybePruneOldTraces(tracesDir); File tracesFile = createAnrDumpFile(tracesDir); if (tracesFile == null ) { return null ; } dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids); return tracesFile; }
这里创建了日志文件,可以看到目录为/data/anr
,然后继续调用了重载方法dumpStackTraces
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static void dumpStackTraces (String tracesFile, ArrayList<Integer> firstPids, ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) { long remainingTime = 20 * 1000 ; if (firstPids != null ) { int num = firstPids.size(); for (int i = 0 ; i < num; i++) { final long timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile, remainingTime); remainingTime -= timeTaken; if (remainingTime <= 0 ) { Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + firstPids.get(i) + "); deadline exceeded." ); return ; } if (DEBUG_ANR) { Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms" ); } } } ··· }
首先关注到remainingTime
变量,这个变量规定了我们dump所有进程的最长时间,因为dump进程所有线程的堆栈本身就是一个重操作,何况是要dump许多进程,所以规定了发生ANR之后,dump全部进程的总时间不能超过20秒 。如果超过了,马上返回,确保ANR弹窗可以及时的弹出(或者被kill掉)。
继续跟进到dumpJavaTracesTombstoned
方法:
1 2 3 4 5 6 7 8 9 private static long dumpJavaTracesTombstoned (int pid, String fileName, long timeoutMs) { final long timeStart = SystemClock.elapsedRealtime(); boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName, (int ) (timeoutMs / 1000 )); ··· return SystemClock.elapsedRealtime() - timeStart; }
方法中使用dumpJavaBacktraceToFileTimeout
方法来完成Java堆栈的dump操作。
关于具体的dump操作需要追踪到native层,一路跟踪到debuggerd_client.cpp文件中的debuggerd_trigger_dump
方法。
debuggerd_trigger_dump
system/core/debuggerd/client/debuggerd_client.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bool debuggerd_trigger_dump (pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) { pid_t pid = tid; ··· const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER; sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0 }; if (sigqueue(pid, signal, val) != 0 ) { log_error(output_fd, errno, "failed to send signal to pid %d" , pid); return false ; } ··· LOG(INFO) << TAG "done dumping process " << pid; return true ; }
这里会通过sigqueue
向需要dump堆栈的进程发送SIGQUIT信号,也就是signal 3信号,而发生ANR的进程,也就是我们的APP进程同样也会通过这个信号来开启进程的堆栈dump过程。
每一个应用进程都会有一个SignalCatcher线程,专门处理SIGQUIT,源码位于art/runtime/signal_catcher.cc
文件中。
SignalCatcher#Run
art/runtime/signal_catcher.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void * SignalCatcher::Run (void * arg) { SignalCatcher* signal_catcher = reinterpret_cast <SignalCatcher*>(arg); ··· Thread* self = Thread::Current(); ··· SignalSet signals; signals.Add(SIGQUIT); signals.Add(SIGUSR1); while (true ) { int signal_number = signal_catcher->WaitForSignal(self, signals); if (signal_catcher->ShouldHalt()) { runtime->DetachCurrentThread(); return nullptr ; } switch (signal_number) { case SIGQUIT: signal_catcher->HandleSigQuit(); break ; case SIGUSR1: signal_catcher->HandleSigUsr1(); break ; default : LOG(ERROR) << "Unexpected signal %d" << signal_number; break ; } } }
SignalCatcher中开启了一个死循环,其中调用了WaitForSignal
方法来阻塞地等待信号到达,主要关注两个信号:SIGQUIT和SIGUSR1。当SignalCatcher接收到SIGQUIT信号时,就会调用HandleSigQuit
方法。
SignalCatcher#HandleSigQuit 1 2 3 4 5 6 7 8 9 10 void SignalCatcher::HandleSigQuit () { ··· runtime->DumpForSigQuit(os); ··· Output(os.str()); }
调用了Runtime中的DumpForSigQuit方法。
Runtime#DumpForSigQuit
art/runtime/runtime.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void Runtime::DumpForSigQuit (std ::ostream& os) { GetClassLinker()->DumpForSigQuit(os); GetInternTable()->DumpForSigQuit(os); GetJavaVM()->DumpForSigQuit(os); GetHeap()->DumpForSigQuit(os); oat_file_manager_->DumpForSigQuit(os); if (GetJit() != nullptr ) { GetJit()->DumpForSigQuit(os); } else { os << "Running non JIT\n" ; } DumpDeoptimizations(os); TrackedAllocators::Dump(os); GetMetrics()->DumpForSigQuit(os); os << "\n" ; thread_list_->DumpForSigQuit(os); BaseMutex::DumpAll(os); { ScopedObjectAccess soa (Thread::Current()) ; callbacks_->SigQuit(); } }
可以看到这里才真正完成了我们应用App所在进程的dump工作。
因此我们可以得出一条重要结论:发生ANR的进程在接收到系统进程发送过来的SIGQUIT
信号时,会开启自身的堆栈dump过程 。
这一结论对我们监控ANR有重要意义,后文会详细分析如何进行监控。
重新回到appNotResponding
方法中来看后续操作。
此时我们已经完成了所有相关进程的dump过程,并且拿到了保存的文件File对象,后续还需要输出log、弹出ANR弹窗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void appNotResponding (String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, String annotation) { ··· Slog.e(TAG, info.toString()); if (tracesFile == null ) { Process.sendSignal(pid, Process.SIGNAL_QUIT); } StatsLog.write(StatsLog.ANR_OCCURRED, uid, processName, activityShortComponentName == null ? "unknown" : activityShortComponentName, annotation, (this .info != null ) ? (this .info.isInstantApp() ? StatsLog.ANROCCURRED__IS_INSTANT_APP__TRUE : StatsLog.ANROCCURRED__IS_INSTANT_APP__FALSE) : StatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE, isInterestingToUserLocked() ? StatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND : StatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND, getProcessClassEnum(), (this .info != null ) ? this .info.packageName : "" ); final ProcessRecord parentPr = parentProcess != null ? (ProcessRecord) parentProcess.mOwner : null ; mService.addErrorToDropBox("anr" , this , processName, activityShortComponentName, parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null ); if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr" , true ), () -> { synchronized (mService) { mService.mServices.scheduleServiceTimeoutLocked(this ); } })) { return ; } synchronized (mService) { if (mService.mBatteryStatsService != null ) { mService.mBatteryStatsService.noteProcessAnr(processName, uid); } if (isSilentAnr() && !isDebugging()) { kill("bg anr" , true ); return ; } makeAppNotRespondingLocked(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR" , info.toString()); if (mService.mUiHandler != null ) { Message msg = Message.obtain(); msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = new AppNotRespondingDialog.Data(this , aInfo, aboveSystem); mService.mUiHandler.sendMessage(msg); } } }
可以看到在完成dump之后主要做了以下操作:
输出日志到logcat
如果没有获取到tracesFile,再次给进程发送一个SIGNAL_QUIT
信号
输出日志到DropBox
如果是后台ANR,直接杀进程
发送消息弹出ANR Dialog
到这里整个触发流程就结束了,下面来看看对于没有达到触发ANR边界的消息,是如何移除的。
消息移除 我们可以通过全局搜索的方式来寻找ANR消息类型的remove是在何时调用的:
可以看到是在ActiveServices中的serviceDoneExecutingLocked
进行消息的移除。
继续利用全局搜索我们可以知道最早的调用时机是在Service的Create 生命周期中,具体是在ActivityThread的handleCreateService
方法中,接着来看这个方法。
ActivityThread#handleCreateService
frameworks/base/core/java/android/app/ActivityThread.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void handleCreateService (CreateServiceData data) { ··· try { ··· try { ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0 , 0 ); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { throw new RuntimeException( "Unable to create service " + data.info.name + ": " + e.toString(), e); } } }
在handleCreateService
中通过IPC的方式来通知AMS移除对应的消息。
另外在Service的其他生命周期中也有调用serviceDoneExecuting
方法,这里不再赘述。
ANR日志的分析 上节我们知道ANR产生时会进行堆栈dump,并把日志文件输出到/data/anr/traces.txt文件中,我们可以尝试手写一个ANR来分析其日志。
模拟死锁导致ANR 利用synchronized可以很容易写出死锁代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private val lock1 = Object()private val lock2 = Object()private fun tryDeadLock () { thread { synchronized(lock1) { try { Thread.sleep(100L ) } catch (e: Exception) { e.printStackTrace() } synchronized(lock2) { } } } synchronized(lock2) { try { Thread.sleep(100L ) } catch (e: Exception) { e.printStackTrace() } synchronized(lock1) { } } }
然后提供一个按钮来触发ANR,界面如下所示:
点击Button会调用tryDeadLock
方法,为了触发ANR效果,我们需要再点击一下屏幕发送一些触摸事件,过一会就会看到ANR 弹窗了。
分析日志 产生ANR之后可以使用Device File Explorer看到/data/anr目录下出现了一个日志文件了,可以直接打开也可以使用adb pull命令从手机传到电脑中,需要注意的是如果使用的是Google Play版的镜像创建的模拟器是没有权限访问这个目录的,文件如下:
由于参与dump的进程和线程很多,因此这个文件里面的日志可能会非常多,善用搜索可以找到关键信息。
首先搜索main,来看主线程:
可以看到第一行就清晰的指出了当前主线程状态为”blocked“,也就是阻塞状态。
下面的堆栈信息告诉我们,MainActivity的tryDeadLock
方法中主线程在等待一个lock<0x0475d18b>释放,这个lock被id为2的线程持有,并且主线程持有一个Object类型的锁,地址为<0x0dc0eb5a>。
那么我们可以搜索tid=2的线程查看其堆栈情况:
可以看到这个tid为2的线程同样处于阻塞状态,并且日志告诉我们它也在等待一个锁<0x0dc0eb5a>释放,而这个锁则被id为1的线程持有,同时它持有锁<0x0475d18b>。
id为1的线程实际上就是我们的主线程main。到这里我们发现线程2与主线程互相持有对方需要的锁,并且同时又在请求对方线程持有的锁,造成了循环等待,产生死锁。通过ANR日志,可以很清楚分析出这个ANR是死锁导致的,并且有具体堆栈信息。
上面只是举例一种非常简单的ANR情况,实际项目中可能会有很多意料之外的ANR,而且可能会涉及到多个进程例如system_server,那么对于线上项目,要如何拿到这个ANR日志文件呢?下面就来讨论一下微信的ANR监控方案。
微信ANR监控方案 微信Android客户端的ANR监控方案
参考
微信Android客户端的ANR监控方案
卡顿、ANR、死锁,线上如何监控?
理解Android ANR的触发原理