When an application is running in the background, it consumes limited device resources, such as RAM. This can have an impact on the user experience, especially if the user is using a resource-intensive application, such as playing a game or watching a video. To improve the user experience, Android 8.0 (API level 26) imposes restrictions on what an app can do while it is running in the background.

What limits?

1. Background applications have limited access to background services

In background applications that do not interact directly with users, running services consumes system resources, which may affect the normal operation of foreground applications. Android 8.0 and later does not allow background applications to run background services. You need to specify startForegroundService() to run for the foreground service or use JobScheduler instead.

2. Registration of implicit broadcast receivers is limited

For some system implicit broadcast (not all), the system does not allow applications to register corresponding broadcast receivers in the AndroidManifest, so as to avoid system broadcast causes many applications to quickly and continuously consume system resources, thus affecting the user experience. Need to be dynamically registered via context.registerReceiver () or JobScheduler instead.

3. The frequency of background applications receiving location updates is reduced

In order to save battery power, maintain a good user experience, and ensure the health of the system, the frequency of background apps receiving location updates is reduced when using them on devices running Android 8.0.

What is a foreground application?

The system can distinguish between foreground and background applications. An application is considered foreground if any of the following conditions are met:

  1. Have a visible Activity
  2. With front Service
  3. Another foreground application is associated with the application (binding services or using content providers)

If none of the above conditions is met, the application is considered to be in the background.

Correctly understand the background service restrictions

Background applications are not allowed to run background services

The description on the website is simple, but do you really know what it means? Follow this sentence:

The background application cannot start the background service

-> Foreground application can start background service

-> A is the foreground application, then A can start the background service

Based on this conclusion and combined with the types of background services, the following three scenarios are verified and the results are as follows:

  1. If the background service belongs to an application process, it can be started properly
  2. If the background service belongs to B’s application process and B is a foreground application process, it can be started properly
  3. If the background service belongs to application process B, and B is a background application, thenUnable to start!

According to the verification results of the third scenario, the description that background applications are not allowed to run background services is inaccurate and ambiguous. A more accurate description should be:

Background services belonging to background applications cannot be started

Background services limit source code analysis

If startService is used to start a background service belonging to a background application on an Android 8.0 device, it will crash directly:

Caused by: java.lang.IllegalStateException: Not allowed to start service Intent
        { act=intent.action.ServerService pkg=com.server }: app is in background uid null
   at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
   at android.app.ContextImpl.startService(ContextImpl.java:1532)
   at android.content.ContextWrapper.startService(ContextWrapper.java:664)...Copy the code

With this exception as a clue, let’s take a step by step look at the restrictions in the source code. An exception is thrown in ContextImpl:

private ComponentName startServiceCommon(Intent service, boolean requireForeground,
        UserHandle user) {... ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier());if(cn ! =null) {
        if (cn.getPackageName().equals("!")) {... }else if (cn.getPackageName().equals("?")) {
            // This throws an exception that starts the service restriction
            throw new IllegalStateException(
                    "Not allowed to start service " + service + ":"+ cn.getClassName()); }}return cn;
}
Copy the code

Cn.getpackagename ().equals(“?”) AMS startService (ComponentName); AMS startService (ComponentName);

public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, boolean requireForeground, String callingPackage, int userId)
        throws TransactionTooLargeException {... ComponentName res;try {
        res = mServices.startServiceLocked(caller, service,
                resolvedType, callingPid, callingUid,
                requireForeground, callingPackage, userId);
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
    return res;
}
Copy the code

Call the startServiceLocked method of ActiveServices in AMS to handle starting the service.

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
        int callingPid, int callingUid, boolean fgRequired, String callingPackage,
        final int userId, boolean allowBackgroundActivityStarts)
        throws TransactionTooLargeException {...// If it is not a foreground service, check whether the background service startup condition is met. If it is not met, start is restricted
    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) {// The startup conditions are not met
            if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
                // Null is returned to represent the silent start of the restriction in this scenario, without notifying the application
                return null; }...// Return ComponentName (ComponentName); // Return ComponentName (ComponentName); // Return ComponentName (ComponentName)
            // Note the return "?" Is the cause of the application crash
            UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
            return new ComponentName("?"."app is in background uid "+ uidRec); }}}Copy the code

