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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 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(以及进程)如何重启的呢?


目录
相关文章
|
15天前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
52 7
|
15天前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术特性与用户体验的深度解析
在移动操作系统的战场上,安卓和iOS一直占据着主导地位。本文将深入探讨这两大平台的核心技术特性,以及它们如何影响用户的体验。我们将从系统架构、应用生态、安全性能和创新功能四个方面进行比较,帮助读者更好地理解这两个系统的异同。
45 3
|
11天前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
47 12
|
9天前
|
存储 监控
【Azure Cloud Service】在Azure云服务中收集CPU监控指标和IIS进程的DUMP方法
在使用Cloud Service服务时,发现服务的CPU占用很高,在业务请求并不大的情况下,需要直到到底是什么进程占用了大量的CPU资源,已经如何获取IIS进程(w3wp.exe)的DUMP文件?
|
7天前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
60 4
|
1天前
|
搜索推荐 Linux Android开发
深入解析安卓与iOS系统架构设计差异
本文旨在探讨Android和iOS两大主流操作系统在架构设计上的根本差异。通过分析两种系统的设计理念、核心组件以及实际应用表现,揭示它们如何反映不同的开发哲学和用户体验策略。我们将从系统层级结构、内存管理机制、用户界面设计三个方面入手,逐一对比Android的开放性和灵活性如何与其对手iOS的封闭性和一致性相互辉映。
|
6天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
17 0
|
19天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
80 0
|
20天前
|
开发者 算法 虚拟化
惊爆!Uno Platform 调试与性能分析终极攻略,从工具运用到代码优化,带你攻克开发难题成就完美应用
【8月更文挑战第31天】在 Uno Platform 中,调试可通过 Visual Studio 设置断点和逐步执行代码实现,同时浏览器开发者工具有助于 Web 版本调试。性能分析则利用 Visual Studio 的性能分析器检查 CPU 和内存使用情况,还可通过记录时间戳进行简单分析。优化性能涉及代码逻辑优化、资源管理和用户界面简化,综合利用平台提供的工具和技术,确保应用高效稳定运行。
31 0
|
20天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
全面解析TensorFlow Lite:从模型转换到Android应用集成,教你如何在移动设备上轻松部署轻量级机器学习模型,实现高效本地推理
【8月更文挑战第31天】本文通过技术综述介绍了如何使用TensorFlow Lite将机器学习模型部署至移动设备。从创建、训练模型开始,详细演示了模型向TensorFlow Lite格式的转换过程,并指导如何在Android应用中集成该模型以实现预测功能,突显了TensorFlow Lite在资源受限环境中的优势及灵活性。
49 0

热门文章

最新文章