Android Service重啟恢復(Service程式重啟)原理解析

看書的小蝸牛發表於2018-08-17

Android系統中,APP程式被殺後,等一會經常發現程式又起來了,這個現象同APP中Service的使用有很大關係,本文指的Service是通過startService啟動的,而不是通binderSertvice啟動的,binderSertvice是通Activity顯示介面相關的,如果兩者統一程式,binderSertvice的影響可以忽略,如果不是同一程式,Service會被重啟,畢竟業務都沒了,Service也沒必要啟動了,但是對於通過startService啟動的服務,很可能需要繼續處理自己需要處理的問題,因此,可能需要重啟。

相信不少人之前多少都瞭解過,如果想要Service在程式結束後重新喚醒,那麼可能需要用到將Service的onStartCommand返回值設定成START_REDELIVER_INTENT或者START_STICKY等,這樣被殺後Service就可以被喚醒,那麼為什麼?

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    return START_REDELIVER_INTENT(或者START_STICKY  、START_STICKY_COMPATIBILITY);
}
複製程式碼

先看下Google文件對於Service的onStartCommand常用的幾個返回值的解釋(不完全正確):

  • START_REDELIVER_INTENT

Constant to return from onStartCommand(Intent, int, int): if this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)), then it will be scheduled for a restart and the last delivered Intent re-delivered to it again via onStartCommand(Intent, int, int).

  • START_STICKY

Constant to return from onStartCommand(Intent, int, int): if this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)), then leave it in the started state but don't retain this delivered intent.

  • START_NOT_STICKY

Constant to return from onStartCommand(Intent, int, int): if this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)), and there are no new start intents to deliver to it, then take the service out of the started state and don't recreate until a future explicit call to Context.startService(Intent).

簡單說就是:程式被殺後,START_NOT_STICKY 不會重新喚起Service,除非重新呼叫startService,才會呼叫onStartCommand,而START_REDELIVER_INTENT跟START_STICKY都會重啟Service,並且START_REDELIVER_INTENT會將最後的一個Intent傳遞給onStartCommand。不過,看原始碼,這個解釋並不準確,START_REDELIVER_INTENT不僅僅會傳送最後一個Intent,它會將之前所有的startService的Intent都重發給onStartCommand,所有在AMS中會儲存所有START_REDELIVER_INTENT的Intent資訊:

AMS儲存所有殺死後需要重發的Intent

而START_NOT_STICKY跟START_STICKY都不需要AMS儲存Intent,如下圖:

AMS不儲存Intent

從測試來看,所有的Intent都會被重發,而不僅僅是最後一個。為什麼設定了某些選項就會重啟,甚至會重新傳送之前Intent呢?本文就來分析下原理,先簡單跟蹤下啟動,因為恢復所需要的所有資訊都是在啟動的時候構建好的,之後再分析恢復。(基於Android6.0)

Service首次啟動簡述(Android6.0)

為了簡化流程,我們假設Service所在的程式已經啟動,程式碼我們直接從AMS呼叫ActiveService 的startServiceLocked開始,主要看看啟動的時候是如何為恢復做準備的

  ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, String callingPackage, int userId)
            throws TransactionTooLargeException {
         <!--構建ServiceRecord-->
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg);
        ..
        ServiceRecord r = res.record;				 ..
        <!--為呼叫onStartCommand新增ServiceRecord.StartItem-->
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                service, neededGrants));
         ...
      <!--繼續啟動Service路程-->   
    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    }
複製程式碼

