1 概述
本文針對Service啟動過程造成的ANR進行分析,同時啟動過程限制在以下兩個條件中:
(A程式,呼叫startService()方法的程式;B程式,需要開啟的Service所在的程式,A和B不是同一個程式)
- 呼叫startService()啟動service,暫不分析bindService()的情況
- 啟動Service前,B程式不存在
為了分析ANR所產生的原因,對於在不同程式中啟動Service的流程需要有一個簡單的瞭解,下面首先簡要分析一個這個過程。
2 Service啟動流程
2.1 啟動流程時序圖
首先來看下簡化的啟動流程時序圖,共分為兩張圖。第一張圖是描述startService()的呼叫過程,第二張圖是描述startProcess的呼叫過程。
圖1:
圖2:
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方法。這個方法主要做了兩件事:
- 告訴AMS程式已啟動:呼叫attach()方法
- 啟動Looper
2.2.2.1 ActivityThread.attach()方法解析
attach()方法在ActivityThread一側,最主要的是呼叫了AMS的attachApplication()方法,將自身程式的ApplicationThread物件傳送給AMS。然後在AMS一側,AMS則做了以下操作:
- 儲存ApplicationThread物件
- 通過ApplicationThread物件呼叫ActivityThread的bindApplication()方法,bindApplication()方法就是在應用程式側傳送了一個BIND_APPLICATION訊息然後就返回了,所以說bindApplication()方法是一個非同步的方法。注意,此時應用程式中的looper並沒有開始迴圈,所以這條message也僅是被增加到了訊息佇列中。
- 如果有待啟動的Activity,給ActivityThread傳送 CREATE_ACTIVITY 的訊息,這個也是非同步的。此時對於ActivityThread來說,主執行緒訊息佇列中有兩條待處理的訊息:1.bindApplication 2.createActivity。特別注意,此時主執行緒的Looper並沒有開始迴圈,所以在後面開始迴圈的時候,第一條訊息一定會延後第二條訊息的執行。另外,這裡只會傳送一條 CREATE_ACTIVITY 的訊息,因為只有最頂部的第一個待開啟的Activity會傳送訊息。
- 如果有待啟動的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耗時造成的。
- 如果有待啟動的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就會更加明晰。