In Android system, after the APP process is killed, it is often found that the process starts again. This phenomenon is mainly related to the use of Service in APP. In this article, the Service refers to the startService, not through binderSertvice. If the binderSertvice and binderSertvice are in the same process, the impact of the binderSertvice can be ignored. If they are not in the same process, the Service will be restarted. After all, the Service is no longer needed. However, for a service started by startService, it is likely that it will continue to work on its own issues and, therefore, may need to be restarted.

If you want a Service to wake up after the process ends, you may need to set the return value of onStartCommand to START_REDELIVER_INTENT or START_STICKY. So the Service can be woken up after being killed, so why?

@Override public int onStartCommand(Intent intent, int flags, Int startId) {return START_REDELIVER_INTENT (or START_STICKY, START_STICKY_COMPATIBILITY); }Copy the code

The onStartCommand of a Service returns several values (not entirely correct) :

  • 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).

To put it simply: After a process is killed, START_NOT_STICKY will not evoke the Service again. OnStartCommand will be invoked only when startService is called again. Both START_REDELIVER_INTENT and START_STICKY restart the Service, and START_REDELIVER_INTENT passes the last Intent to onStartCommand. Instead of just sending the last Intent, START_REDELIVER_INTENT resends all previous intents from startService to onStartCommand. All intents that store all start_redeliver_intents in AMS:

Both START_NOT_STICKY and START_STICKY do not require AMS to store intEnts.

In testing, all intents are reissued, not just the last one. Why does setting certain options restart the Intent, or even resend the Intent? This article will analyze the principle, first briefly trace startup, because all the information needed for recovery is built at startup time, and then analyze recovery. (based on Android6.0)

Service Startup (Android6.0)

To simplify the process, let’s assume that the Service is already started. The code starts with AMS calling ActiveService’s 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; . <! --> r.endingstarts. Add (new Servicerecord. StartItem(r, false, r.makeNextStartId(), service, neededGrants)); . <! Return startServiceInnerLocked(smap, Service, r, callerFg, addToStarting); }Copy the code

When the Service starts, the AMS server creates a ServiceRecord, which is an image of the Service on the AMS server. Then the AMS server adds a ServiceRecord.StartItem to the pendingStarts list. This is the basis for the onStartCommand callback, followed by a call to startServiceInnerLocked and a call to bringUpServiceLocked to start the Service further:

