Application.onCreate()會造成Service啟動ANR麼?

無尾熊二發表於2018-12-02

1 概述

本文針對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。

3. 結尾

本文只是一個對Service啟動流程與ANR的簡單介紹,主要是為了解決自己一直以來的一個困惑,即到底Application.onCreate()會不會造成Service的ANR,也希望能給一樣困惑的人解釋清楚。如果希望深入瞭解,建議還是通過閱讀原始碼仔細學習一下Service的啟動流程以及ANR的觸發原理,這兩個點理解之後,對啟動Service造成的ANR就會更加明晰。

流程圖見 github.com/kishimotoin…

相關文章