安卓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()方法耗时的问题.

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值