Android ANR原理分析

作者:侠客Ren 时间:2023-02-01 13:11:58 

目录
  • 卡顿原理

  • 卡顿监控

  • ANR原理

卡顿原理

主线程有耗时操作会导致卡顿,卡顿超过阀值,触发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;
               }
       }
   }
  1. MessageQueue是链表数据结构,判断MessageQueue头部(第一个消息)是不是同步屏障消息(给同步消息加一层屏障,让同步消息不被处理,只会处理异步消息);

  2. 如果遇到同步屏障消息,就会跳过MessageQueue中同步消息,只会处理里面的异步消息来处理。如果没有异步消息则到注释5,nextPollTimeoutMillis为-1,下次循环调用注释1的nativePollOnce就会阻塞;

  3. 如果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);
       }
   }
  1. Handler#post(Runnable r)

  2. 构造方法传CallBack

  3. 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中,发生卡顿,取出卡顿时间内的堆栈可行。适合线下使用。

  1. logging.println存在字符串拼接,频繁调用,创建大量对象,内存抖动。

  2. 后台频繁获取主线程堆栈,对性能影响,获取主线程堆栈,暂停主线程的运行。

卡顿监控方案二

对于线上卡顿监控,需要字节码插桩技术。

通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计耗时。例如微信Matrix使用的卡顿监控方案。注意问题:

  1. 避免方法数暴增:分配独立ID作为参数

  2. 过滤简单函数:添加黑明单降低非必要函数统计

微信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);
       }
 }
  1. 写入event log

  2. 写入main log

  3. 生成tracesFile

  4. 输出ANR logcat(控制台可以看到)

  5. 如果没有获取tracesFile,会发SIGNAL_QUIT信号,触发收集线程堆栈信息流程,写入traceFile

  6. 输出到drapbox

  7. 后台ANR,直接杀进程

  8. 错误报告

  9. 弹出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开源库

来源:https://juejin.cn/post/6976554853661310983

标签:Android,ANR
0
投稿

猜你喜欢

  • java 学习笔记(入门篇)_java程序helloWorld

    2023-02-28 02:53:44
  • Spring Cache框架应用介绍

    2023-06-15 22:32:59
  • Java简单工厂模式定义与用法实例分析

    2023-10-12 10:58:38
  • Unity3D实现飞机大战游戏(2)

    2021-11-16 10:41:38
  • SpringBoot自定义starter启动器的实现思路

    2023-10-09 00:55:15
  • Android组合控件自定义标题栏

    2021-11-04 01:12:36
  • C#判断页面中的多个文本框输入值是否有重复的实现方法

    2022-10-30 19:41:56
  • 浅析Java中comparator接口与Comparable接口的区别

    2023-11-01 20:31:14
  • Android仿京东搜索框渐变效果

    2022-09-08 19:57:05
  • 详解Java动态字节码技术

    2022-06-20 03:20:20
  • C#拼接SQL语句 用ROW_NUMBER实现的高效分页排序

    2023-12-27 04:47:01
  • springMVC引入Validation的具体步骤详解

    2021-12-19 18:30:19
  • 十种JAVA排序算法实例

    2022-11-11 00:19:54
  • C语言预处理预编译命令及宏定义详解

    2023-06-18 16:28:06
  • JavaWeb建立简单三层项目步骤图解

    2023-03-08 16:51:02
  • Java基于分治算法实现的棋盘覆盖问题示例

    2021-07-17 14:05:16
  • 解决spring boot启动扫描不到自定义注解的问题

    2023-10-29 14:31:48
  • startJVM错误Unable to load native library: libjvm.so解决方法

    2023-03-24 08:54:29
  • C#文件路径操作详细总结

    2021-06-06 04:07:41
  • java实现多线程的两种方式继承Thread类和实现Runnable接口的方法

    2022-07-24 04:27:18
  • asp之家 软件编程 m.aspxhome.com