深入分析ANR

为什么会产生ANR

ANR 是英文Application Not Response 的缩写,也就是当前的应用未响应。“临床”表现为当前的应用滑动无响应,点击无响应,输入无响应。等待一会后有些手机会直接闪退(比如oppo),有些手机会有弹框“当前应用未响应,是否结束”。然后你可以选择结束应用或继续等待。是否有弹框选择对于我们开发人员来说可以去开发者设置里面进行设置,但是对于普通用户来说,闪退了就是闪退了,跟crash的用户体感是一样的

产生anr情况

首先为什么会产生ANR?其实以下四种场景总结起来一句话:UI线程没有办法在规定的时间内做出本应当做的事情。

1InputDispatching Timeout

这个Timeout 引发的anr表示5s内无法响应屏幕的点击事件或者输入事件。inputDispatching 的超时事件是一个native层的引发的anr,我们这里由于篇幅所限制不做扩展

2 广播的onReceiver()在规定的事件内无法被处理完

Android 系统定义了如果广播的onReceiver()事件在规定的时间内无法被执行完,那么系统就会抛出ANR异常,其中:
前台广播:10s
后台广播:60s
整个流程是如何判断的呢?我们接下来就进入源码的世界中

BroadcastQueue

我们看一下BroadcastQueue里面的重要方法

 public void scheduleBroadcastsLocked() {
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
                + mQueueName + "]: current="
                + mBroadcastsScheduled);

        if (mBroadcastsScheduled) {
            return;
        }
        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
        mBroadcastsScheduled = true;
    }

这里我们看到是通过handler的方法发送了一个广播的intent Message。我们再往后追,handler的回调中处理这个消息后调用了processNextBroadcastLocked()方法。我们看一下源码,这里篇幅较长,我们只截取了核心的调用方法

    final void processNextBroadcast(boolean fromMsg) {
        broadcastTimeoutLocked(false);     
        setBroadcastTimeoutLocked(timeoutTime); 
        cancelBroadcastTimeoutLocked();
    }

我们看一下

final void setBroadcastTimeoutLocked(long timeoutTime) {
        if (! mPendingBroadcastTimeoutMessage) {
            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
            mHandler.sendMessageAtTime(msg, timeoutTime);
            mPendingBroadcastTimeoutMessage = true;
        }
    }

整个的核心思路就是发送一个延时的message,当时间到达的时候,触发handler的回调,这个时候回去检查当前的超时广播事件监听有没有被remove掉,如果没有被remove掉,就表明当前的广播执行超时了。我们看一下超时的处理

 scheduleBroadcastsLocked(){

        if (anrMessage != null) {
            // Post the ANR to the handler since we do not want to process ANRs while
            // potentially holding our lock.
            mHandler.post(new AppNotResponding(app, anrMessage));
        }
}

有注释也可以看出,这个handler会抛往ActivityManagerService,执行anr处理的流程。这个流程我们在下面进行分析

3 Service 组件引发的ANR

我们都知道,service里面是不可以做耗时操作的,我们先抛出结果:
前台service:20s
后台service:200s
超过这两个时间,我们的应用程序也会抛出ANR。
接下来我们进入源码分析。我们知道应用冷启动的时候,ActvityManagerService的attachApplicationLocked方法会通知ActivityThread 把当前的application 创建起来。attachApplicationLocked里面有一个重要的逻辑,就是会判断当前有没有需要执行的Service

// Find any services that should be running in this process...
        if (!badApp) {
            try {
                didSomething |= mServices.attachApplicationLocked(app, processName);
                checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
                badApp = true;
            }
        }

进入ActiveService.java的类后,核心的“挖坑”的地方就是这个方法

   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);
    }

这个流程就和广播的ANR超时处理机制很像了。一样就是开启一个定时消息。如果这个定时消息没有被取消,那么就会走ANR的处理逻辑,抛出异常

 void serviceTimeout(ProcessRecord proc) {
        ...
        if (anrMessage != null) {
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }
    }

4 Contentprovider publish 引发的异常

还是回到万能的ActivityManagerService的attachApplicationLocket()方法中,查看关键的contentprovider 流程处理相关

 List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

        if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
            Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
            msg.obj = app;
            mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
        }

还是这个熟悉的流程,发送定时Message,如果能收到这个,说明当前CP的发布10s超时,从而引发ANR

四大组件产生的原因小结

其实总结上面的几个流程,简单来说就是一个挖坑,掉坑和填坑的过程
挖坑:发送定时消息
填坑:正常处理流程,成功后remove本条message
掉坑:无法正常处理流程,定时消息成功接受,触发anr,掉进坑里去

发生anr后,系统做了什么

我们从上文的分析中可以看到,所有的ANR错误最后都会走到AppErrors里面的appNotResponding里面。下面我们用流程图进行分析


ANR处理流程

anr 监控的原理和方法

1 监听Looper Log的时间

思路
在Looper的loop()方法中,我们可以看到在调用msg的dispatchMessage方法的前后有两个logger的日志打印。那么我们就可以采用这个方法,定义一个Printer类,去计算这两个Logger的打印时间,这样就可以准确的判断出是否发生了ANR
优点
灵活配置,可以准备的得到发生ANR的时间和调用栈;
缺点
1 不能覆盖所有的场景,有些anr并不通过dispatchMessage方法调用,比如input事件的ANR
2 Logger有可能被第三方所接管
3 dispatchMessage方法如果执行时间过长,同样也无法触发计算

2 定时往主线程发一个时间,如果5s后没有响应,那么就是发生anr了

思路
启用一个线程向主线程定时发送一个消息a,然后线程进行休眠,等到时间结束后去检查判断这个值是否变成了a+1,如果没有,说明发生了anr
优点
1 无侵入,对工程项目比较友好
2 代码简单,流程比较容易理解
3 没有兼容问题,通杀
缺点
1 不能保证所有场景都可以触发,另外这个休眠的时间阀值不好控制

3 监听fileobserve的ANR路径的读写

思路
监听data/anr 文件路径下的文件读写的变化,因为一旦发生了anr,系统就会往这个路径创建anr文件
缺点
随着Android对文件读写权限的严格把控,这个思路现在已经不可用了~

4 监听广播

思路
监听“android.intent.action.ANR“广播
“缺点”
所有发生ANR的应用都会发出这个广播,因此就容易发生“误报”。所以需要在接收到广播的时候进行过滤

日常开发如何尽量避免anr

1 小心使用SharedPreferences
2 多线程情况下选用线程池
3 不要在主线程做耗时操作
4 跨进程也有可能引发anr(cpu抢占)

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