Android O has introduced Background Execution Limits to reduce the memory usage and power consumption of Background applications. One obvious example is that Background applications are not allowed to start a service through startService. StartService belongs to the background startup service. Second: if you want to startService in the background, how to compatibility, so divided into the following problems analysis

  • Background startService scenario
  • Analysis of background startService Crash principle
  • How to modify to achieve compatibility

For common APP, we do not consider various whitelists of the system. Generally, background startService can be divided into the following two types:

  • StartService through another application
  • Apply startService yourself

Each scenario can be divided into different scenarios. It is not recommended to use startService in other scenarios.

This article is based on Android P source code

Limit by startService in the background through your own application

A simple experiment can be performed to see what the background startService is. Note that if your APP starts the Service, it must already be up. The scenario is recreated by delaying execution. For example, with the click event, delay executing a startService operation for 65s (more than a minute, as you’ll see later), then click the Home button to return to the desktop, and wait a minute to Crash again:

@OnClick(R.id.first) void first() { new Handler().postDelayed(new Runnable() { @Override public void run() { Intent intent = new Intent(LabApplication.getContext(), BackGroundService.class); startService(intent); Logutils.v (" delayed execution "); }}, 1000 * 65); }Copy the code

After about a minute, the delay message is executed and the following Crash log is printed:

-- -- -- -- -- -- -- -- -- beginning of crash 19:47:43. 2019-06-17, 148, 25916-25916 / com. Q. Labaffinity E/AndroidRuntime: FATAL EXCEPTION: main Process: com.snail.labaffinity, PID: 25916 java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.snail.labaffinity/.service.BackGroundService }: App is in background uid UidRecord{9048c2c u0a73 LAST bg:+1m4s376ms idle change:idle procs:1 seq(0,0,0)} at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577) at android.app.ContextImpl.startService(ContextImpl.java:1532) at android.content.ContextWrapper.startService(ContextWrapper.java:664) at com.snail.labaffinity.activity.MainActivity$2.run(MainActivity.java:41) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)Copy the code

You’ll see the classic startService restriction:

 Not allowed to start service Intent XXX : app is in background uid UidRecord  
Copy the code

In other words, the current APP belongs to the background APP and cannot be started by using startService. According to? Binder calls AMS to start the Service and optionally throws an IllegalStateException based on the return value:

ContextImpl.java

private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); if (cn ! = null) { <! What is the return value? --> if (cn.getPackagename ().equals("?") )) { throw new IllegalStateException( "Not allowed to start service " + service + ": " + cn.getClassName()); }}Copy the code

When ActivityManager. GetService (). StartService the return value of the package name? With the core code on the AMS side, AMS further calls ActiveServices. Java’s startServiceLocked:

ActiveServices.java

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId) throws TransactionTooLargeException { final boolean callerFg; if (caller ! = null) { final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); . callerFg = callerApp.setSchedGroup ! = ProcessList.SCHED_GROUP_BACKGROUND; } else { callerFg = true; } ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg, false, false); . ServiceRecord r = res.record; // If we're starting indirectly (e.g. from PendingIntent), figure out whether // we're launching into an app in a background state. This keys off of the same // idleness state tracking as e.g. O+ background service start policy. <! PendingIntent specifies whether the intent is Active or not. Final Boolean bgLaunch =! mAm.isUidActiveLocked(r.appInfo.uid); // If the app has strict background restrictions, we treat any bg service // start analogously to the legacy-app forced-restrictions case, regardless // of its target SDK version. boolean forcedStandby = false; <! - appRestrictedAnyInBackground average person does not take the initiative in setting, So this is often returns false - > if (bgLaunch && appRestrictedAnyInBackground (state Richard armitage ppInfo. The uid, r.p ackageName)) {... forcedStandby = true; } <! First, r.startrequested is started by a startService call and is false the first time it is invoked. Second, forcedStandby: For ordinary is starServicefgRequired is false -- > if (forcedStandby | | (! r.startRequested && ! fgRequired)) { <! - check whether the current app allows background start -- > final int allowed = mAm. GetAppStartModeLocked (state Richard armitage ppInfo. The uid, r.p ackageName, r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby); <! -- If Background start not allowed--> if (allowed! = ActivityManager.APP_START_MODE_NORMAL) { ... <! - return? Tells the client that it is now in the background startup state, forbids you --> return New ComponentName("? , "app is in background uid " + uidRec); }}Copy the code

Assuming we are startService for the first time, then (! r.startRequested && ! FgRequired) is true, then entered the mAm. GetAppStartModeLocked, see if the current process in the background the activated state, if it is, will not allow startService:

