安卓ANR问题1_ANR问题类型及产生原理

ANR问题类型及产生原理

ANR(Application Not Responding):即应用无响应. 在日常使用安卓手机的过程中, 对最anr最直接的印象就是手机弹框显示应用未响应. 选择继续等待或者关闭.
如果应用程序的主线程在规定的时间内, 没有完成特定操作和事件, 就会发生ANR.

四种ANR类型

  1. KeyDispatchTimeout : input事件在5S内没有处理完成发生ANR
  2. ServiceTimeout : bind,create,start,unbind等操作,前台Service在20s内,后台
    Service在200s内没有处理完成发生ANR
  3. BroadcastTimeout : BroadcastReceiver onReceiver处理事务时前台广播在10S内,后台广播在60s内 (应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service).
    没有处理完成发生ANR
  4. ProcessContentProviderPublishTimedOutLocked : ContentProvider publish在10s内没有处理完成发
    生ANR

其中第四种ANR发生的概率最小.

ANR产生的常见原因

  1. 主线程耗时操作,如复杂的layout,庞大的for循环,IO等. (实际APP开发时开发者会避开这种, 没有见到过这种问题产生ANR);
  2. 主线程被子线程同步锁block. (当子线程先拿着锁, 主线程等待这把锁的时候, 子线程太耗时. 导致主线程一直被阻塞, 从而ANR)
  3. 主线程被Binder对端阻塞
  4. Binder被占满导致主线程无法和SystemServer通信
  5. 得不到系统资源(CPU/RAM/IO) (耗时的动画需要大量的计算工作,可能导致CPU负载过重.)

ANR触发机制

ANR有四种类型, 所以可以从这四种类型去了解ANR触发机制.

1 Service发生ANR的机制

Service Timeout是AMS中MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。

由ServiceTimeout原因发生的ANR有两种, 前台服务未响应(20s) 和 后台服务未响应(200s).

1.1 startService()触发ANR流程:

  1. 在用户进程A中, 当发起startService的时候, 通过binder通信, 通过IActivityManager#startService调用AMS中startService()方法. (这一步从用户进程跨越到system_server进程, 调用AMS的方法)
    备注:IActivityManager对象保存在进程A的单例Singleton<IActivityManager>中,进程启动时查询ServiceManager#getService获得,具有缓存作用
  1. 在AMS中, 当realStartServiceLocked()启动服务的时候, 其内部会操作AMS的MainHandler调用sendMessageAtTime()方法发送SERVICE_TIMEOUT_MSG消息埋下炸弹, 并通过参数指定消息发送的时间. (当AMS埋好炸弹之后, 通过binder把目标服务创建所需要的信息传递给目标服务进程B, 进行服务的创建)

    在这里插入图片描述

  2. 在服务进程B中, 服务进程将AMS传递的过来的服务创建信息进行打包, 然后ApplicationThread内部类使用消息传递机制让ActivityThread外部类异步执行service.onCreate回调. 当service创建完毕之后, 服务进程B会再次binder通信, 告诉AMS调用serviceDoneExecuting(), 该方法最后会执行Handler.removeMessages()把之前埋下的炸弹拆除.
    所以, 回调完Service的onCreate()方法之后便会移除启动服务超消息SERVICE_TIMEOUT_MSG.
    Service启动过程出现ANR,”executing service [发送超时serviceRecord信息]”, 这往往是service的onCreate()回调方法执行时间过长。

  3. 当炸弹没有及时被拆除. 当SERVICE_TIMEOUT_MSG爆炸消息被AMS的MainHandler收到的时候, 就会发生ANR.

    在这里插入图片描述

注: 1: 当启动进程A发起startService的时候, AMS如果发现目标服务的进程还没有启动, 会启动一个进程. 但是启动这个进程的时间不算在ANR的爆炸时间内. 因为只有当AMS真正调用realStartServiceLocked()之后, 才会埋下炸弹.

注2: 在ActivityManagerService实例化的时候, AMS会新开的一条HandlerThread线程,在这个线程里会准备好一个Looper. 然后使用有参构造指定之前线程准备好的Looper,实例化一个Handler.
该Handler就是AMS中的mHandler.
所有埋炸弹的Message都会在这个Looper中的, MessageQueue中进行入队. 最终回调MainHandler.handleMessage()方法.

注3: AMS和ActivityService
在ActivityManagerService实例化的时候, 会实例化一个ActiveService成员变量.
在实例化ActiveService的时候, AMS会把this传给ActiveService, 然后ActiveService也有一个AMS的成员变量.
AMS.mServices和AS.mAm.

