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

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


目录
相关文章
|
2月前
|
IDE Android开发 iOS开发
深入解析Android与iOS的系统架构及开发环境差异
本文旨在探讨Android和iOS两大主流移动操作系统在系统架构、开发环境和用户体验方面的显著差异。通过对比分析,我们将揭示这两种系统在设计理念、技术实现以及市场策略上的不同路径,帮助开发者更好地理解其特点,从而做出更合适的开发决策。
125 2
|
2月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
132 8
|
3天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
18 8
|
21天前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
29 3
|
29天前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
33 6
|
2月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
133 5
|
2月前
|
缓存 Android开发 开发者
Android RecycleView 深度解析与面试题梳理
本文详细介绍了Android开发中高效且功能强大的`RecyclerView`,包括其架构概览、工作流程及滑动优化机制,并解析了常见的面试题。通过理解`RecyclerView`的核心组件及其优化技巧,帮助开发者提升应用性能并应对技术面试。
65 8
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
63 8
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
39 2
|
22天前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
18 0

推荐镜像

更多