Android Service重启恢复(Service进程重启)原理解析(一)

简介: 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信息:

image.png

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

image.png

从测试来看,所有的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:

image.png

image.png

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(以及进程)如何重启的呢?


目录
相关文章
|
28天前
|
关系型数据库 MySQL Shell
CMake构建Makefile深度解析:从底层原理到复杂项目(三)
CMake构建Makefile深度解析:从底层原理到复杂项目
30 0
|
28天前
|
编译器 vr&ar C++
CMake构建Makefile深度解析:从底层原理到复杂项目(二)
CMake构建Makefile深度解析:从底层原理到复杂项目
33 0
|
24天前
|
存储 安全 编译器
【C++ 17 新功能 std::visit 】深入解析 C++17 中的 std::visit:从原理到实践
【C++ 17 新功能 std::visit 】深入解析 C++17 中的 std::visit:从原理到实践
69 0
|
20天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
43 1
|
1天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
12 0
|
6天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
15 3
|
16天前
|
安全 Linux API
Android进程与线程
Android进程与线程
18 0
|
23天前
|
存储 并行计算 算法
C++动态规划的全面解析:从原理到实践
C++动态规划的全面解析:从原理到实践
89 0
|
23天前
|
监控 算法 Unix
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
55 0
|
28天前
|
存储 Linux API
解析音频输出调节音量的原理以及调节的方法
解析音频输出调节音量的原理以及调节的方法
33 0

推荐镜像

更多