<! -- Function 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); . <! Function 2--> Private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, Boolean execInFg, Boolean whileRestarting) throws TransactionTooLargeException {/ / the first call, of state Richard armitage pp = null, A second call to sendServiceArgsLocked can trigger the execution of onStartCommand if (r.pp! = null && r.app.thread ! // sendServiceArgsLocked(r, execInFg, false) will also be called at startup; 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); Servie realStartServiceLocked(r, app, execInFg); .Copy the code

R.app is not assigned until realStartServiceLocked is executed. If the service has been started, call startService again. SendServiceArgsLocked, calling back to 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 { <! - inform the APP to start the Service - > APP. Thread. ScheduleCreateService (r, r.s erviceInfo, 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. <! - recovery: This should be mostly for start_sticky, To resume, onStartCommand is invoked --> if (r.startrequested && R.callstart && R.popingStarts. Size () == 0) {r.popingstarts. Add (new)  ServiceRecord.StartItem(r, false, r.makeNextStartId(), null, null)); } <! OnstartComand --> sendServiceArgsLocked(r, execInFg, true); .Copy the code

RealStartServiceLocked notifies your APP with Binder to create a Service: App. Thread. ScheduleCreateService, then through the notification callback onStartCommand app, due to the AMS is through to the app UI thread insert message to handle, when the sendServiceArgsLocked request is performed, The Service must be created. There is nothing to say about the creation process, but sendServiceArgsLocked is the main thing here. Starting Servicelocked, we plugged a ServiceRecord.StartItem to pendingStarts, which is used in sendServiceArgsLocked below:

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, boolean oomAdjusted) throws TransactionTooLargeException { final int N = r.pendingStarts.size(); if (N == 0) { return; While (r.endingstarts. Size () >0) {Exception caughtException = null; ServiceRecord.StartItem si; try { si = r.pendingStarts.remove(0); <! If onStartCommand is triggered by a sticky intent, the intent is null. 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; } <! -- Update a factor for deliveredTime recovery delay calculation --> si.deliveredTime = systemclock. uptimeMillis(); <! Add (si) indicates that ServiceRecord.StartItem of pendingStarts is changed to deliveredStarts. <! DeliveryCount++ is a judgment condition for recovery --> si. DeliveryCount++; . r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); . }Copy the code

SendServiceArgsLocked is used to send messages to apps. It is used to trigger onStartCommand or onTaskRemoved when a recent task is removed. SendServiceArgsLocked starts to see what needs to be sent to the APP using pendingStarts. The ServiceRecord. The incoming Intent must be non-null, so execute the subsequent Intent. Here are some important points:

  • Transfers the record from pendingStarts to deliveredStarts, that is, the onStartCommand never executed is transferred to executed
  • Update deliveredTime, for START_REDELIVER_INTENT, which is a factor in future recovery latency
  • Update deliveryCount. If onStartCommand fails more than two times, the Intent will not be redelivered later (START_REDELIVER_INTENT only).
  • The APP is called back through scheduleServiceArgs

After that, scheduleServiceArgs calls back to the APP, and ActivityThread is processed as follows:

private void handleServiceArgs(ServiceArgsData data) { Service s = mServices.get(data.token); if (s ! = null) { ... int res; // If taskRemoved is not present, call onTaskRemoved if (! data.taskRemoved) { <! OnStartCommand --> res = s.nstartCommand (data.args, data.flags, data.startid); } else { <! --> s.ontaskremoved (data.args); res = Service.START_TASK_REMOVED_COMPLETE; } try { <! - inform the AMS processed - > ActivityManagerNative. GetDefault (.) serviceDoneExecuting (data. The token, SERVICE_DONE_EXECUTING_START, data.startId, res); }... }Copy the code

After the onStartCommand callback is triggered, the Service is started and serviceDoneExecuting is continued on the Service ActiveServices. This is also a key point for Service recovery. The return value of onStartCommand is actually used here to generate a key metric for Service recovery

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) { <! -- For START_STICKY_COMPATIBILITY and START_STICKY Service, OnStartCommand --> case service. START_STICKY_COMPATIBILITY: case Service.START_STICKY: { <! R.indindstart (startId, true); <! --> r.topifkilled = false; break; } case Service.START_NOT_STICKY: { <! --> r.indDeliveredStart (startId, true); <! --> if (r.getlastStartid () == startId) {r.stopifkilled = true; } break; } case Service.START_REDELIVER_INTENT: { ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); // kill = true if (si! = null) { si.deliveryCount = 0; si.doneExecutingCount++; // Don't stop if killed. -- r.stopifkilled = true -- r.start = true -- r.start = true; } break; }... } if (res == Service.START_STICKY_COMPATIBILITY) { <! -- Service.START_STICKY_COMPATIBILITY restarts but does not trigger onStartCommand. Different versions may vary --> r.callstart = false; }}... }Copy the code

ServiceDoneExecutingLocked mainly do two things

  • For services that do not need to re-send the Intent, clean up deliveredStarts
  • Set stopIfKilled to false for services that need to be restarted immediately

If a service. START_REDELIVER_INTENT is a sticky Intent, it needs to restart. If a service. START_REDELIVER_INTENT is a sticky Intent, it needs to restart. Service.START_REDELIVER_INTENT restart does not require this flag bit.

If the second parameter is true, it indicates that deliveredStarts needs to be cleared. If the second parameter is true, it indicates that deliveredStarts needs to be cleared. If the second parameter is true, it indicates that deliveredStarts needs to be cleared. This is an indicator of the START_REDELIVER_INTENT restart.

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;
}
Copy the code

At this point, the Service is started and all the data built for the restart is ready, including two

  • The stopIfKilled field of ProcessRecord, if false, needs to be restarted immediately
  • ProcessRecord starts with deliveredStarts. If it is not empty, it needs to restart the original Intent.

If the APP is killed by the background, the Service (and process) will be restarted.

How to Restart the Service after the APP is killed

Binder has an obituary mechanism. When the Server dies, it sends an obituary notification to the Client. In this case, ActivityManagerService sends an obituary notification to the Client. To further process the process, ActivityManagerService calls handleAppDiedLocked to handle the dead process:

<! --> Private final void handleAppDiedLocked(ProcessRecord app, Boolean restarter, boolean allowRestart) { int pid = app.pid; boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1); . The <! 2 - function -- -- > private final Boolean cleanUpApplicationRecordLocked (ProcessRecord app, Boolean restarting. boolean allowRestart, int index) { ... mServices.killServicesLocked(app, allowRestart);Copy the code

KillServicesLocked of ActiveServcies is further called. KillServicesLocked cleans up the services of dead processes and, if necessary, restarts the services according to previous startup Settings:

final void killServicesLocked(ProcessRecord app, boolean allowRestart) { <! 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); <! --> for (int I =app.services.size()-1; i>=0; i--) { ServiceRecord sr = app.services.valueAt(i); . <! Do not restart the Service more than twice, but the process will still wake up. Still restart - > if (allowRestart && sr. CrashCount > = 2 && (sr) serviceInfo) applicationInfo. The flags &ApplicationInfo.FLAG_PERSISTENT) == 0) { bringDownServiceLocked(sr); } else if (! allowRestart || ! mAm.isUserRunningLocked(sr.userId, false)) { <! --> bringDownServiceLocked(sr); } else { <! - prepared to restart - > 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. <! - restart canceled too many words = true (mainly for retransmission of intent) -- > if (sr) startRequested && (sr) stopIfKilled | | canceled)) {if (sr.pendingStarts.size() == 0) { sr.startRequested = false; . if (! sr.hasAutoCreateConnections()) { bringDownServiceLocked(sr); } } } } }Copy the code

There are some limitations, such as restart failed twice, then restart the Service no longer, but the system APP is not restricted, bindService that regardless of the first, the other for the normal stop will be called scheduleServiceRestartLocked to register the restart, But for things like START_NOT_STICKY, the registration will be cancelled again, where sr.stopifkilled is used. See first scheduleServiceRestartLocked, it also affect whether the return value of the need to restart the:

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 Final int N = r.delieveredStarts. Size (); 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 starts with pendingStarts. R.endingstarts. Add (0, si); 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 Restarts for the first time 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). <! -- If killed, run short and then killed, then increase the restart delay, otherwise reset to 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; }}} <! -- calculate the next restart time --> r.extrestartTime = now + r.restartdelay; <! --> < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: break-all; 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 { <! -- System services, // 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)) { <! Add Service--> mrestartingservices.add (r); . } mAm.mHandler.removeCallbacks(r.restarter); // postAtTime mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime); <! R.extrestarttime = systemclock. uptimeMillis() + r.restartDelay; . return canceled; }Copy the code

ScheduleServiceRestartLocked main effect is to calculate restart delay, and restart the message sent to the corresponding MessageQueue Handler, to need to send the Intent of the Service, before their Intent is temporarily is delivered, During the recovery phase, the original deliveredStarts is cleaned up and converted to the pendingStart list. Later, when restarting, the Intent is reissued to the Service according to pendingStart, calling its onStartCommand. However, for this type of Service, the recovery time depends on its running time. The longer the distance from startService is, the longer the delay time for the recovery is, which will be explained separately later.

In addition, if the Service restart interval is too short, it indicates that the Service is killed too quickly, most likely due to insufficient system resources. In this case, the restart time is gradually extended. Second, when will Cancle restart? DeliveredStarts (START_DELIVER_INTENT) and starts the onStartCommand callback with a failure count >=2 or a success count >=6. For START_DELIVER_INTENT, if it is killed more than six times, AMS will clean up the Service and never restart it again. In addition, if multiple services are restarted, a preset interval of at least 10 seconds is required to prevent the restart time from being too close to each other. This does not mean that services need to be restarted at a minimum of 10 seconds. It takes at least 10 seconds to restart the next Service. If the first Service is successfully restarted and the process is successfully started, all services will be invoked immediately, without waiting for the actual 10-second delay.

As you can see from pendingStart, although the Service restart interval is at least 10 seconds, when a Service is successfully started, all services are aroused, even though the preset start time is not reached. Why is that? If you attach an Application to the attatch Service, you need to attach the Application to the attatch Service. If you attach an Application to the attatch Service, you need to attach an Application to the attatch Service.

boolean attachApplicationLocked(ProcessRecord proc, String processName) throws RemoteException { boolean didSomething = false; . 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; } <! - remove the old, - > mAm. MHandler. RemoveCallbacks (sr. Restarter); <! -- Add new --> mam.mhandler. post(sr.restarter); } } return didSomething; }Copy the code

