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


目录
相关文章
|
4月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
211 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
11月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
7月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
199 15
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
10月前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
10月前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
375 4
|
10月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
11月前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
11月前
|
安全 Java Linux
深入解析Android系统架构及其对开发者的意义####
【10月更文挑战第21天】 本文旨在为读者揭开Android操作系统架构的神秘面纱,探讨其如何塑造现代移动应用开发格局。通过剖析Linux内核、硬件抽象层、运行时环境及应用程序框架等关键组件,揭示Android平台的强大功能与灵活性。文章强调了理解Android架构对于开发者优化应用性能、提升用户体验的重要性,并展望了未来技术趋势下Android的发展方向。 ####
359 0
|
算法 Android开发
Android Service重启恢复(Service进程重启)原理解析(二)
Android Service重启恢复(Service进程重启)原理解析(二)
1951 0
Android Service重启恢复(Service进程重启)原理解析(二)

热门文章

最新文章

推荐镜像

更多