ActivityManagerService.java

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk, int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) { UidRecord uidRec = mActiveUids.get(uid); <! - UidRecord is key alwaysRestrict | | forcedStandby incoming is false, Ignore - > if (uidRec = = null | | alwaysRestrict | | forcedStandby | | uidRec. Idle) {Boolean ephemeral; . final int startMode = (alwaysRestrict) ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk) : appServicesRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk); . return startMode; } return ActivityManager.APP_START_MODE_NORMAL; }Copy the code

Here UidRecord is the key, UidRecord is null, it indicates that the entire APP has not been started, then it must belong to the background start Service, if the UidRecord is not null, then to determine whether the application belongs to the background application, and the key is uidrec.idle, If idle is true, is applied in the background condition, continue to call appServicesRestrictedInBackgroundLocked see whether O later, go Crash logic:

int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) { <! -- Persistent process --> // Persistent app? if (mPackageManagerInt.isPackagePersistent(packageName)) { return ActivityManager.APP_START_MODE_NORMAL; } <! Whitelist --> // Non-persistent but background whitelisted? if (uidOnBackgroundWhitelist(uid)) { return ActivityManager.APP_START_MODE_NORMAL; } <! --> // Is this app on the battery whitelist? if (isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ false)) { return ActivityManager.APP_START_MODE_NORMAL; } / / normal process return appRestrictedInBackgroundLocked (uid, packageName, packageTargetSdk); }Copy the code

Look at the O limit for normal processes

int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) { <! Activitymanager.app_start_mode_delayed_rigid --> if (packageTargetSdk >= Build.VERSION_CODES.O) { return ActivityManager.APP_START_MODE_DELAYED_RIGID; } / / or you just do to the old version compatibility limited int appop = mAppOpsService. NoteOperation (AppOpsManager OP_RUN_IN_BACKGROUND, uid, packageName); if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop); } switch (appop) { case AppOpsManager.MODE_ALLOWED: // If force-background-check is enabled, restrict all apps that aren't whitelisted. if (mForceBackgroundCheck && ! UserHandle.isCore(uid) && ! isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ true)) { return ActivityManager.APP_START_MODE_DELAYED; }... }Copy the code

AppServicesRestrictedInBackgroundLocked merely according to whether O later, return ActivityManager. APP_START_MODE_DELAYED_RIGID, is compatible with, The core is UidRecord idle, the following focus on the UidRecord and its idle value, this value is the core indicator of whether the application is in the background, the application is not started, not started certainly belongs to the “background” of an extreme.

Older Versions of Android don’t allow applications without a LAUNCHER Activity, or they won’t compile and run at all. For example, in common scenarios, when you start an APP from the desktop, you start the APP directly through startActivity. UidRecord will be created (AMS side), UidRecord constructor default idle = true.

public UidRecord(int _uid) {
    uid = _uid;
    idle = true;
    reset();
}
Copy the code

The startup process call stack looks like this:

If idle in UidRecord is set to true when the APP is started, it must be set to false somewhere.

Switching timing and principle of foreground and background applications

An APP can have one or more processes, and when any process is converted to a foreground visible process, the APP is considered a foreground application (for startService applications). ResumetopActivity is a very specific time to switch.

Will be called

final void scheduleIdleLocked() {
    mHandler.sendEmptyMessage(IDLE_NOW_MSG);
}
Copy the code

The idle state of the Activity application that is about to be visible is changed with updateOomAdjLocked. During this time, updateOomAdjLocked may be called several times.

@GuardedBy("this") final void updateOomAdjLocked() { ... for (int i=mActiveUids.size()-1; i>=0; i--) { final UidRecord uidRec = mActiveUids.valueAt(i); int uidChange = UidRecord.CHANGE_PROCSTATE; if (uidRec.curProcState ! = ActivityManager.PROCESS_STATE_NONEXISTENT && (uidRec.setProcState ! = uidRec.curProcState || uidRec.setWhitelist ! = uidRec.curWhitelist)) { ... if (ActivityManager.isProcStateBackground(uidRec.curProcState) && ! uidRec.curWhitelist) { ... } else { <! If (uidrec.idle) {uidChange = uidRecord.change_active; EventLogTags.writeAmUidActive(uidRec.uid); uidRec.idle = false; } <! - zero a background process anchors time - > uidRec. LastBackgroundTime = 0; }Copy the code

For the visible APP ActivityManager. IsProcStateBackground to false, Idle = false,uidChange = uidRecord.change_active, enqueueUidChangeLocked, idle = false. Correspondingly, the last application that was switched away may trigger the operation of idle = true. However, the operation that is set to true is not ready to be executed, but delayed. The delay time is 60s:

final void updateOomAdjLocked() { ... for (int i=mActiveUids.size()-1; i>=0; i--) { final UidRecord uidRec = mActiveUids.valueAt(i); int uidChange = UidRecord.CHANGE_PROCSTATE; if (uidRec.curProcState ! = ActivityManager.PROCESS_STATE_NONEXISTENT && (uidRec.setProcState ! = uidRec.curProcState || uidRec.setWhitelist ! = uidRec.curWhitelist)) { if (ActivityManager.isProcStateBackground(uidRec.curProcState) && ! uidRec.curWhitelist) { // UID is now in the background (and not on the temp whitelist). Was it // previously in the foreground (or on the temp whitelist)? if (! ActivityManager.isProcStateBackground(uidRec.setProcState) || uidRec.setWhitelist) { <! - when switching the background update lastBackgroundTime - > uidRec. LastBackgroundTime = nowElapsed; if (! mHandler.hasMessages(IDLE_UIDS_MSG)) { <! Update, after 60 s -- > mHandler. SendEmptyMessageDelayed (IDLE_UIDS_MSG, mConstants. BACKGROUND_SETTLE_TIME); }}Copy the code

The delay of 60s is to prevent repeated updates caused by multiple APP switching within 60s. The system only needs to ensure that there is one update within 60s.

 private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
Copy the code

60 seconds later, idleUids is called to update the idle field

final void idleUids() { synchronized (this) { final int N = mActiveUids.size(); if (N <= 0) { return; } final long nowElapsed = SystemClock.elapsedRealtime(); final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME; long nextTime = 0; for (int i=N-1; i>=0; i--) { final UidRecord uidRec = mActiveUids.valueAt(i); <! - just cut the background has been updated uidRec. LastBackgroundTime - > final long bgTime = uidRec. LastBackgroundTime; if (bgTime > 0 && ! uidRec.idle) { <! BACKGROUND_SETTLE_TIME--> if (bgTime <= maxBgTime) {uidrec. idle = true; uidRec.setIdle = true; doStopUidLocked(uidRec.uid, uidRec); } else {if is carried out in advance, then the next 60 s arrived to perform the if (nextTime = = 0 | | nextTime > bgTime) {nextTime = bgTime; } } } } if (nextTime > 0) { mHandler.removeMessages(IDLE_UIDS_MSG); mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed); }}}Copy the code

Is the front desk before, now becomes the background, so uidRec. LastBackgroundTime = nowElapsed assignment, cut the front desk again, uidRec. LastBackgroundTime reset, in short, applied to the front desk, The UID status changes to active immediately, and the application changes to background. That is, if procState is greater than or equal to PROCESS_STATE_TRANSIENT_BACKGROUND, if the process has been in background for 60 seconds, The UID status changes to idle=true and startService cannot be used.

StartService through other applications

StartService is not recommended, but it is also easy to simulate. In A application, you can use setAction+setPackage to startService:

        var intent = Intent();
        intent.setAction("com.snail.BackGroundService");
        intent.setPackage("com.snail.labaffinity");
        startService(intent)
Copy the code

Of course, AndroidManifest should be exposed in the B application:

<service android:name=".service.BackGroundService" <! -- Independent process, Android :exported="true"> <intent-filter> <action android:name="com.snail.BackGroundService" /> </intent-filter> </service>Copy the code

In this case, startService in A must also comply with the condition that background startup is not allowed. For example, if B has not been started, start startService in A directly, then Crash will occur. If B has been started and has not become A background application (back to the background within 60 seconds), then Crash will not occur. In my opinion, the adb startService command also belongs to this category. The following command can achieve the same effect.

am startservice -n com.snail.labaffinity/com.snail.labaffinity.service.BackGroundService 
Copy the code

If the APP is not started, you will see the following log:

app is in background uid null
Copy the code

If the Service is started in the background, you will see the following log:

Not allowed to start service Intent { cmp=com.snail.labaffinity/.service.BackGroundService }: The app is in background uid UidRecord {72 bb30d u0a238 SVC idle change: idle | uncached procs: 1 seq (0, 0)}Copy the code

In fact, startService is not to see the call APP in what state, but to see the Servic APP in what state, because the Servic is in the state of the UidRecord, UidRecord only with APP installation, with the process PID has no relation.

Special scenario: The process is recovered through Service

When the APP starts, it starts a service using startService in the onCreate of the Application without stopping. In this scenario, the cold start of the APP is ok. If we kill the APP in the background, Because there is an unstopped service, the system will restart the service, that is, restart a process, and then start the service.

public class LabApplication extends Application { @Override public void onCreate() { super.onCreate(); Intent intent = new Intent( this, BackGroundService.class); startService(intent); } } public class BackGroundService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { LogUtils.v("onStartCommand"); }}Copy the code

In this process, the application restart will repeat the following Crash (disable background Service Crash Log) :

java.lang.RuntimeException: Unable to create application com.snail.labaffinity.app.LabApplication: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.snail.labaffinity/.service.BackGroundService }: The app is in background uid UidRecord {72 bb30d u0a238 SVC idle change: idle | uncached procs: 1 seq (0, 0)} the at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5925) at android.app.ActivityThread.access$1100(ActivityThread.java:200) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1656) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6718) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)Copy the code

According to? If the app is in background, Not allowed to start service, that is, the background process cannot start the service through startService. If the app is in background, Not allowed to start service, the background process cannot start the service through startService. In the onCreate of LabApplication, we did initiate startService(Intent). This is the reason why crash happened the first time. As we know above, the application started by Laucher is started by startActivity, that is, there is a resumeTopActivity time, at this time, the idle of the APP will be set to false, that is, the non-background application, but for the background kill and resume scenario, It is not started by startActivity, so even if the APP is restarted, the idle status of the APP is still true. It is not activated, which means it belongs to the background APP. StartService is not allowed to start the service (assuming a single process).

For the first cold startup, follow the normal Activity startup process, create a new process, and remove the AMS attachApplication.

@GuardedBy("this") private final boolean attachApplicationLocked(IApplicationThread thread, int pid, int callingUid, long startSeq) { ... <! Thread. BindApplication (processName, appInfo, providers, app.instr.mclass, profilerInfo, app.instr.mArguments,...) ; . boolean badApp = false; boolean didSomething = false; // See if the top visible activity is waiting to run in this process... if (normalMode) { try { <! Key points - need to start the Activity - > if (mStackSupervisor. AttachApplicationLocked (app)) {didSomething = true; } if (! badApp) { try { <! - need to restore the Service - > didSomething. | = mServices attachApplicationLocked (app, processName); checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked"); }Copy the code

When the APP is first started, Thread. bindApplication tells the APP to start the Application and execute onCreate, but the startService in onCreate waits for a message from AMS to complete (Handler guarantees). The process mStackSupervisor. AttachApplicationLocked (app) will call realStartActivityLocked start-up Activity, UidRecord idle to update to false first, After attachApplicationLocked is executed, the next message startService may be executed. At this point, the APP is no longer in the background, so it will not Crash.

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException { final String processName = app.processName; boolean didSomething = false; . final int size = mTmpActivityList.size(); <! --> for (int I = 0; i < size; i++) { final ActivityRecord activity = mTmpActivityList.get(i); if (activity.app == null && app.uid == activity.info.applicationInfo.uid && processName.equals(activity.processName)) { try { <! --> if (realStartActivityLocked(activity, app, top == activity /* andResume */, true /* checkConfig */))Copy the code

RealStartActivityLocked updates oom to Idle and sets idle to false. If an Activity is being started, it is no longer a background process.

But for processes that are killed and restored by Service, there is no clear startActivity, so size = mtmpActivityList.size () where size is 0, realStartActivityLocked is not going, In the process recovery phase, the APP will not be classified as the foreground application. At this time, when AMS executes the next message to start the Service, it will tell the APP end that the application cannot be started in the background.

How to solve this problem

Since you can’t start it secretly in the background, you can only show it started. Google provides the solution: startForegroundService(). In addition, after Service is created, startForeground() must be called within a certain period to make Service visible to users; otherwise, the system will stop this Service and throw ANR. If it is not visible to users, please refer to JobScheduler. startForegroundService

@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, false, mUser);
}

@Override
public ComponentName startForegroundService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, true, mUser);
}
Copy the code

StartServiceCommon: Boolean requireForeground is true;

ComponentName startServiceLocked(IApplicationThread caller, Intent service ... } <! - fgRequired is true, won't detect starting background restrictions - > if (forcedStandby | | (! r.startRequested && ! fgRequired)) { final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName, r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby); if (allowed ! = ActivityManager.APP_START_MODE_NORMAL) { return new ComponentName("?" , "app is in background uid " + uidRec); }}... <! --ServiceRecord assignment R.grequired = fgRequired; <! Add (new ServiceRecord.StartItem(r, false, r.MakenextStartid (), service, neededGrants, callingUid));Copy the code

For the AMS end, the startForegroundService is different from the normal startService. For the guidance of ServiceRecord, the fgRequired Service is set to true; Then go through the process bringUpServiceLocked->realStartServiceLocked-> sendServiceArgsLocked, and when sendServiceArgsLocked, The Service is actually created and started (see the Service startup process).

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, boolean oomAdjusted) throws TransactionTooLargeException { ... ArrayList<ServiceStartArgs> args = new ArrayList<>(); while (r.pendingStarts.size() > 0) { ServiceRecord.StartItem si = r.pendingStarts.remove(0); . if (r.fgRequired && ! r.fgWaiting) { if (! r.isForeground) { <! - to monitor whether startForeground within 5 s - > scheduleServiceForegroundTransitionTimeoutLocked (r); }... try { r.app.thread.scheduleServiceArgs(r, slice); }Copy the code

You can see for the front desk to start the Service fgRequired = true, and for the first time, r.f gWaiting = false, so will walk scheduleServiceForegroundTransitionTimeoutLocked,

void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
    if (r.app.executingServices.size() == 0 || r.app.thread == null) {
        return;
    }
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
    msg.obj = r;
    r.fgWaiting = true;
    mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);
}
Copy the code

R.f gWaiting will be set to true, after scheduleServiceForegroundTransitionTimeoutLocked once, will not walk again.

static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;
Copy the code

Otherwise, the Service will be stopped and the ANR of the Service will be raised when the Handler handles the startForeground 10 seconds later.

void serviceForegroundTimeout(ServiceRecord r) { ProcessRecord app; synchronized (mAm) { if (! r.fgRequired || r.destroying) { return; } app = r.app; r.fgWaiting = false; stopServiceLocked(r); } if (app ! = null) { mAm.mAppErrors.appNotResponding(app, null, null, false, "Context.startForegroundService() did not then call Service.startForeground(): " + r); }}Copy the code

The exception stack is thrown as follows

--------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.snail.labaffinity, PID: 21513
    android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1768)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Copy the code

The solution is to call startForeground immediately, but notice that the Notification needs a ChannelID after O

public class BackGroundService extends Service { @Override public void onCreate() { super.onCreate(); startForeground(); } private void startForeground() { String CHANNEL_ONE_ID = "com.snail.labaffinity"; String CHANNEL_ONE_NAME = "Channel One"; NotificationChannel notificationChannel = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { notificationChannel = new NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_DEFAULT); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); assert manager ! = null; manager.createNotificationChannel(notificationChannel); startForeground(1, new NotificationCompat.Builder(this, CHANNEL_ONE_ID).build()); }}}Copy the code