啟動Service的時候,AMS端先為其構建一個ServiceRecord,算是Service在AMS端的映像,然後新增一個ServiceRecord.StartItem到pendingStarts列表,這個是回撥onStartCommand的依據,之後呼叫startServiceInnerLocked 再呼叫bringUpServiceLocked進一步啟動Service:

   <!--函式1-->
   ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    <!--還沒有處理onStart-->
    r.callStart = false;
    ...
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
    ...
     
   <!--函式2-->          
    private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting) throws TransactionTooLargeException {
         //第一次呼叫的時候,r.app=null,第二次可以直接呼叫sendServiceArgsLocked觸發onStartCommand的執行
        if (r.app != null && r.app.thread != null) {
            // 啟動的時候也會呼叫
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
        ...
        
       if (!isolated) {
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
               // 呼叫realStartServiceLocked真正開始啟動Servie
                realStartServiceLocked(r, app, execInFg);
          ...           
複製程式碼

第一次啟動service的時候,為了表示APP端Service還沒啟動,r.app是沒有賦值的,r.app要一直到realStartServiceLocked的執行才被賦值,如果已經啟動了,再次呼叫startService,這裡就會走sendServiceArgsLocked,直接回撥到APP端onstartCommand:

  private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
       
        r.app = app;
        r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
		 ..
        boolean created = false;
        try {
           <!--通知APP啟動Service-->
            app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                    app.repProcState);
            r.postNotification();
            created = true;
        } ...
     // If the service is in the started state, and there are no
    // pending arguments, then fake up one so its onStartCommand() will
    // be called.
    <!--恢復:這裡應該主要是給start_sticky用的,恢復的時候觸發呼叫onStartCommand-->
    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null));
    }
    <!--處理onstartComand-->
    sendServiceArgsLocked(r, execInFg, true);
    ...
複製程式碼

