Application.onCreate()会造成Service启动ANR么?

栏目: 后端 · 发布时间: 6年前

内容简介:本文针对Service启动过程造成的ANR进行分析,同时启动过程限制在以下两个条件中:(A进程,调用startService()方法的进程;B进程,需要开启的Service所在的进程,A和B不是同一个进程)为了分析ANR所产生的原因,对于在不同进程中启动Service的流程需要有一个简单的了解,下面首先简要分析一个这个过程。

本文针对Service启动过程造成的ANR进行分析,同时启动过程限制在以下两个条件中:

(A进程,调用startService()方法的进程;B进程,需要开启的Service所在的进程,A和B不是同一个进程)

  1. 调用startService()启动service,暂不分析bindService()的情况
  2. 启动Service前,B进程不存在

为了分析ANR所产生的原因,对于在不同进程中启动Service的流程需要有一个简单的了解,下面首先简要分析一个这个过程。

2 Service启动流程

2.1 启动流程时序图

首先来看下简化的启动流程时序图,共分为两张图。第一张图是描述startService()的调用过程,第二张图是描述startProcess的调用过程。

图1:

Application.onCreate()会造成Service启动ANR么?

图2:

Application.onCreate()会造成Service启动ANR么?

2.2 流程分析

2.2.1 startService流程分析

从图1中我们已经可以看到一个大致的流程,其中有一个对ActiveServices.bringUpServiceLocked()的调用,这个方法对分析启动流程比较关键,下面我们对这个方法进行一下分析。