As you can see, attachApplicationLocked removes the old 10-second delay from the restarter and adds restart commands without delay, so that services that need to be restarted do not have to wait for the preset delay. What else do you need to consider? Look at the following:

<! - restart canceled too many words = true (mainly for retransmission of intent) -- > if (sr) startRequested && (sr) stopIfKilled | | canceled)) {if (sr.pendingStarts.size() == 0) { sr.startRequested = false; . if (! sr.hasAutoCreateConnections()) { bringDownServiceLocked(sr); }}}}Copy the code
  • For START_STICKY scheduleServiceRestartLocked return value must be false, the delay time is 1 s, and due to its stopIfKilled is false, so must be quick restart, Does not go through the bringDownServiceLocked process
  • For STAR_NO_STICKY scheduleServiceRestartLocked the return value is flase, but stopIfKilled is true, in addition its pendingStarts list is empty, if there is no other Activity of survival binding, Then you need to go through the bringDownServiceLocked process, that is, it won’t be restarted.

After processing the above logic, the ServiceRestarter is inserted into the MessegeQueue for execution, Called after performServiceRestartLocked – > bringUpServiceLocked – > realStartServiceLocked further processing Service to restart.

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 the blank, also don't have to restart the 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. }Copy the code

}

If you have a sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky sticky

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... <! - recovery: This should be mostly for start_sticky, To resume, onStartCommand is invoked --> if (r.startrequested && R.callstart && R.popingStarts. Size () == 0) {r.popingstarts. Add (new)  ServiceRecord.StartItem(r, false, r.makeNextStartId(), null, null)); } <! OnstartComand --> sendServiceArgsLocked(r, execInFg, true); .Copy the code