realStartServiceLocked會通過Binder通知APP建立Service:app.thread.scheduleCreateService,然後接著通過通知APP回撥onStartCommand,由於AMS是通過向APP的UI執行緒插入訊息來處理的,等到sendServiceArgsLocked的請求被執行的時候,Service一定會被建立完成,建立流程沒什麼可說的,這裡主要說的是sendServiceArgsLocked。之前在startServiceLocked的時候,我們向pendingStarts塞入了一個ServiceRecord.StartItem,這個在下面的sendServiceArgsLocked會被用到:

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
        boolean oomAdjusted) throws TransactionTooLargeException {
    final int N = r.pendingStarts.size();
    if (N == 0) {
        return;
    }
    // 這裡只處理pendingStarts>0 這裡處理的是淺殺
    while (r.pendingStarts.size() > 0) {
        Exception caughtException = null;
        ServiceRecord.StartItem si;
        try {
            si = r.pendingStarts.remove(0);
            <!--這裡主要是給START_STICKY恢復用的,在START_STICKY觸發onStartCommand的時候其intent為null,pendingStarts size為1-->
            if (si.intent == null && N > 1) {
                // If somehow we got a dummy null intent in the middle,
                // then skip it.  DO NOT skip a null intent when it is
                // the only one in the list -- this is to support the
                // onStartCommand(null) case.
                continue;
            }
            <!--更新deliveredTime  恢復延時計算的一個因子-->
            si.deliveredTime = SystemClock.uptimeMillis();
            <!--將pendingStarts中的ServiceRecord.StartItem轉移到deliveredStarts 恢復的一個判斷條件-->
            r.deliveredStarts.add(si);
            <!--deliveryCount++ 是恢復的一個判斷條件-->
            si.deliveryCount++;
            ...
            r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
       	 ... 
         }
複製程式碼

sendServiceArgsLocked主要用來向APP端傳送訊息,主要有兩個作用:要麼是讓APP端觸發onStartCommand,要麼是在刪除最近任務的時候觸發onTaskRemoved。這裡先關心觸發onStartCommand,sendServiceArgsLocked會根據pendingStarts來看看需要傳送哪些給APP端,之前被塞入的ServiceRecord.StartItem在這裡就用到了,由於是第一次,這了傳過來的Intent一定是非空的,所以執行後面的。這裡有幾點比較重要的:

  • 將pendingStarts中的記錄轉移到deliveredStarts,也就是從未執行onStartCommand轉移到已執行
  • 更新deliveredTime,對於START_REDELIVER_INTENT,這個是將來恢復延時的一個計算因子
  • 更新deliveryCount,如果onStartCommand執行失敗的次數超過兩次,後面就不會為這個Intent重發(僅限START_REDELIVER_INTENT)
  • 通過scheduleServiceArgs回撥APP

之後通過scheduleServiceArgs回撥APP端,ActivityThread中相應處理如下:

private void handleServiceArgs(ServiceArgsData data) {
    Service s = mServices.get(data.token);
    if (s != null) {
        ...
            int res;
            // 如果沒有 taskRemoved,如果taskRemoved 則回撥onTaskRemoved
            if (!data.taskRemoved) {
            <!--普通的觸發onStartCommand-->
                res = s.onStartCommand(data.args, data.flags, data.startId);
            } else {
            <!--刪除最近任務回撥-->
                s.onTaskRemoved(data.args);
                res = Service.START_TASK_REMOVED_COMPLETE;
            }                
            try {
             <!-- 通知AMS處理完畢-->
                ActivityManagerNative.getDefault().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
            } ...
       }
複製程式碼

APP端觸發onStartCommand回撥後,會通知服務端Service啟動完畢,在服務端ActiveServices繼續執行serviceDoneExecuting,這裡也是Service恢復的一個關鍵點,onStartCommand的返回值在這裡真正被用,用來生成Service恢復的一個關鍵指標

void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
    boolean inDestroying = mDestroyingServices.contains(r);
    if (r != null) {
        if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
            // This is a call from a service start...  take care of
            // book-keeping.
            r.callStart = true;
            switch (res) {
            <!--對於 START_STICKY_COMPATIBILITY跟START_STICKY的Service,一定會被重啟 但是START_STICKY_COMPATIBILITY不一定回撥onStartCommand-->
                case Service.START_STICKY_COMPATIBILITY:
                case Service.START_STICKY: {
                <!--清理deliveredStarts-->
                    r.findDeliveredStart(startId, true);
                 <!--標記 被殺後需要重啟-->
                    r.stopIfKilled = false;
                    break;
                }
                case Service.START_NOT_STICKY: {
                    <!--清理-->
                    r.findDeliveredStart(startId, true);
                    <!--不需要重啟-->
                    if (r.getLastStartId() == startId) {
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_REDELIVER_INTENT: {
                 
                    ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
                    // 不過這個時候 r.stopIfKilled = true
                    if (si != null) {
                        si.deliveryCount = 0;
                        si.doneExecutingCount++;
                        // Don't stop if killed.  這個解釋有些奇葩 
                       <!--不需要立即重啟 START_REDELIVER_INTENT的時候,依靠的是deliveredStarts觸發重啟-->
                        r.stopIfKilled = true;
                    }
                    break;
                }
                ...
            }
            if (res == Service.START_STICKY_COMPATIBILITY) {
            <!--如果是Service.START_STICKY_COMPATIBILITY,會重啟,但是不會觸發onStartCommand,不同版本可能不同-->
                r.callStart = false;
            }
        } ...
}
複製程式碼

serviceDoneExecutingLocked主要做了以下兩件事

  • 對於不需要重新傳送Intent的Service,清理deliveredStarts
  • 對於需要立刻重啟的Service將其stopIfKilled設定為false

對於 Service.START_STICKY比較好理解,需要重啟,並且不傳送Intent,但是對於Service.START_REDELIVER_INTENT有些迷惑,這個也需要重啟,只是重啟的不是那麼迅速(後面會分析),不過Google這裡將其stopIfKilled設定為了true,其實Service.START_REDELIVER_INTENT型別的Service重啟依靠的不是這個標誌位,對比下兩種情況的ProcessRecord:

START_STICKY

Service.START_REDELIVER_INTENT

findDeliveredStart是用來清理deliveredStarts的,第二個引數如果是true,就說明需要清除,否則,就是保留,可以看到對於Service.START_REDELIVER_INTENT是保留的,其餘全部清除,這個就是START_REDELIVER_INTENT重啟的一個指標。

public StartItem findDeliveredStart(int id, boolean remove) {
    final int N = deliveredStarts.size();
    for (int i=0; i<N; i++) {
        StartItem si = deliveredStarts.get(i);
        if (si.id == id) {
            if (remove) deliveredStarts.remove(i);
            return si;
        }
    }
    return null;
}
複製程式碼

執行到這裡,Service啟動完畢,為重啟構建的資料也都準備好了,主要包括兩個

  • ProcessRecord的stopIfKilled欄位,如果是false,需要立即重啟
  • ProcessRecord 的deliveredStarts,如果非空,則需要重啟,並重發之前的Intent(重啟可能比較慢)

除了上面的情況,基本都不重啟,啟動分析完成,場景構建完畢,下面看看如何恢復的,假設APP被後臺殺死了,Service(以及程式)如何重啟的呢?

APP被殺後Service如何重啟

Binder有個訃告機制,Server死後,會向Client傳送一份通知,在這裡,其實就是APP死掉後,會像ActivityManagerService傳送一份訃告通知,AMS後面負責清理APP的場景,並看是否需要回復Service,進一步處理後續流程,ActivityManagerService會呼叫handleAppDiedLocked處理死去的程式:

<!--函式1-->
private final void handleAppDiedLocked(ProcessRecord app,
        boolean restarting, boolean allowRestart) {
    int pid = app.pid;
    boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
    ..,

<!--函式2-->    
private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
    boolean restarting, boolean allowRestart, int index) {
   ...
   mServices.killServicesLocked(app, allowRestart);
複製程式碼

進一步呼叫ActiveServcies的killServicesLocked,killServicesLocked負責清理已死程式的Service,如果有必要,還需要根據之前啟動時的設定重啟Service:

final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
	<!--先清理bindService,如果僅僅是bind先清理掉-->
   for (int i = app.connections.size() - 1; i >= 0; i--) {
        ConnectionRecord r = app.connections.valueAt(i);
        removeConnectionLocked(r, app, null);
    }
      ...     
    ServiceMap smap = getServiceMap(app.userId);
    <!--處理未正常stop的Service-->
    for (int i=app.services.size()-1; i>=0; i--) {
        ServiceRecord sr = app.services.valueAt(i);
		  ...
        <!--  超過兩次的要避免再次重啟Service,但是程式還是會被喚醒 如果是系統應用則無視,仍舊重啟-->
        if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
                &ApplicationInfo.FLAG_PERSISTENT) == 0) {
            bringDownServiceLocked(sr);
        } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) {
           <!--不準重啟的-->
            bringDownServiceLocked(sr);
        } else {
        		<!--準備重啟-->
            boolean canceled = scheduleServiceRestartLocked(sr, true);
            <!--看看是否終止一些極端的情況-->
            // Should the service remain running?  Note that in the
            // extreme case of so many attempts to deliver a command
            // that it failed we also will stop it here.
            <!-重啟次數過多的話canceled=true(主要針對重發intent的)-->
            if (sr.startRequested && (sr.stopIfKilled || canceled)) {
                if (sr.pendingStarts.size() == 0) {
                    sr.startRequested = false;...
                    if (!sr.hasAutoCreateConnections()) {
                        bringDownServiceLocked(sr);
             } }  }
        }
    }
複製程式碼

這裡有些限制,比如重啟兩次都失敗,那就不再重啟Service,但是系統APP不受限制,bindService的那種先不考慮,其他的為被正常stop的都會呼叫scheduleServiceRestartLocked進行重啟登記,不過對於像START_NOT_STICKY這種,登記會再次被取消,sr.stopIfKilled就是在這裡被用到。先看下 scheduleServiceRestartLocked,它的返回值也會影響是否需要重啟:

private final boolean scheduleServiceRestartLocked(ServiceRecord r,
        boolean allowCancel) {
    boolean canceled = false;

    ServiceMap smap = getServiceMap(r.userId);
    if (smap.mServicesByName.get(r.name) != r) {
        ServiceRecord cur = smap.mServicesByName.get(r.name);
        Slog.wtf(TAG, "Attempting to schedule restart of " + r
                + " when found in map: " + cur);
        return false;
    }

    final long now = SystemClock.uptimeMillis();

    if ((r.serviceInfo.applicationInfo.flags
            &ApplicationInfo.FLAG_PERSISTENT) == 0) {
        long minDuration = SERVICE_RESTART_DURATION;
        long resetTime = SERVICE_RESET_RUN_DURATION;

        // Any delivered but not yet finished starts should be put back
        // on the pending list.
        // 在clean的時候會處理
        // 這裡僅僅是要處理的需要deliveredStarts intent
        // remove task的被清理嗎
        final int N = r.deliveredStarts.size();
        // deliveredStarts的耗時需要重新計算
        if (N > 0) {
            for (int i=N-1; i>=0; i--) {
                ServiceRecord.StartItem si = r.deliveredStarts.get(i);
                si.removeUriPermissionsLocked();
                if (si.intent == null) {
                    // We'll generate this again if needed.
                } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT
                        && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) {
                    // 重啟的時候
                    // 重啟的時候,deliveredStarts被pendingStarts替換掉了
                    // 也就說,這個時候由死轉到活
                    r.pendingStarts.add(0, si);
                    // 當前時間距離上次的deliveredTime,一般耗時比較長
                    long dur = SystemClock.uptimeMillis() - si.deliveredTime;
                    dur *= 2;
                    if (minDuration < dur) minDuration = dur;
                    if (resetTime < dur) resetTime = dur;
                } else {
                    canceled = true;
                }
            }
            r.deliveredStarts.clear();
        }
       r.totalRestartCount++;
        // r.restartDelay第一次重啟
        if (r.restartDelay == 0) {
            r.restartCount++;
            r.restartDelay = minDuration;
        } else {
            // If it has been a "reasonably long time" since the service
            // was started, then reset our restart duration back to
            // the beginning, so we don't infinitely increase the duration
            // on a service that just occasionally gets killed (which is
            // a normal case, due to process being killed to reclaim memory).
            <!--如果被殺後,執行時間較短又被殺了,那麼增加重啟延時,否則重置為minDuration,(比如記憶體不足,經常重殺,那麼不能無限重啟,增大延時)-->
            if (now > (r.restartTime+resetTime)) {
                r.restartCount = 1;
                r.restartDelay = minDuration;
            } else {
                r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
                if (r.restartDelay < minDuration) {
                    r.restartDelay = minDuration;
                }
            }
        }
       <!--計算下次重啟的時間-->
        r.nextRestartTime = now + r.restartDelay;
        <!--兩個Service啟動至少間隔10秒,這裡的意義其實不是很大,主要是為了Service啟動失敗的情況,如果啟動成功,其他要啟動的Service會被一併直接重新喚起,-->
        boolean repeat;
        do {
            repeat = false;
            for (int i=mRestartingServices.size()-1; i>=0; i--) {
                ServiceRecord r2 = mRestartingServices.get(i);
                if (r2 != r && r.nextRestartTime
                        >= (r2.nextRestartTime-SERVICE_MIN_RESTART_TIME_BETWEEN)
                        && r.nextRestartTime
                        < (r2.nextRestartTime+SERVICE_MIN_RESTART_TIME_BETWEEN)) {
                    r.nextRestartTime = r2.nextRestartTime + SERVICE_MIN_RESTART_TIME_BETWEEN;
                    r.restartDelay = r.nextRestartTime - now;
                    repeat = true;
                    break;
                }
            }
        } while (repeat);
    } else {
    <!--系統服務,即可重啟-->
        // Persistent processes are immediately restarted, so there is no
        // reason to hold of on restarting their services.
        r.totalRestartCount++;
        r.restartCount = 0;
        r.restartDelay = 0;
        r.nextRestartTime = now;
    }
    
     if (!mRestartingServices.contains(r)) {
        <!--新增一個Service-->
        mRestartingServices.add(r);
        ...
    }
    
    mAm.mHandler.removeCallbacks(r.restarter);
    // postAtTime
    mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
    <!--校準一下真實的nextRestartTime,dump時候可以看到-->
    r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
    ...
    return canceled;
}
複製程式碼

scheduleServiceRestartLocked主要作用是計算重啟延時,併傳送重啟的訊息到Handler對應的MessageQueue,對於需要傳送Intent的Service,他們之前的Intent被暫存在delivered, 在恢復階段,原來的deliveredStarts會被清理,轉換到pendingStart列表中,後面重新啟動時候會根據pendingStart重發Intent給Service,呼叫其onStartCommand。不過對於這種Service,其啟動恢復的時間跟其執行時間有關係,距離startService時間越長,其需要恢復的延時時間就越多,後面會單獨解釋。

341534476234_.pic_hd.jpg

另外,如果Service重啟的時間間隔過短,則說明被殺的太迅速,很可能是系統資源不足,這個時候就逐步拉大重啟時間。其次,什麼時候Cancle重新啟動呢?只有deliveredStarts非空(START_DELIVER_INTENT),並且回撥onStartCommand失敗的次數>=2,或者成功的次數>=6的時候,比如:對於START_DELIVER_INTENT,如果被殺超過6次,AMS會清理該Service,不會再重啟了。另外如果重啟的Service可有很多個,為了避免重啟時間太接近,多個Service預置的重啟間隔最少是10S,不過,並不是說Service真的需要間隔10s才能重啟,而是說,如果前一個Service重啟失敗或者太慢,要至少10s後才重啟下一個,如果第一個Service就重啟成功,同時程式也啟動成功,那麼所有的Service都會被立刻喚起,而不需要等到真正的10秒延時間隔。

321534474231_.pic_hd.jpg

331534474536_.pic_hd.jpg

可以看到,雖然pendingStart中Service重啟的間隔是至少相隔10秒,但是一個Service啟動成功後,所有的Service都被喚起了,雖然還沒有到之前預置的啟動時機。這是為什麼?因為,如果在程式未啟動的時候啟動Service,那麼需要先啟動程式,之後attach Application ,在attatch的時候,除了啟動自己Service,還要將其餘等待喚醒的Service一併喚起,原始碼如下:

boolean attachApplicationLocked(ProcessRecord proc, String processName)
        throws RemoteException {
    boolean didSomething = false;
    ...
    // 只要是一個起來了,就立刻重新啟動所有Service,程式已經活了,就無須等待
    if (mRestartingServices.size() > 0) {
        ServiceRecord sr = null;
        for (int i=0; i<mRestartingServices.size(); i++) {
            sr = mRestartingServices.get(i);
            if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
                    || !processName.equals(sr.processName))) {
                continue;
            }
            <!--清除舊的,-->
            mAm.mHandler.removeCallbacks(sr.restarter);
            <!--新增新的-->
            mAm.mHandler.post(sr.restarter);
        }
    }
    return didSomething;
}
複製程式碼

