背景问题

1. 什么是ANR?

ANR是Android体系中的一种过错状况,全称为Application Not Responding,中文翻译为“运用无呼应”。当Android体系检测到运用程序在一段时刻内未能呼运用户输入或无法履行首要的UI线程操作时,就会触发ANR过错。ANR是一种体系保护机制,旨在保证运用的呼应性,防止用户在运用运用时遇到卡顿或无呼应的状况。

2. ANR对用户体会的影响

ANR问题会直接影响用户体会,以下是一些具体影响:

  • 呼应速度: 用户希望运用在点击屏幕或履行操作时能够敏捷呼应,ANR会导致运用无法及时呼运用户的输入,运用户感到操作迟缓。
  • 用户流失: 频繁的ANR过错或许导致用户流失,因为用户或许会以为运用质量较低,切换到其他更流畅的运用。
  • 用户满意度: ANR直接与用户对运用的满意度相关。用户更倾向于运用那些反响敏捷、没有卡顿的运用,而ANR问题会降低用户对运用的满意度。

3. 产生ANR的原因

  • 主线程堵塞: 当运用在主线程履行耗时操作时,例如网络请求或杂乱计算,会导致主线程无法及时呼运用户输入,触发ANR。
  • 死锁: 多线程编程中,死锁或许产生,当一个线程等候另一个线程开释锁时,运用就会无法持续履行,导致ANR。
  • 非法耗时操作: Android规则在主线程中不答应履行耗时操作,违反这一规则会触发ANR。
  • BroadcastReceiver超时: 当BroadcastReceiver的onReceive()办法履行时刻过长,也会导致ANR。

问题剖析

ANR的分类

1. InputDispatching Timeout ANR

触发原因:

当运用在规则时刻内无法处理用户输入事情,体系会以为运用无呼应,触发InputDispatching Timeout ANR。这通常与主线程堵塞或繁忙有关。

源码剖析:

javaCopy code
// 坐落 InputDispatcher.java
void monitorInputDispatchingLocked(long dispatchingTimeoutNanos) {
    // 监控输入事情的分发
    long currentTimeNanos = System.nanoTime();
    long timeSinceDispatchStartedNanos = currentTimeNanos - mFirstInputEventTimeNanos;
    // 检查是否超时
    if (timeSinceDispatchStartedNanos > dispatchingTimeoutNanos) {
        // 触发 ANR
        handleANR();
    }
}

在上述代码片段中,monitorInputDispatchingLocked办法监控输入事情的分发状况,假如处理时刻超越规则的时刻,就会触发ANR。

2. BroadcastReceiver Timeout ANR

触发原因:

当BroadcastReceiver的onReceive()办法履行时刻过长,或者在该办法中履行了需求较长时刻完结的使命,体系会以为运用无呼应,触发ANR。

源码剖析:

javaCopy code
// 坐落 ActivityManagerService.java
boolean broadcastTimeoutLocked(long currentTime) {
    // 监控播送接收器的履行时刻
    long elapsedTime = currentTime - mLastBroadcastTime;
    // 检查是否超时
    if (elapsedTime > mConstants.BROADCAST_FG_MSG_TIMEOUT) {
        // 触发 ANR
        return handleANR();
    }
    return false;
}

上述代码片段中,broadcastTimeoutLocked办法监控播送接收器的履行时刻,假如履行时刻超越规则的时刻,就会触发ANR。

3. Service Timeout ANR

触发原因:

当Service履行的使命耗时较长,或者在主线程履行了堵塞操作,体系会以为运用无呼应,触发ANR。

源码剖析:

javaCopy code
// 坐落 ActivityManagerService.java
boolean serviceTimeoutLocked(long now) {
    // 监控服务的履行时刻
    long maxTime = mConstants.SERVICE_TIMEOUT > mConstants.SERVICE_BACKGROUND_TIMEOUT
            ? mConstants.SERVICE_TIMEOUT : mConstants.SERVICE_BACKGROUND_TIMEOUT;
    long executeTime = now - service.mExecuteNesting * maxTime;
    // 检查是否超时
    if (executeTime > maxTime) {
        // 触发 ANR
        return handleANR();
    }
    return false;
}

在上述代码片段中,serviceTimeoutLocked办法监控服务的履行时刻,假如履行时刻超越规则的时刻,就会触发ANR。

4. Activity Timeout ANR

触发原因:

当Activity的主线程在规则时刻内无法呼运用户输入或履行UI操作,体系会以为运用无呼应,触发Activity Timeout ANR。

源码剖析:

