Android ANR问题专题

android ANR发生的原因总结和解决办法

ANR的全称是application not responding,意思就是程序未响应,类似于我们在windows上见到的程序未响应。ANR发生会使用户觉得我们的程序不友好,那么什么情况会导致ANR的发生呢?

首先ANR的发生是有条件限制的,分为以下三点:

1.只有主线程才会产生ANR,主线程就是UI线程;

2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;

3.上述事件响应超时,不同的context规定的上限时间不同

  • a.主线程对输入事件5秒内没有处理完毕
  • b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
  • c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。

细分的话,导致ANR的原因有如下几点:

  • 1.耗时的网络访问
  • 2.大量的数据读写
  • 3.数据库操作
  • 4.硬件操作(比如camera)
  • 5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候
  • 6.service binder的数量达到上限
  • 7.system server中发生WatchDogANR
  • 8.service忙导致超时无响应
  • 9.其他线程持有锁,导致主线程等待超时
  • 10.其它线程终止或崩溃导致主线程一直等待

那么如何避免ANR的发生呢或者说ANR的解决办法是什么呢?

1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。

2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。

3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。


1.3 如何解决ANR
  1. 使用AsyncTask处理耗时IO操作
  2. 使用Thread(不可以创建handler)或者HandlerThread提高优先级
  3. 使用Handler来处理工作线程的耗时任务
  4. Activity的onCreate和onResume回调中尽量少写耗时操作的代码
1.4 ANR问题排查

1 获取trace.txt文件

adb shell cat /data/anr/traces.txt > d:/traces.txt (拷贝到d盘)

2 根据trace.txt分析anr问题的原因

从LOG可以看出ANR的类型,CPU的使用情况,如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR

如果CPU使用量很少,说明主线程被BLOCK了

如果IOwait很高,说明ANR有可能是主线程在进行I/O操作造成的

除了看LOG,解决ANR还得需要trace.txt文件,


看完这篇 Android ANR 分析,就可以和面试官装逼了!
https://juejin.im/entry/5c3e9ef6e51d4539b927dfd1

  1. ANR的监测机制:首先分析Service和输入事件大致工作流程,然后从Service,InputEvent两种不同的ANR监测机制的源码实现开始,分析了Android如何发现各类ANR。在启动服务、输入事件分发时,植入超时检测,用于发现ANR。
  2. ANR的报告机制:分析Android如何输出ANR日志。当ANR被发现后,两个很重要的日志输出是:CPU使用情况和进程的函数调用栈,这两类日志是我们解决ANR问题的利器。
  3. 监测ANR的核心原理是消息调度和超时处理。
  4. 只有被ANR监测的场景才会有ANR报告以及ANR提示框。

我们先抛出两个问题问题一:Service启动流程?问题一: 如何监测Service超时?

1. Service启动流程如下图所示:

(1)ActiveServices.realStartServiceLocked()在通过app.thread的scheduleCreateService()来创建Service对象并调用Service.onCreate()后,接着又调用sendServiceArgsLocked()方法来调用Service的其他方法,如onStartCommand。以上两步均是进程间通信,应用与AMS之间跨进程通信可以参考应用进程与系统进程通信(2)以上只是列出Service启动流程的关键步骤,具体每个方法主要做哪些工作还需要查看具体的代码,暂时先忽略这些,感兴趣的可以参考Android开发艺术探索等其他相关资料

2. Service超时监测机制Service超时监测机制可以从Service启动流程中找到。