可以看到,attachApplicationLocked的時候,會將之前舊的含有10秒延遲間隔的restarter清理掉,並重新新增無需延時的重啟命令,這樣那些需要重啟的Service就不用等到之前設定的延時就可以重新啟動了。還有什麼情況,需要考慮呢,看下面的:

        <!-重啟次數過多的話canceled=true(主要針對重發intent的)-->
            if (sr.startRequested && (sr.stopIfKilled || canceled)) {
                if (sr.pendingStarts.size() == 0) {
                    sr.startRequested = false;...
                    if (!sr.hasAutoCreateConnections()) {
                        bringDownServiceLocked(sr);
             } }  }
        }
複製程式碼
  • 對於START_STICKY,scheduleServiceRestartLocked返回值一定是false,delay的時間是1S,並且由於其stopIfKilled是false,所以一定會被快速重啟,不會走bringDownServiceLocked流程
  • 對於STAR_NO_STICKY,scheduleServiceRestartLocked返回值是flase,但是stopIfKilled是true,另外其pendingStarts列表為空,如果沒有被其他存活的Activity繫結,那麼需要走bringDownServiceLocked流程,也就是說,不會被重啟。

處理完上述邏輯後,ServiceRestarter就會被插入到MessegeQueue等待執行,之後呼叫performServiceRestartLocked-> bringUpServiceLocked-> realStartServiceLocked進一步處理Service的重啟。

