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常用的几个返回值的解释(不完全正确):

  • 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信息:

Android Service重启恢复(Service进程重启)原理解析_第1张图片
AMS存储所有杀死后需要重发的Intent

而START_NOT_STICKY跟START_STICKY都不需要AMS存储Intent,如下图:

Android Service重启恢复(Service进程重启)原理解析_第2张图片
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 {
         
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg);
        ..
        ServiceRecord r = res.record;                ..
        
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                service, neededGrants));
         ...
         
    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    }

启动Service的时候,AMS端先为其构建一个ServiceRecord,算是Service在AMS端的映像,然后添加一个ServiceRecord.StartItem到pendingStarts列表,这个是回调onStartCommand的依据,之后调用startServiceInnerLocked 再调用bringUpServiceLocked进一步启动Service:

   
   ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    
    r.callStart = false;
    ...
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
    ...
     
             
    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.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.
    
    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null));
    }
    
    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);
            
            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;
            }
            
            si.deliveredTime = SystemClock.uptimeMillis();
            
            r.deliveredStarts.add(si);
            
            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) {
            
                res = s.onStartCommand(data.args, data.flags, data.startId);
            } else {
            
                s.onTaskRemoved(data.args);
                res = Service.START_TASK_REMOVED_COMPLETE;
            }                
            try {
             
                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) {
            
                case Service.START_STICKY_COMPATIBILITY:
                case Service.START_STICKY: {
                
                    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.  这个解释有些奇葩 
                       
                        r.stopIfKilled = true;
                    }
                    break;
                }
                ...
            }
            if (res == Service.START_STICKY_COMPATIBILITY) {
            
                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:

Android Service重启恢复(Service进程重启)原理解析_第3张图片
START_STICKY
Android Service重启恢复(Service进程重启)原理解析_第4张图片
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

执行到这里,Service启动完毕,为重启构建的数据也都准备好了,主要包括两个

  • ProcessRecord的stopIfKilled字段,如果是false,需要立即重启
  • ProcessRecord 的deliveredStarts,如果非空,则需要重启,并重发之前的Intent(重启可能比较慢)

除了上面的情况,基本都不重启,启动分析完成,场景构建完毕,下面看看如何恢复的,假设APP被后台杀死了,Service(以及进程)如何重启的呢?

APP被杀后Service如何重启

Binder有个讣告机制,Server死后,会向Client发送一份通知,在这里,其实就是APP死掉后,会像ActivityManagerService发送一份讣告通知,AMS后面负责清理APP的场景,并看是否需要回复Service,进一步处理后续流程,ActivityManagerService会调用handleAppDiedLocked处理死去的进程:


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

    
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) {
    
   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);
    
    for (int i=app.services.size()-1; i>=0; i--) {
        ServiceRecord sr = app.services.valueAt(i);
          ...
        
        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.
            
            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).
            
            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;
        
        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)) {
        
        mRestartingServices.add(r);
        ...
    }
    
    mAm.mHandler.removeCallbacks(r.restarter);
    // postAtTime
    mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
    
    r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
    ...
    return canceled;
}

scheduleServiceRestartLocked主要作用是计算重启延时,并发送重启的消息到Handler对应的MessageQueue,对于需要发送Intent的Service,他们之前的Intent被暂存在delivered, 在恢复阶段,原来的deliveredStarts会被清理,转换到pendingStart列表中,后面重新启动时候会根据pendingStart重发Intent给Service,调用其onStartCommand。不过对于这种Service,其启动恢复的时间跟其运行时间有关系,距离startService时间越长,其需要恢复的延时时间就越多,后面会单独解释。

Android Service重启恢复(Service进程重启)原理解析_第5张图片
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秒延时间隔。

Android Service重启恢复(Service进程重启)原理解析_第6张图片
321534474231_.pic_hd.jpg
Android Service重启恢复(Service进程重启)原理解析_第7张图片
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
            mAm.mHandler.removeCallbacks(sr.restarter);
            
            mAm.mHandler.post(sr.restarter);
        }
    }
    return didSomething;
}

可以看到,attachApplicationLocked的时候,会将之前旧的含有10秒延迟间隔的restarter清理掉,并重新添加无需延时的重启命令,这样那些需要重启的Service就不用等到之前设定的延时就可以重新启动了。还有什么情况,需要考虑呢,看下面的:

        
            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 {
       ...
    
    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null));
    }
    
    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分钟后重启,如果是半小时,那么它就在一小时后启动,

Android Service重启恢复(Service进程重启)原理解析_第8张图片
启动后运行时间
Android Service重启恢复(Service进程重启)原理解析_第9张图片
再次启动延时

而对于START_STICK,它的启动延时基本上是系统设置的Service最小重启延时单位,一般是一秒:

static final int SERVICE_RESTART_DURATION = 1*1000;
Android Service重启恢复(Service进程重启)原理解析_第10张图片
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.
    
    mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));

    if (!killProcess) {
        return;
    }

    
    ArrayList procsToKill = new ArrayList<>();
    ArrayMap> pmap = mProcessNames.getMap();
    for (int i = 0; i < pmap.size(); i++) {
       SparseArray uids = pmap.valueAt(i);
        for (int j = 0; j < uids.size(); j++) {
            ProcessRecord proc = uids.valueAt(j);
            ...
            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 services = new ArrayList<>();
    ArrayMap 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 {
            
                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处理的事情,保存现场等。

Android O 之后的变更

似乎Android O之后START_STICK的Service会重新唤起进程,但是onStartCommand不会被调用,这样似乎更合情理,不是startService启动的Service不会调用onStartCommand。

总结

  • 通过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进程重启)原理解析

仅供参考,欢迎指正

你可能感兴趣的:(Android Service重启恢复(Service进程重启)原理解析)