(1)ActiveServices.realStartServiceLocked()主要工作有

    private final void realStartServiceLocked(ServiceRecord r,            ProcessRecord app, boolean execInFg) throws RemoteException {        ...        // 主要是为了设置ANR超时,可以看出在正式启动Service之前开始ANR监测;        bumpServiceExecutingLocked(r, execInFg, "create");       // 启动过程调用scheduleCreateService方法,最终会调用Service.onCreate方法;        app.thread.scheduleCreateService(r, r.serviceInfo,        // 绑定过程中,这个方法中会调用app.thread.scheduleBindService方法        requestServiceBindingsLocked(r, execInFg);        // 调动Service的其他方法,如onStartCommand,也是IPC通讯        sendServiceArgsLocked(r, execInFg, true);    }

(2)bumpServiceExecutingLocked()会调用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;        // 在serviceDoneExecutingLocked中会remove该SERVICE_TIMEOUT_MSG消息,        // 当超时后仍没有remove SERVICE_TIMEOUT_MSG消息,则执行ActiveServices. serviceTimeout()方法;        mAm.mHandler.sendMessageDelayed(msg,                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);        // 前台进程中执行Service,SERVICE_TIMEOUT=20s;后台进程中执行Service,SERVICE_BACKGROUND_TIMEOUT=200s    }

(3)如果在指定的时间内还没有serviceDoneExecutingLocked()方法将消息remove掉,就会调用ActiveServices. serviceTimeout()方法

void serviceTimeout(ProcessRecord proc) {    ...    final long maxTime =  now -              (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);    ...    // 寻找运行超时的Service    for (int i=proc.executingServices.size()-1; i>=0; i--) {        ServiceRecord sr = proc.executingServices.valueAt(i);        if (sr.executingStart < maxTime) {            timeout = sr;            break;        }       ...    }    ...    // 判断执行Service超时的进程是否在最近运行进程列表,如果不在,则忽略这个ANR    if (timeout != null && mAm.mLruProcesses.contains(proc)) {        anrMessage = "executing service " + timeout.shortName;    }    ...    if (anrMessage != null) {        // 当存在timeout的service,则执行appNotResponding,报告ANR        mAm.appNotResponding(proc, null, null, false, anrMessage);    }}

(4)Service onCreate超时监测整体流程如下图

输入事件超时监测


记一次分析解决ANR过程

经过我查看log信息发现
Reason: Input dispatching timed out (Waiting because the touched window has not finished processing the input events that were previously delivered to it.)

Load: 0.9 / 0.57 / 0.68
CPU usage from 2505ms to -3307ms ago:
==94%== 20357/com.richeninfo.cmoa: 94% user+ 0.3% kernel / faults: 1 minor
22% 810/system_server: 17% user + 5.1% kernel / faults: 1061 minor
0.5% 146/debuggerd: 0.2% user + 0.3% kernel / faults: 2717 minor
3.4% 977/com.android.systemui: 3.2% user + 0.1% kernel / faults: 11 minor
1.8% 1310/com.android.phone: 1.5% user + 0.3% kernel

从LOG可以看出ANR的类型,CPU的使用情况,如果CPU使用量接近100%,说明当前设备很忙,有可能是CPU饥饿导致了ANR
如果CPU使用量很少,说明主线程被BLOCK了
如果IOwait很高,说明ANR有可能是主线程在进行I/O操作造成的

所以我这里导致ANR的原因应该是CPU不足。

仅仅查看log的信息还不足以帮我们定位到ANR的原因,所以需要去看data/anr/trace文件

at com.richeninfo.cmoa.widget.AutoScrollViewPager.onTouchEvent(AutoScrollViewPager.java:219)

从这些信息中首先看到线程的状态为”main” prio=5 tid=1 SUSPENDED

而经过一位朋友提示线程状态为SUSPENDED

只有在debug的时候会这样,可是我没在debug啊,所以网上查到下面资料图:

可以看到资料说这种状态通常是由于GC或者debug,所以我的情况应该是就GC了,这也验证了前面说的CPU不足的原因。
Reason: Input dispatching timed out(Waiting because the touched window has not finished processing the input events that were previously delivered to it.)
说明这CPU不足导致无法相应下一个input events导致ANR。

那就去看看onTouchEvent里都执行了什么鬼操作。

 announcePager.setOnTouchListener(new View.OnTouchListener() {

                @Override
                public boolean onTouch(View v, MotionEvent event) {

                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            announcePager.stopAutoScroll();

                            break;
                        case MotionEvent.ACTION_MOVE:
                        announcePager.startAutoScroll();

                            break;
                        case MotionEvent.ACTION_UP:
                            announcePager.startAutoScroll();

                            break;

                        default:
                            break;
                    }

                    return false;
                }

            });

可以看到相应了三个action,那么就去看看startAutoScroll()stopAutoScroll()都写了什么。

public void startAutoScroll(int delayTimeInMills) {
        isAutoScroll = true;
        sendScrollMessage(delayTimeInMills);
    }

    /**
     * stop auto scroll
     */
    public void stopAutoScroll() {
        isAutoScroll = false;
        handler.removeMessages(SCROLL_WHAT);
    }

    /**
     * set the factor by which the duration of sliding animation will change
     */
    public void setScrollDurationFactor(double scrollFactor) {
        scroller.setScrollDurationFactor(scrollFactor);
    }

    private void sendScrollMessage(long delayTimeInMills) {
        /** remove messages before, keeps one message is running at most **/
        handler.removeMessages(SCROLL_WHAT);
        handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills);
    }

看到是handler在发送消息并且每次发送之前都要把前面的消息移除。
结合我操作APP发生ANR的时机,判断问题应该是出现在action_move响应太频繁,导致频繁startAutoScroll();然后方法内部里频繁handler.removeMessages(),这样被remove的消息由于垃圾回收机制频繁引起GC,所以就导致了CPU不足,这样似乎可以验证前面的说法。

问题找到了,那就要解决,这个解决也简单,直接把action_move里的
startAutoScroll()注释掉就OK了,其实这里也不需要在action_move里执行startAutoScroll(),因为action_up里已经执行了startAutoScroll()。

这样ANR就分析解决完毕了。有了这次经验,以后相信自己能比较好应对ANR。。。认真分析trace信息和log日志.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容