private class ServiceRestarter implements Runnable {
    private ServiceRecord mService;

    void setService(ServiceRecord service) {
        mService = service;
    }

    public void run() {
        synchronized(mAm) {
            performServiceRestartLocked(mService);
        }
    }
}

final void performServiceRestartLocked(ServiceRecord r) {
// 如果被置空了,也是不用重啟
if (!mRestartingServices.contains(r)) {
    return;
}
try {
    bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
} catch (TransactionTooLargeException e) {
    // Ignore, it's been logged and nothing upstack cares.
}
複製程式碼

}

之前第一次啟動的時候看過了,這裡再來看複習一下,主要看不同的點,其實主要是針對START_STICKY的處理:

  private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
       ...
    <!--恢復:這裡應該主要是給start_sticky用的,恢復的時候觸發呼叫onStartCommand-->
    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null));
    }
    <!--處理onstartComand-->
    sendServiceArgsLocked(r, execInFg, true);
    ... 
複製程式碼

對於START_STICKY需要重啟,之前說過了,但是怎麼標記需要重新呼叫onStartCommand呢?上面的realStartServiceLocked會主動新增一個ServiceRecord.StartItem到pendingStarts,因為這個時候,對於START_STICKY滿足如下條件。

 r.startRequested && r.callStart && r.pendingStarts.size() == 0