The Service foreground foreground is foreground visible, and the delayed Message is cancelled so that no exception can be detected and thrown.

private void setServiceForegroundInnerLocked(final ServiceRecord r, int id, Notification notification, int flags) { <! --id cannot be 0--> if (id! = 0) {... if (r.fgRequired) { <! -- Set fgRequired = false--> R.grequired = false; <! -- set fgWaiting = false--> r.fgwaiting = false; alreadyStartedOp = true; <! -- remove ActivityManagerService. SERVICE_FOREGROUND_TIMEOUT_MSG message - > mAm, mHandler. RemoveMessages ( ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); }Copy the code

However, in this case, the status bar will have a notification that XXX is running, which is not a good experience. If you want to complete a task after completion, you’d better actively stop it. One more thing to note: Do not call stop until startForGround is called, otherwise an exception will be thrown:

private final void bringDownServiceLocked(ServiceRecord r) {
        ...
        if (r.fgRequired) {
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
        }
    }
Copy the code

If startForegroundService is called but startForGround is not called, R.grequired = True when stopService is called, BringDownServiceLocked will directly remove ActivityManagerService. SERVICE_FOREGROUND_TIMEOUT_MSG message, And throw ActivityManagerService. SERVICE_FOREGROUND_CRASH_MSG is unusual, in fact just in onCreate startForeground.

conclusion

  • StartService does not look at the state of the calling APP, but at the state of the Servic APP, because it looks at the state of the UID, so what matters here is the APP, not just the process state
  • Do not delay startService too long with Handler, or you may have problems
  • After the application enters the background, the state becomes idle 60 seconds later. Service cannot be started, but startForegroundService can be used to start the application
  • Do not use startService in Application, otherwise there may be problems during recovery
  • StartForGround must cooperate with startForegroundService in a timely manner; otherwise, various exceptions may occur.