美文网首页
安卓ANR问题1_ANR问题类型及产生原理

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

作者: nianxing | 来源:发表于2019-11-29 11:21 被阅读0次

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

    相关文章

      网友评论

          本文标题:安卓ANR问题1_ANR问题类型及产生原理

          本文链接:https://www.haomeiwen.com/subject/hpiwwctx.html