複製程式碼

不過,這個Item沒有Intent,也就說,回撥onStartCommand的時候,沒有Intent傳遞給APP端,接下來的sendServiceArgsLocked跟之前的邏輯沒太大區別,不再分析,下面再說下為什麼START_REDELIVER_INTENT比較耗時。

被殺重啟時候,為什麼 START_REDELIVER_INTENT通常比START_STICK延時更多

之前說過,在onStartCommand返回值是START_REDELIVER_INTENT的時候,其重啟恢復的延時時間跟Service的啟動時間有關係。具體演算法是:從start到now的時間*2,距離啟動時間越長,restart的延時越多。

 private final boolean scheduleServiceRestartLocked(ServiceRecord r,
            boolean allowCancel) {
        boolean canceled = false;
	...
	final int N = r.deliveredStarts.size();
        // deliveredStarts的耗時需要重新計算
        if (N > 0) {
        ...
			if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT
	                            && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) {
	                        r.pendingStarts.add(0, si);
	                        // 當前時間距離上次的deliveredTime,一般耗時比較長
	                        long dur = SystemClock.uptimeMillis() - si.deliveredTime;
	                        dur *= 2;
	                        if (minDuration < dur) minDuration = dur;
	                        if (resetTime < dur) resetTime = dur;
	                    }
複製程式碼

如果設定了START_REDELIVER_INTENT,這裡的deliveredStarts就一定非空,因為它持有startService的Intent列表,在這種情況下,重啟延時是需要重新計算的,一般是是2*(距離上次sendServiceArgsLocked的時間(比如由startService觸發))

long dur = SystemClock.uptimeMillis() - si.deliveredTime;
複製程式碼

比如距離上次startService的是3分,那麼它就在6分鐘後重啟,如果是1小時,那麼它就在一小時後啟動,

啟動後執行時間

再次啟動延時

而對於START_STICK,它的啟動延時基本上是系統設定的Service最小重啟延時單位,一般是一秒:

static final int SERVICE_RESTART_DURATION = 1*1000;
複製程式碼

START_STICK重啟延時

所以如果你需要快速重啟Service,那麼就使用START_STICK,不過START_STICK不會傳遞之前Intent資訊,上面分析都是假設程式被意外殺死,那麼使用者主動從最近的任務列表刪除的時候,也會重啟,有什麼不同嗎?

從最近任務列表刪除,如何處理Service的重啟

左滑刪除有時候會導致程式被殺死,這個時候,未被stop的Service也是可能需要重新啟動的,這個時候跟之前的有什麼不同嗎?在這種情況下Service的onTaskRemoved會被回撥。

@Override
public void onTaskRemoved(Intent rootIntent) {
    super.onTaskRemoved(rootIntent);
}
複製程式碼

左滑刪除TASK會呼叫AMS的cleanUpRemovedTaskLocked,這個函式會先處理Service的,並回撥其onTaskRemoved,之後殺程式,殺程式之後的邏輯同樣走binder訃告機制,跟之前的恢復沒什麼區別,這裡主要看看onTaskRemoved,如果不需要重啟,可以在這裡做下處理:

private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) {
    ...
    // Find any running services associated with this app and stop if needed.
    <!--先處理Service,如果有必要清理Service-->
    mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));

    if (!killProcess) {
        return;
    }

    <!--找到跟改Task相關的程式,並決定是否需要kill-->
    ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
    ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap();
    for (int i = 0; i < pmap.size(); i++) {
       SparseArray<ProcessRecord> uids = pmap.valueAt(i);
        for (int j = 0; j < uids.size(); j++) {
            ProcessRecord proc = uids.valueAt(j);
            ...<!--滿足條件的等待被殺 (不是Home,)-->
            procsToKill.add(proc);
        }
    }

   // 如果可以即可殺,就立刻殺,否則等待下一次評估oomadj的時候殺,不過,總歸是要殺的 
    for (int i = 0; i < procsToKill.size(); i++) {
        ProcessRecord pr = procsToKill.get(i);
        if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                && pr.curReceiver == null) {
            pr.kill("remove task", true);
        } else {
            pr.waitingToKill = "remove task";
        }
    }
}
複製程式碼

