Android ANR原理分析
侠客Ren 人气:1卡顿原理
主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR。 应用进程启动时候,Zygote会反射调用ActivityThread的main方法,启动loop循环。 ActivityThread(api29)
public static void main(String[] args) { Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
Looper的loop方法:
// 在线程中运行消息队列。一定要调用 public static void loop() { for (;;) { // 1、取消息 Message msg = queue.next(); // might block ... // This must be in a local variable, in case a UI event sets the logger // 2、消息处理前回调 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } ... // 3、消息开始处理 msg.target.dispatchMessage(msg); ... // 4、消息处理完回调 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } } }
loop中for循环存在,主线程可以长时间运行。在主线程执行任务,可以通过Handler post一个任务到消息队列去,loop循环拿到msg,交给msg的target(Handler)处理。
可能导致卡顿两个地方:
- 注释1 queue.next()
- 注释3 dispatchMessage耗时
MessageQueue.next 耗时代码(api29)
@UnsupportedAppUsage Message next() { for (;;) { // 1、nextPollTimeoutMillis不为0则阻塞 nativePollOnce(ptr, nextPollTimeoutMillis); // 2、先判断当前第一条消息是不是同步屏障消息, if (msg != null && msg.target == null) { // 3、遇到同步屏障消息,就跳过去取后面的异步消息来处理,同步消息相当于被设立了屏障 // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } // 4、正常消息处理,判断是否延时 if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // 5、如果没有取到异步消息,下次循环到注视1,nativePollOnce为-1,会一直阻塞 // No more messages. nextPollTimeoutMillis = -1; } } }
- MessageQueue是链表数据结构,判断MessageQueue头部(第一个消息)是不是同步屏障消息(给同步消息加一层屏障,让同步消息不被处理,只会处理异步消息);
- 如果遇到同步屏障消息,就会跳过MessageQueue中同步消息,只会处理里面的异步消息来处理。如果没有异步消息则到注释5,nextPollTimeoutMillis为-1,下次循环调用注释1的nativePollOnce就会阻塞;
- 如果looper能正常获取消息,不论异步/同步消息,处理流程一样,在注释4,判断是否延时,如果是,nextPollTimeoutMillis被赋值,下次调用注释1的nativePollOnce就会阻塞一段时间。如果不是delay消息,直接返回msg,给handler处理。
next方法不断从MessageQueue取消息,有消息就处理,没有消息就调用nativePollOnce阻塞,底层是Linux的epoll机制,Linux IO多路复用。
Linux IO多路复用方案有select、poll、epoll。其中epoll性能最优,支持并发量最大。
- select: 是操作系统提供的系统调用函数,可以把文件描述符的数组发给操作系统,操作系统去遍历,确定哪个描述符可以读写,告诉我们去处理。
- poll:和select主要区别,去掉了select只能监听1024个文件描述符的限制。
- epoll:针对select的三个可优化点进行改进。
1、内核中保持一份文件描述符集合,无需用户每次重新传入,只需要告诉内核修改部分。 2、内核不再通过轮询方式找到就绪的文件描述符,通过异步IO事件唤醒。 3、内核仅会将有IO的文件描述符返回给用户,用户无需遍历整个文件描述符集合。
同步屏障消息
Android App是无法直接调用同步消息屏障的,MessageQueue(api29)代码
@TestApi public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { ... }
系统高优先级的操作使用到同步屏障消息,例如:View绘制的时候ViewRootImpl的scheduleTraversals方法,插入同步屏障消息,绘制完成后移除同步屏障消息。ViewRootImpl api29
@UnsupportedAppUsage void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } }
为了保证View的绘制过程不被主线程其他任务影响,View在绘制之前会先往MessageQueue插入同步屏障消息,然后再注册Vsync信号监听,Choreographer$FrameDisplayEventReceiver监听接收vsync信号回调的。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { Message msg = Message.obtain(mHandler, this); // 1、发送异步消息 msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run() { // 2、doFrame优先执行 doFrame(mTimestampNanos, mFrame); } }
收到Vsync信号回调,注释1往主线程MessageQueue post一个异步消息,保证注释2的doFrame优先执行。
doFrame才是View真正开始绘制的地方,会调用ViewRootIml的doTraversal、performTraversals,而performTraversals里面会调用View的onMeasure、onLayout、onDraw。
虽然app无法发送同步屏障消息,但是使用异步消息是允许的。
异步消息 SDK中限制了App不能post异步消息到MessageQueue中,Message类
@UnsupportedAppUsage /*package*/ int flags;
谨慎使用异步消息,使用不当,可能出现主线程假死。
Handler#dispatchMessage
/** * Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
- Handler#post(Runnable r)
- 构造方法传CallBack
- Handler重写handlerMessage方法
应用卡顿,一般都是Handler处理消息太耗时导致的(方法本身、算法效率、cpu被抢占、内存不足、IPC超时等)
卡顿监控
卡顿监控方案一 Looper#loop
// 在线程中运行消息队列。一定要调用 public static void loop() { for (;;) { // 1、取消息 Message msg = queue.next(); // might block ... // This must be in a local variable, in case a UI event sets the logger // 2、消息处理前回调 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } ... // 3、消息开始处理 msg.target.dispatchMessage(msg); ... // 4、消息处理完回调 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } } }
注释2和4的logging.println是api提供接口,可监听Handler耗时,通过Looper.getMainLooper().setMessageLogging(printer),拿到消息前后的时间。监听到卡顿后,dispatchMessage早已调用结束,堆栈不包含卡顿代码。
定时获取主线程堆栈,时间为key,堆栈信息为value,保存map中,发生卡顿,取出卡顿时间内的堆栈可行。适合线下使用。
- logging.println存在字符串拼接,频繁调用,创建大量对象,内存抖动。
- 后台频繁获取主线程堆栈,对性能影响,获取主线程堆栈,暂停主线程的运行。
卡顿监控方案二
对于线上卡顿监控,需要字节码插桩技术。
通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计耗时。例如微信Matrix使用的卡顿监控方案。注意问题:
- 避免方法数暴增:分配独立ID作为参数
- 过滤简单函数:添加黑明单降低非必要函数统计
微信Matrix做大量优化,包体积增加1%~2%,帧率下降2帧以内,灰度包使用。
ANR原理
- Service Timeout:前台服务20s内未执行完成,后台服务是10s
- BroadcastQueue Timeout:前台广播10s内执行完成,后台60s
- ContentProvider Timeout:publish超时10s
- InputDispatching Timeout:输入事件分发超过5s,包括按键和触摸事件。
ActivityManagerService api29
// How long we allow a receiver to run before giving up on it. static final int BROADCAST_FG_TIMEOUT = 10*1000; static final int BROADCAST_BG_TIMEOUT = 60*1000;
ANR触发流程
埋炸弹
后台sevice调用:Context.startService--> AMS.startService--> ActiveService.startService--> ActiveService.realStartServiceLocked
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { // 1、发送delay消息(SERVICE_TIMEOUT_MSG) bumpServiceExecutingLocked(r, execInFg, "create"); try { // 2、通知AMS创建服务 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo), app.getReportedProcState()); } }
注释1内部调用scheduleServiceTimeoutLocked
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; // 发送delay消息,前台服务是20s,后台服务是200s mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); }
注释2通知AMS启动服务前,注释1发送handler延迟消息,20s内(前台服务)没有处理完,则ActiveServices#serviceTimeout被调用。
拆炸弹
启动一个Service,先要经过AMS管理,然后AMS通知应用执行Service的生命周期,ActivityThread的handlerCreateService方法被调用。
@UnsupportedAppUsage private void handleCreateService(CreateServiceData data) { try { Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); // 1、service onCreate调用 service.onCreate(); mServices.put(data.token, service); try { // 2、拆炸弹 ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }
注释1,Service的onCreate方法被调用 注释2,调用AMS的serviceDoneExecuting方法,最终会调用ActiveServices.serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) { //移除delay消息 mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); }
onCreate调用后,就会移除delay消息,炸弹拆除。
引爆炸弹,假设Service的onCreate执行超过10s,那么炸弹就会引爆,也就是ActiveServices#serviceTimeout方法会被调用。api29
void serviceTimeout(ProcessRecord proc) { if (anrMessage != null) { proc.appNotResponding(null, null, null, null, false, anrMessage); } }
所有ANR,最终带调用ProcessRecord的appNotResponding方法。api29
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, String annotation) { // 1、写入event log // Log the ANR to the event log. EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags, annotation); // 2、收集需要的log、anr、cpu等,放到StringBuilder中。 // Log the ANR to the main log. StringBuilder info = new StringBuilder(); info.setLength(0); info.append("ANR in ").append(processName); if (activityShortComponentName != null) { info.append(" (").append(activityShortComponentName).append(")"); } info.append("\n"); info.append("PID: ").append(pid).append("\n"); if (annotation != null) { info.append("Reason: ").append(annotation).append("\n"); } if (parentShortComponentName != null && parentShortComponentName.equals(activityShortComponentName)) { info.append("Parent: ").append(parentShortComponentName).append("\n"); } ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); // 3、dump堆栈信息,包括java堆栈和native堆栈,保存到文件中 // For background ANRs, don't pass the ProcessCpuTracker to // avoid spending 1/2 second collecting stats to rank lastPids. File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids, nativePids); String cpuInfo = null; // 4、输出ANR日志 Slog.e(TAG, info.toString()); if (tracesFile == null) { // 5、没有抓到tracesFile,发一个SIGNAL_QUIT信号 // There is no trace file, so dump (only) the alleged culprit's threads to the log Process.sendSignal(pid, Process.SIGNAL_QUIT); } // 6、输出到drapbox mService.addErrorToDropBox("anr", this, processName, activityShortComponentName, parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null); synchronized (mService) { // 7、后台ANR,直接杀进程 if (isSilentAnr() && !isDebugging()) { kill("bg anr", true); return; } // 8、错误报告 // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLocked(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); // 9、弹出ANR dialog,会调用handleShowAnrUi方法 Message msg = Message.obtain(); msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem); mService.mUiHandler.sendMessage(msg); } }
- 写入event log
- 写入main log
- 生成tracesFile
- 输出ANR logcat(控制台可以看到)
- 如果没有获取tracesFile,会发SIGNAL_QUIT信号,触发收集线程堆栈信息流程,写入traceFile
- 输出到drapbox
- 后台ANR,直接杀进程
- 错误报告
- 弹出ANR dialog 调用AppErrors#handleShowAnrUi方法。
ANR触发流程,埋炸弹--》拆炸弹的过程 启动Service,onCreate方法调用之前会使用Handler延时10s的消息,Service的onCreate方法执行完,会把延迟消息移除掉。 假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,触发ANR,收集cpu、堆栈消息,弹ANR dialog
抓取系统的data/anr/trace.txt文件,但是高版本系统需要root权限才能读取这个目录。
ANRWatchDog github.com/SalomonBrys…
自动检测ANR开源库
加载全部内容