For START_STICKY, restart is required, as mentioned earlier, but how to mark the need to call onStartCommand again? The realStartServiceLocked above automatically adds a ServiceRecord.StartItem to pendingStarts because the following conditions are met for START_STICKY.

 r.startRequested && r.callStart && r.pendingStarts.size() == 0
Copy the code

However, this Item has no Intent, that is, when the onStartCommand callback is called, no Intent is delivered to the APP, and sendServiceArgsLocked is the same logic as before. Here’s why START_REDELIVER_INTENT is time-consuming.

Why is START_REDELIVER_INTENT usually delayed more than START_STICK when killed

As mentioned earlier, when onStartCommand returns a START_REDELIVER_INTENT, the restart delay is dependent on the Service startup time. The specific algorithm is as follows: Time from start to Now x 2. The longer the start time is, the longer the restart delay is.

private final boolean scheduleServiceRestartLocked(ServiceRecord r, boolean allowCancel) { boolean canceled = false; . final int N = r.deliveredStarts.size(); If (N > 0) {... if (! allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) { r.pendingStarts.add(0, si); Long dur = systemclock.uptimemillis () -si.deliveredTime; dur *= 2; if (minDuration < dur) minDuration = dur; if (resetTime < dur) resetTime = dur; }Copy the code

If START_REDELIVER_INTENT is set, deliveredStarts must be non-empty because it holds the Intent list of startService. In this case, the restart delay needs to be recalculated. Generally 2* (time since last sendServiceArgsLocked (e.g. triggered by startService))

long dur = SystemClock.uptimeMillis() - si.deliveredTime;
Copy the code

For example, if it is 3 minutes since the last startService, it will restart in 6 minutes, if it is 1 hour, it will start in 1 hour,

For START_STICK, the startup delay is basically the minimum restart delay unit set by the system, usually one second:

static final int SERVICE_RESTART_DURATION = 1*1000;
Copy the code

So if you need to restart a Service quickly, use START_STICK. However, START_STICK does not deliver the Intent. If the process was accidentally killed, the user will restart the process when it is removed from the latest task list.

Removed from recent task list, how to handle Service restart

Left-swiping sometimes causes the process to be killed. In this case, unstopped services may also need to be restarted. Is this different? In this case the Service’s onTaskRemoved is called back.

@Override
public void onTaskRemoved(Intent rootIntent) {
    super.onTaskRemoved(rootIntent);
}
Copy the code

Swiping left to remove the TASK calls AMS cleanUpRemovedTaskLocked, which handles the Service first and calls back onTaskRemoved to remove the process. The removed logic also goes through the Binder obituary mechanism. Here’s a look at onTaskRemoved, but if it doesn’t require a reboot, it could be removed here:

private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) { ... // Find any running services associated with this app and stop if needed. <! - processing Service first, if it is necessary to clean up the Service - > mServices. CleanUpRemovedTaskLocked (tr, component, the new Intent (tr) getBaseIntent ())); if (! killProcess) { return; } <! 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); . <! --> procstokill.add (proc); }} for (int I = 0;}} 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"; }}}Copy the code

The 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); // If (sr.startrequested) {if ((sr.serviceinfo.flags&serviceInfo.flag_stop_with_task)! = 0) { stopServiceLocked(sr); } else { <! -- As part of the remove function, the add function is used to return to onStartCommand, and the process is not dead yet. 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); }... }Copy the code

FLAG_STOP_WITH_TASK is a Service set to serviceInfo. FLAG_STOP_WITH_TASK. Left swipe removed to remove the Service without restarting it. The ServiceRecord.StartItem is added to pendingStarts so that sendServiceArgsLocked can send the onTaskRemoved request. To separate the process from starting onStartCommand, ServiceRecord.StartItem’s TaskRemover is set to true, so that handleServiceArgs on The ActiviyThread callback goes onTaskRemoved as follows:

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; // If taskRemoved is not present, call onTaskRemoved if (! data.taskRemoved) { res = s.onStartCommand(data.args, data.flags, data.startId); } else { s.onTaskRemoved(data.args); res = Service.START_TASK_REMOVED_COMPLETE; }...Copy the code

Thus, the removal of the most recent task removed from the list is merely an onTaskRemoved process in which the user can take care of things themselves, such as disconnecting from Service processes, saving the scene, etc.

conclusion

  • A Service started by startService but not terminated by stopService does not necessarily trigger a restart. You need to set the corresponding onStartCommand return value, such as START_REDELIVER_INTENT and START_STICK
  • START_REDELIVER_INTENT does not redeliver the last Intent
  • The delay for a START_REDELIVER_INTENT is different from that for a START_STICK intent. The delay for a START_REDELIVER_INTENT is usually 1 second, but the delay for a START_REDELIVER_INTENT is twice that for a startService.
  • It can be used to do package work, but it is not recommended, and it is not easy to use in China (MIUI, Huawei and others have customized AMS, with more restrictions).

Android Service restart recovery (Service process restart) principle analysis

For reference only, welcome correction