private final String bringUpServiceLocked(...) throws TransactionTooLargeException {
    ...
    // 1. 如果Service已经启动了,向B进程主线程发送消息,异步调用onStartCommand()方法
    if (r.app != null && r.app.thread != null) {
        sendServiceArgsLocked(...);
    }
    ...
    // 2. 如果B进程存在,那么向B进程发送消息,启动Service
    app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
    if (app != null && app.thread != null) {
            realStartServiceLocked(...);
    }
    ...
    // 3. 如果B进程不存在,那么首先创建进程,并将要启动的Service添加到mPendingServices中。添加到mPendingServices中的Service,在进程创建成功后,会被依次启动。
    if (app == null) {
        if ((app=mAm.startProcessLocked(...) == null) {
            ...
        }
        ...
    }
    if (!mPendingServices.contains(r)) {
        mPendingServices.add(r);
    }
    ...
}
复制代码

因为本文分析的是在另外一个未启动的进程中启动Service的情况,所以我们只需关注注释3的流程。这个流程会先去异步的启动B进程,同时将需要启动的Service加到mPendingServices中。接下来我们再去看看B进程启动的流程。

2.2.2 startProcess流程分析

进程在启动起来之后,会调用ActvityThread的main方法。这个方法主要做了两件事:

  1. 告诉AMS进程已启动:调用attach()方法
  2. 启动Looper

2.2.2.1 ActivityThread.attach()方法解析

attach()方法在ActivityThread一侧,最主要的是调用了AMS的attachApplication()方法,将自身进程的ApplicationThread对象发送给AMS。然后在AMS一侧,AMS则做了以下操作:

  1. 保存ApplicationThread对象
  2. 通过ApplicationThread对象调用ActivityThread的bindApplication()方法,bindApplication()方法就是在应用进程侧发送了一个BIND_APPLICATION消息然后就返回了,所以说bindApplication()方法是一个异步的方法。注意,此时应用进程中的looper并没有开始循环,所以这条message也仅是被增加到了消息队列中。
  3. 如果有待启动的Activity,给ActivityThread发送 CREATE_ACTIVITY 的消息,这个也是异步的。此时对于ActivityThread来说,主线程消息队列中有两条待处理的消息:1.bindApplication 2.createActivity。特别注意,此时主线程的Looper并没有开始循环,所以在后面开始循环的时候,第一条消息一定会延后第二条消息的执行。另外,这里只会发送一条 CREATE_ACTIVITY 的消息,因为只有最顶部的第一个待开启的Activity会发送消息。
  4. 如果有待启动的Service,给ActivityThread发送 CREATE_SERVICE 的消息,这个也是异步的。此时对于ActivityThread来说,主线程消息队列中有大于等于3条待处理的消息:1.bindApplication 2.createActivity 3.createService(一条或多条,不像Activity,Service会为所有等待启动的Service发送消息到消息队列)。注意,此时主线程的Looper同样没有开始循环,所以在后面开始循环的时候,前面几条消息的执行,会延后这些Service的启动。这样问题就来了,service executing timeout类型的ANR,其在AMS中的超时倒计时消息是在发送 CREATE_SERVICE 消息的同时发送到AMS的消息队列中的,即倒计时已经开始了,所以ActivityThread中排在Service启动前的消息,其处理时间都会被计算在Service的启动时间中,所以Service启动的anr,并不一定是自己的启动过程发生了耗时操作,也有可能是application初始化有耗时或先启动的Activity耗时造成的。
  5. 如果有待启动的BroadcastReceiver,给ActivityThread发送 RECEIVER 的消息。逻辑与前面一样,插到消息队列等待执行。

特别特别注意一点,就是attach()方法执行完毕之前,主线程的Looper一直是没有开始循环的状态。attach()执行完毕后,才会调用Looper.loop()开始消息循环。

当Looper开始循环之后,就会马上开始处理消息队列中的message。对于启动Service来说,那么此时队列中的message包括两条:1.BIND_APPLICATION 2.CREATE_SERVICE。

下面我们看下这两条消息都做了什么,BIND_APPLICATION。

BIND_APPLICATION 在ActivityThread里对应的就是handleBindApplication()方法。

private void handleBindApplication(AppBindData data) {
    ...
    Application app = data.info.makeApplication(...);
    ...
}
复制代码

从上面我们看到有一个我们经常接触的操作——Application的初始化,让我们继续看看makeApplication做了什么? (data.info 是一个LoadedApk对象)

public Application makeApplication(...){
    ...
    app = mActivityThread.mInstrumentation.newApplication(...);
    ...
    instrumentation.callApplicationOnCreate(app);
    ...
}
复制代码

上面就是通过反射创建Application对象,然后调用application.onCreate()方法。这说明启动Service的过程中涉及到application的初始化。

接下来,我们看看第二条message做了什么?CREATE_SERVICE 在ActivityThread里对应的就是handleCreateService()方法

private void handleCreateService(...) {
    ...
    service = (Service) cl.loadClass(data.info.name).newInstance();
    ...
    service.onCreate();
    ...
    ActivityManagerNative.getDefault().serviceDoneExecuting(...);
    ...
}
复制代码

这个方法做了三件事,反射创建Service对象,调用Service的onCreate(),通知AMS服务启动完成。

上面就是Service启动流程的大致介绍,有了对流程的简单了解,还不足以分析产生ANR的原因。我们还要再了解一下ANR的触发机制。

2.3 ANR触发机制

这个机制的本质就是AMS在某个时间节点发送一个“超时等待”的message到自己的消息队列中,如果超时时间结束前,service启动了,就移除这个message,如果没启动,这个message就会被执行。这个message执行的内容最终就是调用AMS的appNotResponding()方法触发ANR操作。如果大家有意愿更深入的了解ANR的触发原理,可以阅读一下GitYuan的博文 理解Android ANR的触发原理

那么对于Service来说,是在什么节点发送的“超时等待”的message,又是在什么时候移除的message呢?

回顾一下2.1节中的图2,以及2.2.2.1节中对attach()方法分析的第4个步骤,AMS是在进程attach,发送创建Service消息的时候,一并将“超时等待”消息发出去的。具体的方法可以查看该步骤调用的方法(ActiveServices.realStartServiceLocked()->bumpServiceExecutingLocked())。然后在ActivityThread.handleCreateService()方法中,通过ActivityManagerNative.getDefault().serviceDoneExecuting(...)方法告诉AMS服务启动完成,此时AMS会移除“超时等待”消息。

2.4 可能造成ANR的原因

在完成对service启动流程和ANR触发原理的简单介绍之后,我们来最终分析一下可能造成ANR的原因。

从上面的分析我们可以看到,新的进程在Looper开始循环前,消息队列中有两个消息等待处理(BIND_APPLICATION,CREATE_SERVICE),并且此时AMS侧已经开始对Service启动过程进行倒计时。所以对于Service来说,其启动时间会受到两个方法的影响:1.Application.onCreate() 2.Service.onCreate()。也就是说两个方法中任何一个方法有耗时操作,都有可能造成ANR。


以上所述就是小编给大家介绍的《Application.onCreate()会造成Service启动ANR么?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Rust

Programming Rust

Jim Blandy / O'Reilly Media / 2016-8-25 / GBP 47.99

This practical book introduces systems programmers to Rust, the new and cutting-edge language that’s still in the experimental/lab stage. You’ll learn how Rust offers the rare and valuable combination......一起来看看 《Programming Rust》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试