javaCopy code
// 坐落 ActivityManagerService.java
boolean activityTimeoutLocked(ProcessRecord proc, HistoryRecord r, long now, boolean aboveSystem) {
    // 监控Activity的履行时刻
    long maxTime = aboveSystem ? mConstants.ACTIVITY_BG_START_TIMEOUT
            : mConstants.ACTIVITY_BG_START_TIMEOUT;
    long executeTime = now - r.startUptime;
    // 检查是否超时
    if (executeTime > maxTime) {
        // 触发 ANR
        return handleANR();
    }
    return false;
}

在上述代码片段中,activityTimeoutLocked办法监控Activity的履行时刻,假如履行时刻超越规则的时刻,就会触发ANR。

ANR的监控计划

微信Android客户端的ANR监控计划 (qq.com)

1. 监控的根底:SIGQUIT信号

Android体系供给了SIGQUIT信号来协助监控ANR事情。本文首先介绍了两种监控信号的办法:一是经过SignalCatcher线程运用sigwait办法进行同步、堵塞地监听;二是运用sigaction办法注册signal handler进行异步监听。对比两者的完结,文章指出了在有多个线程一起监听同一个信号时或许出现的问题。

2. 防止误报

监控到SIGQUIT信号并不等于监控到了真正的ANR。文章具体讨论了两种误报状况:其他进程的ANR和非ANR发送SIGQUIT信号。经过符号进程的NOT_RESPONDING状况,并结合ActivityManager的ProcessErrorStateInfo,文章提出了防止误报的解决计划,保证只有真实的ANR事情被捕获。

3. 防止漏报

漏报是指尽管产生了ANR,但监控机制没有正确识别的状况。本文剖析了两种或许的漏报状况:后台ANR和闪退ANR。经过轮询检查进程状况和主线程是否卡顿,文章供给了快速识别ANR的办法,保证监控不会错失任何潜在的ANR事情。

4. 获取ANR Trace

为了更好地定位问题,文章介绍了获取ANR Trace的办法。经过Hook的方法阻拦体系dump的ANR Trace内容,开发人员能够取得包含线程状况、锁和仓库等具体信息,有助于更全面地剖析问题。

5. API兼容性

考虑到Android体系版本的差异,本文强调了在不同API级别上的兼容性。经过选择不同的Hook点和处理方法,保证监控计划在各种Android版本上都能够平稳运转。

常见的ANR原因剖析

  • 主线程耗时操作引发ANR

    • 主线程堵塞:主线程在履行某些同步操作时被堵塞,例如I/O操作、数据库查询等。这或许导致ANR,影响用户体会。
    • 主线程挂起:主线程被挂起,通常因为死锁、死循环或其他线程同步问题引起。这种状况下,主线程无法持续履行,导致ANR。
  • CPU资源争夺导致ANR

    • 其他进程CPU占比过高:假如其他进程在某一时刻点占用了大量CPU资源,或许导致当时运用在该时刻段内无法取得满足的CPU时刻片,然后引发ANR。
    • 体系CPU占比过高:体系在某一刻的CPU占比过高,或许导致所有运用无法正常运转。这种状况也会引发ANR。
  • 主线程卡在Binder通讯的对端

    • 运用binderinfo检查对端信息:inder通讯是Android体系中组件之间进行跨进程通讯的根底。假如主线程卡在Binder通讯的对端,能够经过binderinfo检查具体信息,找出具体原因。
  • 体系或运用内存严重

    • 体系或运用内存严重时,或许触发体系的lowmemorykiller操作,导致运用进程被杀死,引发ANR。
  • 运用频繁Crash

    • 运用本身的Crash也是ANR的潜在原因。频繁的Crash或许导致前台运用出现ANR的现象,影呼运用的稳定性。
  • 运用内存走漏

    • 运用中存在内存走漏时,长时刻运转后内存占用会逐步增加,终究导致内存缺乏,触发ANR。
  • 体系原因导致ANR

    • 体系在特别状况下,如冻结或温度过高,或许导致运用ANR。
    • 多媒体操作,如音视频处理、编解码等,或许在某些状况下引发ANR。
    • I/O 操作被堵塞或许导致ANR,例如读写文件或网络操作。
    • 底层服务的Native Crash或许导致整个运用无法正常运转,然后引发ANR。
    • Watchdog 是Android体系中的一个守护线程,用于监控运用是否呼应,超时未呼应或许触发ANR。
    • 特定芯片或硬件才能问题或许引发ANR。
    • 某些状况下或许出现内存黑洞,导致ANR。

解决计划

运用异步使命和线程池

异步使命

将耗时操作放入异步使命中,以防止在主线程上履行导致ANR。Android供给了AsyncTask类,可方便地履行后台操作并在主线程更新UI。

// 示例:运用异步使命履行耗时操作
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... params) {
        // 履行耗时操作
        return null;
    }
    @Override
    protected void onPostExecute(Void result) {
        // 更新UI或履行其他操作
    }
}
// 发动异步使命
new MyAsyncTask().execute();

线程池

