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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
简介: Android Service重启恢复(Service进程重启)原理解析(二)

APP被杀后Service如何重启


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

<!--函数1-->
private final void handleAppDiedLocked(ProcessRecord app,
        boolean restarting, boolean allowRestart) {
    int pid = app.pid;
    boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
    ..,
<!--函数2-->    
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) {
    <!--先清理bindService,如果仅仅是bind先清理掉-->
   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);
    <!--处理未正常stop的Service-->
    for (int i=app.services.size()-1; i>=0; i--) {
        ServiceRecord sr = app.services.valueAt(i);
          ...
        <!--  超过两次的要避免再次重启Service,但是进程还是会被唤醒 如果是系统应用则无视,仍旧重启-->
        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.
            <!-重启次数过多的话canceled=true(主要针对重发intent的)-->
            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).
            <!--如果被杀后,运行时间较短又被杀了,那么增加重启延时,否则重置为minDuration,(比如内存不足,经常重杀,那么不能无限重启,增大延时)-->
            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;
        <!--两个Service启动至少间隔10秒,这里的意义其实不是很大,主要是为了Service启动失败的情况,如果启动成功,其他要启动的Service会被一并直接重新唤起,-->
        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)) {
        <!--添加一个Service-->
        mRestartingServices.add(r);
        ...
    }
    mAm.mHandler.removeCallbacks(r.restarter);
    // postAtTime
    mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
    <!--校准一下真实的nextRestartTime,dump时候可以看到-->
    r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
    ...
    return canceled;
}

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


image.png

另外,如果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秒延时间隔。


image.png

image.png

可以看到,虽然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<mRestartingServices.size(); i++) {
            sr = mRestartingServices.get(i);
            if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
                    || !processName.equals(sr.processName))) {
                continue;
            }
            <!--清除旧的,-->
            mAm.mHandler.removeCallbacks(sr.restarter);
            <!--添加新的-->
            mAm.mHandler.post(sr.restarter);
        }
    }
    return didSomething;
}

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


<!-重启次数过多的话canceled=true(主要针对重发intent的)-->
            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 {
       ...
    <!--恢复:这里应该主要是给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);
    ...

对于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分钟后重启,如果是半小时,那么它就在一小时后启动,


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

static final int SERVICE_RESTART_DURATION = 1*1000;


image.png

所以如果你需要快速重启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.
    <!--先处理Service,如果有必要清理Service-->
    mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));
    if (!killProcess) {
        return;
    }
    <!--找到跟改Task相关的进程,并决定是否需要kill-->
    ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
    ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap();
    for (int i = 0; i < pmap.size(); i++) {
       SparseArray<ProcessRecord> uids = pmap.valueAt(i);
        for (int j = 0; j < uids.size(); j++) {
            ProcessRecord proc = uids.valueAt(j);
            ...<!--满足条件的等待被杀 (不是Home,)-->
            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<ServiceRecord> services = new ArrayList<>();
    ArrayMap<ComponentName, ServiceRecord> 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 {
            <!--作为remove的一部分,这里pendingStarts的add主要是为了回到onStartCommand,而且这个时候,进程还没死呢,否则通知个屁啊-->
                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做了定制,限制较多)


目录
相关文章
|
1月前
|
XML 存储 JSON
51. 【Android教程】JSON 数据解析
51. 【Android教程】JSON 数据解析
32 2
|
1月前
|
调度 Android开发
43. 【Android教程】服务:Service
43. 【Android教程】服务:Service
22 2
|
1天前
|
Java 调度 Android开发
深入解析Android应用开发中的响应式编程与RxJava应用
在现代Android应用开发中,响应式编程及其核心框架RxJava正逐渐成为开发者的首选。本文将深入探讨响应式编程的基本概念、RxJava的核心特性以及如何在Android应用中利用RxJava提升代码的可读性和性能。 【7月更文挑战第7天】
|
11天前
|
安全 网络协议 算法
Android网络基础面试题之HTTPS的工作流程和原理
HTTPS简述 HTTPS基于TCP 443端口,通过CA证书确保服务器身份,使用DH算法协商对称密钥进行加密通信。流程包括TCP握手、证书验证(公钥解密,哈希对比)和数据加密传输(随机数加密,预主密钥,对称加密)。特点是安全但慢,易受特定攻击,且依赖可信的CA。每次请求可能复用Session ID以减少握手。
18 2
|
16天前
|
前端开发 JavaScript 测试技术
安卓应用开发中的架构模式解析
【6月更文挑战第21天】在软件开发领域,架构模式是设计优雅、高效、可维护应用程序的基石。本文深入探讨了安卓应用开发中常见的架构模式,包括MVC、MVP、MVVM和Clean Architecture,旨在为开发者提供一个清晰的指导,帮助他们选择最适合自己项目的架构风格。通过对比分析这些架构模式的特点、优势以及适用场景,文章揭示了如何根据项目需求和团队能力来采用恰当的架构模式,以实现代码的可维护性、可扩展性和可测试性。
29 7
|
16天前
|
Java 开发工具 Android开发
安卓与iOS开发差异解析
【6月更文挑战第21天】本文旨在深入探讨安卓和iOS两大移动操作系统在应用开发过程中的主要差异。通过对比分析,揭示各自的设计哲学、编程语言选择、用户界面构建、性能优化策略以及发布流程的异同。文章将提供开发者视角下的实用信息,帮助他们更好地理解各自平台的特点和挑战,从而做出更明智的开发决策。
|
24天前
|
算法 Linux 调度
Linux进程——进程的创建(fork的原理)
Linux进程——进程的创建(fork的原理)
15 2
|
27天前
|
搜索推荐 Android开发 iOS开发
深入解析安卓与iOS操作系统的异同
安卓和iOS是目前两大主流移动操作系统,它们分别代表了开放性和封闭性的设计理念。本文将从系统架构、用户界面、应用生态等方面深入探讨安卓和iOS之间的异同,帮助读者更好地理解这两个操作系统的特点和优势。
42 3
|
11天前
|
Unix Shell Perl
技术心得:实例解析shell子进程(subshell)
技术心得:实例解析shell子进程(subshell)
|
11天前
|
Android开发 开发者
“List of Devices Attached“:Android设备连接问题解析
“List of Devices Attached“:Android设备连接问题解析

推荐镜像

更多