Android Service重啓恢復(Service進程重啓)原理解析

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經常使用的幾個返回值的解釋(不徹底正確):app

  • 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).ide

  • 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).post

簡單說就是:進程被殺後,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,以下圖:ui

AMS不存儲Intent

從測試來看,全部的Intent都會被重發,而不只僅是最後一個。爲何設置了某些選項就會重啓,甚至會從新發送以前Intent呢?本文就來分析下原理,先簡單跟蹤下啓動,由於恢復所須要的全部信息都是在啓動的時候構建好的,以後再分析恢復。(基於Android6.0)this

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

爲了簡化流程,咱們假設Service所在的進程已經啓動,代碼咱們直接從AMS調用ActiveService 的startServiceLocked開始,主要看看啓動的時候是如何爲恢復作準備的spa

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進程重啓)原理解析

僅供參考,歡迎指正

相關文章
相關標籤/搜索