ActiveServices的cleanUpRemovedTaskLocked

void cleanUpRemovedTaskLocked(TaskRecord tr, ComponentName component, Intent baseIntent) {

    ArrayList<ServiceRecord> services = new ArrayList<>();
    ArrayMap<ComponentName, ServiceRecord> alls = getServices(tr.userId);
    for (int i = alls.size() - 1; i >= 0; i--) {
        ServiceRecord sr = alls.valueAt(i);
        if (sr.packageName.equals(component.getPackageName())) {
            services.add(sr);
        }
    }
    // Take care of any running services associated with the app.
    for (int i = services.size() - 1; i >= 0; i--) {
        ServiceRecord sr = services.get(i);
   	 // 如果是通過startRequested啟動
        if (sr.startRequested) {
            if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
                stopServiceLocked(sr);
            } else {
            <!--作為remove的一部分,這裡pendingStarts的add主要是為了回到onStartCommand,而且這個時候,程式還沒死呢,否則通知個屁啊-->
                sr.pendingStarts.add(new ServiceRecord.StartItem(sr,  taskremover =true,
                        sr.makeNextStartId(), baseIntent, null));
                if (sr.app != null && sr.app.thread != null) {
                    try {
                        sendServiceArgsLocked(sr, true, false);
                    } ...
                }
複製程式碼

其實從最近任務列表刪除最近任務的時候,處理很簡單,如果Service設定了ServiceInfo.FLAG_STOP_WITH_TASK,那麼左滑刪除後,Service不用重啟,也不會處理 onTaskRemoved,直接幹掉,否則,是需要往pendingStarts填充ServiceRecord.StartItem,這樣在sendServiceArgsLocked才能傳送onTaskRemoved請求,為了跟啟動onStartCommand分開,ServiceRecord.StartItem的taskremover被設定成true,這樣在回撥ActiviyThread的handleServiceArgs就會走onTaskRemoved分支如下:

private void handleServiceArgs(ServiceArgsData data) {
    Service s = mServices.get(data.token);
    if (s != null) {
        try {
            if (data.args != null) {
                data.args.setExtrasClassLoader(s.getClassLoader());
                data.args.prepareToEnterProcess();
            }
            int res;
            // 如果沒有 taskRemoved,如果taskRemoved 則回撥onTaskRemoved
            if (!data.taskRemoved) {
                res = s.onStartCommand(data.args, data.flags, data.startId);
            } else {
                s.onTaskRemoved(data.args);
                res = Service.START_TASK_REMOVED_COMPLETE;
            }

			....
複製程式碼

因此,從最近任務列表刪除,可以看做是僅僅多了個一個onTaskRemoved在這個會調中,使用者可以自己處理一些事情,比如中斷一些Service處理的事情,儲存現場等。

總結

  • 通過startService啟動,但是卻沒有通過stopService結束的Service並不一定觸發重新啟動,需要設定相應的onStartCommand返回值,比如START_REDELIVER_INTENT、比START_STICK
  • START_REDELIVER_INTENT並不是重發最後一個Intent,看原始碼是所有Intent
  • START_REDELIVER_INTENT同START_STICK重啟的延時不一樣,START_STICK一般固定1s,而START_REDELIVER_INTENT較長,基本是距離startService的2倍。
  • 可以用來做包活,但是不推薦,而且國內也不怎麼好用(MIUI、華為等都對AMS做了定製,限制較多)

作者:看書的小蝸牛 Android Service重啟恢復(Service程式重啟)原理解析

僅供參考,歡迎指正

相關文章