合理运用线程池办理线程,以充分利用体系资源。经过线程池能够控制并发线程的数量,防止过多线程导致体系资源缺乏。

// 示例:运用线程池履行耗时操作
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(new Runnable() {
    @Override
    public void run() {
        // 履行耗时操作
    }
});

优化UI线程上的使命

防止长时刻占用UI线程

保证UI线程上的使命能够在短时刻内完结,防止长时刻占用UI线程。

运用Handler和Looper

合理运用HandlerLooper进行异步音讯处理,防止在UI线程上履行过多耗时操作。

// 示例:运用Handler进行异步音讯处理
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        // 在UI线程履行使命
    }
});

合理运用锁

防止UI线程上的堵塞

保证在UI线程上不要运用过多的同步锁,以防止产生死锁或长时刻堵塞。

运用准确的锁

在需求同步的当地,运用准确的锁(如synchronized要害字)而不是大局锁,以减小锁的规模。

异步加载数据

运用Loader或ViewModel

经过运用LoaderViewModel来异步加载数据,能够在后台线程中履行,防止在UI线程上堵塞。

合理运用BroadcastReceiver

异步处理播送

播送接收器中的操作应该尽量简略,防止在主线程上履行长时刻操作。可考虑将耗时使命放入异步使命或线程池中。

动态注册播送接收器

防止运用静态注册播送接收器,因为静态注册的接收器或许在运用处于非活动状况时触发,增加了ANR的危险。

经过采纳上述优化计划,开发者能够明显削减Android运用中ANR的产生概率,提升运用的呼应性和用户体会。

最佳实践

关于闲鱼的ANR治理,我有几条心得… (qq.com)

1. SharedPreference优化

问题描绘: 线上ANR traces数据显现,SharedPreference(sp)导致的ANR问题首要集中在以下三类状况:

  1. SP文件加载与UI线程堵塞 在SP文件创建后,体系会独自运用一个线程来加载解析对应的SP文件。当UI线程尝试拜访SP中的内容时,存在以下问题:UI线程堵塞: 假如SP文件还未被彻底加载解析到内存,UI线程在尝试拜访SP内容时会被堵塞,直到SP文件被彻底加载到内存停止。这或许导致UI线程无法呼运用户操作,引发ANR。加载顺序问题: 因为SP文件加载是在独自的线程中进行的,当UI线程需求拜访SP内容时,或许产生加载没有完结的状况,然后堵塞UI线程。
  2. 数据跨进程通讯与主线程等候 为了保证数据的跨进程完整性,Google体系答应运用运用SP进行跨进程通讯。但是,这或许导致ANR的原因包含:主线程等候SP写入: 在组件毁掉或其他生命周期结束时,为了保证当时写入使命在当时组件的生命周期内完结写入,主线程或许在组件毁掉或暂停的生命周期内等候SP彻底写入到对应的文件中。这种等候会导致主线程被堵塞,直到写入使命完结。QueuedWork.waitToFinish()处堵塞: 在SP写入使命完结前,主线程或许会在QueuedWork.waitToFinish()处堵塞。这是为了保证写入使命的完整性,但也或许导致主线程堵塞时刻过长,触发ANR。

优化计划: 经过对比线下测试中MMKV与sp的性能数据,发现MMKV在以下三个问题上表现较优。经过编译器切面的方法,接管所有getSharedPreferences接口调用,依据白名单装备返回MMKV完结或者原始体系的SharedPreferencesImpl完结,使业务层运用无感知。

2. 网络播送监听耗时优化

问题描绘: 线上ANR traces数据显现,getActiveNetworkInfo的ipc调用耗时较长,或许是因为ipc跨进程通讯本身的耗时,以及监听网络状况的播送监听者实例过多,每个都会重复调用一次查询网络状况,导致耗时加剧。

优化计划: 经过动态代理IConnectivityManager接口,阻拦代理getActiveNetworkInfo办法,优先运用缓存。统一大局的网络播送监听器在异步线程IPC获取网络信息,更新缓存,后续能够直接运用缓存,防止多次IPC调用。

3. 发动组件推迟注册

问题描绘: 在Application#onCreate阶段,串行使命会堵塞主线程履行,然后或许引发ANR。体系发送的要害音讯得不到主线程调度。

优化计划: 尽量防止在发动阶段注册receiver、service等组件,或者推迟到onCreate全部履行结束再注册。经过在Application的registerReceiver办法中判断是否完结初始化,假如未完结则经过Handler推迟注册。

参阅文档

关于闲鱼的ANR治理,我有几条心得… (qq.com)

Android ANR问题总结 (qq.com)

微信Android客户端的ANR监控计划 (qq.com)

【得物技术】得物App ANR监控平台设计 – (juejin.cn)

Android ANR|原理解析及常见案例 (qq.com)