At this point is the key to can know mAm getAppStartModeLocked method, if return APP_START_MODE_NORMAL represents start conditions, will not be restricted.

MAm for ActivityManagerService, continue to look at ActivityManagerService’s getAppStartModeLocked method:

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
        int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
    UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
    // Note that both alwaysRestrict and forcedStandby are false
    if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {
        ...
        final int startMode = (alwaysRestrict)
                ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
                : appServicesRestrictedInBackgroundLocked(uid, packageName,
                        packageTargetSdk);
        return startMode;
    }
    return ActivityManager.APP_START_MODE_NORMAL;
}
Copy the code

UidRec == null indicates that the application is not started, and uidrec. idle indicates that the application is in the background. An application that is not started is considered to be in the background, and of course it is not allowed to start background services.

Continue to see appServicesRestrictedInBackgroundLocked method:

int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
    if (mPackageManagerInt.isPackagePersistent(packageName)) {
        // The system is not restricted to permanent application
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    if (uidOnBackgroundWhitelist(uid)) {
        // Whitelist applications are not restricted
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    if (isOnDeviceIdleWhitelistLocked(uid, false)) {
        // Whitelist applications are not restricted
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    // Other applications follow a general restriction policy
    return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
Copy the code

Ordinary application will go general limit strategy, continue to see appRestrictedInBackgroundLocked method:

int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
    if (packageTargetSdk >= Build.VERSION_CODES.O) {
        return ActivityManager.APP_START_MODE_DELAYED_RIGID;
    }
    // Applications whose TARtget API is less than 8.0 will use the old restriction policy, which is known to be unrestricted. }Copy the code

As you can see, the restriction on the Tartget API is the root cause of the exception thrown by the upper layer.

Applicable to Android 8.0 startService restriction policies

After understanding the limitation principle of the system, combined with the source analysis of AMS startup service limitation above, the possible adaptation schemes are listed:

  1. Use startForegroundService instead
  2. Use JobScheduler instead
  3. Persisten Specifies the Persisten application type
  4. Example Add an application to the system whitelist
  5. Adjust the app’s targetSdkVersion to a version less than Android 8.0
  6. Before starting a service, switch the application where the service resides from the background to the foreground

Scenario 1 is a less work-intensive, old-code compliant scenario, but will display a notification, which may not be what we want

Option 2 is the official proposal, and the compatible workload is more than that of option 1

Solution 3 and Solution 4 require system side cooperation and are applicable to systems or pre-installed applications. They are not feasible for most third-party applications

Option 5 is feasible, but not recommended

Option 6 is feasible, but it is not in line with Google’s intention to promote this restrictive policy and goes against the original intention of improving the user experience

How to bypass Android 8.0 startService restrictions?

Android 8.0 startService does not change to a foreground service, call startService method, still can start the background service belongs to the background application, how to achieve?

Through the above solution 6: before starting the service, the application where the service resides can be realized by switching from the background to the foreground. How to switch the application from the background to the foreground? The above describes the conditions for an application to be considered in the foreground:

  1. Have a visible Activity
  2. With front Service
  3. Another foreground application is associated with this application

According to condition 1, an implementation scheme can be thought of:

If the application is in the background, start a transparent, user-unaware Activity, bring the application to the foreground, start the service with startService, and finish the transparent Activity.

The caller starts service like this:

Intent serviceIntent = new Intent();
serviceIntent.setAction("com.ahab.server.service");
serviceIntent.setPackage("com.ahab.server");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    try{
        context.startService(serviceIntent);
    }catch (Exception e){
        Intent activityIntent = new Intent();
        activityIntent.setAction("com.ahab.server.TranslucentActivity");
        activityIntent.setPackage("com.ahab.server"); activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(activityIntent); }}else{
    context.startService(serviceIntent);
}
Copy the code

StartService:

public class TranslucentActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent serviceIntent = new Intent();
        serviceIntent.setAction("com.ahab.server.service");
        serviceIntent.setPackage("com.ahab.server"); context.startService(serviceIntent); finish(); }}Copy the code

As you can see, the code implementation is quite simple. The system does not directly limit the service startup of bindService.