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

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

内容简介:本文针对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么?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

你不知道的JavaScript(上卷)

你不知道的JavaScript(上卷)

[美] Kyle Simpson / 赵望野、梁杰 / 人民邮电出版社 / 2015-4 / 49.00元

JavaScript语言有很多复杂的概念,但却用简单的方式体现出来(比如回调函数),因此,JavaScript开发者无需理解语言内部的原理,就能编写出功能全面的程序;就像收音机一样,你无需理解里面的管子和线圈都是做什么用的,只要会操作收音机上的按键,就可以收听你喜欢的节目。然而,JavaScript的这些复杂精妙的概念才是语言的精髓,即使是经验丰富的JavaScript开发者,如果没有认真学习也无......一起来看看 《你不知道的JavaScript(上卷)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器