注4: ApplicationThread是ActivityThread的普通内部类, 当system_server进程通过binder通信调用ApplicationThread中的scheduleCreateService()方法时, 在ApplicationThread该方法内部往外部ActivityThread发送消息, 当ActivityThread进行handle调用的时候, 创建服务.

1.2 产生ANR的源码中与"埋炸弹", "拆炸弹"有关的代码

1) 埋炸弹

[ActiveSErvices.java]
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //发送delay消息(SERVICE_TIMEOUT_MSG)
    bumpServiceExecutingLocked(r, execInFg, "create");
   ...
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ... 
    scheduleServiceTimeoutLocked(r.app);
}

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    ...
    //当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

2) 拆炸弹

在目标进程的服务创建之后, 会remove掉AMS, MessageQueue之中的超时消息, 当这个消息被remove掉之后, 自然不会再被MainHandler处理, 也就不会发生ANR.

[ActivityThread.java]
 private void handleCreateService(CreateServiceData data) {
        ...
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...

        try {
            //创建ContextImpl对象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //创建Application对象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //调用服务onCreate()方法 
            service.onCreate();
            
            //拆除炸弹引线
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }
[ActivityThread.java]
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {
    ...
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            r.app.execServicesFg = false;
            r.app.executingServices.remove(r);
            if (r.app.executingServices.size() == 0) {
                //当前服务所在进程中没有正在执行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        ...
    }
    ...
}

2 BroadcastReceiver发生ANR的机制

BroadcastReceiver Timeout是AMS中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发.
其中, 前台广播超时mTimeoutPeriod为10s, 后台广播超时mTimeoutPeriod为 60s.

广播发送的时候有两种方式, 一种是 "默认广播" (并行广播), 第二种是 "有序广播" (串行广播).
只有串行广播才需要考虑超时,因为接收者是串行处理的,前一个receiver处理慢,会影响后一个receiver;并行广播 通过一个循环一次性向所有的receiver分发广播事件,所以不存在彼此影响的问题,则没有广播超时;

串行广播超时情况1:某个广播总处理时间 > 2* receiver总个数 * mTimeoutPeriod, 其中mTimeoutPeriod
串行广播超时情况2:某个receiver的执行时间超过mTimeoutPeriod

Broadcast的ANR流程如下图所示:


BBABA.png
  1. 图中1,2,3 步从用户进程通过binder通信调用AMS的broadcastIntentLocked()方法.
    在该方法中, 首先进行了一系列的广播验证.
    然后先并行处理动态注册的广播, 然后合并动态注册广播和静态注册的广播receivers进行串行处理. (原因: 让静态注册的广播串行化,能防止出现瞬间启动大量进程的喷井效应。)
    由于我们ANR只会在处理串行广播的时候发生, 接下来我们只关注串行广播处理的流程.

  2. 图中第4,5步, 在第4步中AMS按照并行广播和串行广播两种, 分别要发送的广播入相应的队列, 然后再通过scheduleBroadcastLocked()方法 内部使用BroadcastHandler实例 sendMessage (BroadcastHandler是BroadcastQueue类的一个普通内部类).
    备注:这里的BroadcastHandler实例, 是在AMS启动的时候, AMS构造方法中进行实例化的, 在new这个handler的时候, 把AMS构造器之前的准备好Looper给了该Handler.

  3. 图中第6, 7步, 当系统回调handlerMessage()方法的时候, 其内部进行广播的处理. 当然我们关注的是处理串行广播的流程.

  4. 第9步, 当前广播超时, 强制结束广播.

  5. 10,11 通过binder通信告诉ActivityThread, 让他去sendMessage(Receiver), ActivityThread处理这个消息的时候回调BroadcastReceiver的onReceiver()方法, 完成广播接受处理.

  6. BroadcastReceiver完成onReceiver()回调. 而BroadcastQueue向ActivityThread发起Binder通信之后, 在BroadcastQueue的processNextBroadcast()方法中执行cancelBroadcastTimeoutLocked(), handler会发送removeMessages的消息, 把之前埋下的炸弹拆除.

综上所诉: 当分析ANR的问题时, 如果log显示是BroadcastReceiver的ANR, 可以首先怀疑BroadCastReceiver.onRecieve()的问题; 如果是Service的ANR, 可以首先怀疑是Service.onCreate()方法耗时的问题.

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

推荐阅读更多精彩内容