• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar

陈文管的博客

分享有价值的内容

  • Android
  • Affiliate
  • SEO
  • 前后端
  • 网站建设
  • 自动化
  • 开发资源
  • 关于

Android 8(Oreo)后台启动Service限制解析

2019年10月25日发布 | 最近更新于 2023年8月28日

本文针对Android 8(Oreo)开始对于后台启动Service限制做一个解析,一个是startService限制,另一个是startForegroundService启动服务之后5s内必须调用startForeground(),并提供对应异常的适配解决方法。

一、Android Oreo后台启动服务异常

从Androidxref上的源码来看,这个异常机制是从8.1.0版本开始引入的,在8.0.0的源码上还未引入这个限制。

1. 使用startService在后台启动服务

从Android 8.1.0开始,不允许在后台运行服务,应用处于后台的时候,使用startService启动后台服务会抛异常。

相应的异常日志信息如下:

FATAL EXCEPTION: main
Process: com.desay_svautomotive.mapservice, PID: 18123
java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.autonavi.amapauto/com.autonavi.auto.autostart.IntelligentSpeedLimitService }: app is in background uid UidRecord{10e890e 1063 SVC  idle change:uncached procs:2 seq(0,0,0)}
    at android.os.Parcel.readException(Parcel.java:2013)
    at android.os.Parcel.readException(Parcel.java:1951)
    at com.autonavi.amapauto.adapter.ISetter$Stub$Proxy.setBoolean(ISetter.java:195)
    at com.desay_svautomotive.mapservice.manager.adapter.map.gaode.GaodeProxy.onServiceConnected(GaodeProxy.java:101)
    at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1652)
    at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1681)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    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)

2. startForeground没调用或超时调用

使用startForegroundService方法启动服务之后,必须在5秒内调用Service.startForeground(),没有调用或者超时调用都会抛异常。异常机制和ANR埋炸弹的机制一样,详细见:Android ANR详解

相应的异常日志如下:

Exception:(3.2.9.16648)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)

详细可参考Android官网:后台执行限制

二、异常机制解析

1. Android 8.1.0 startService限制机制

1)ContextImpl.java

首先看Android 8.1.0 ContextImpl类startService调用入口,之后调用AMS的startService方法。

@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, false, mUser);
}
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) {
            if (cn.getPackageName().equals("!")) {
                throw new SecurityException(
                        "Not allowed to start service " + service
                        + " without permission " + cn.getClassName());
            } else if (cn.getPackageName().equals("!!")) {
                throw new SecurityException(
                        "Unable to start service " + service
                        + ": " + cn.getClassName());
            } else if (cn.getPackageName().equals("?")) {
                throw new IllegalStateException(
                        "Not allowed to start service " + service + ": " + cn.getClassName());
            }
        }
        return cn;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
2)ActivityManagerService.java

AMS中的startService调用的是ActiveServices类中的startServiceLocked方法。

@Override
public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, boolean requireForeground, String callingPackage, int userId)
        throws TransactionTooLargeException {
    ...
    synchronized(this) {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        ComponentName res;
        try {
            res = mServices.startServiceLocked(caller, service,
                    resolvedType, callingPid, callingUid,
                    requireForeground, callingPackage, userId);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
        return res;
    }
}
3)ActiveServices.java

从注释说明可以看到,如果应用不允许后台运行,就会抛出异常。

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
        int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
        throws TransactionTooLargeException {
    ...
    // If this isn't a direct-to-foreground start, check our ability to kick off an
    // arbitrary service
    if (!r.startRequested && !fgRequired) {
        // Before going further -- if this app is not allowed to start services in the
        // background, then at this point we aren't going to let it period.
        final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                r.appInfo.targetSdkVersion, callingPid, false, false);
        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
            Slog.w(TAG, "Background start not allowed: service "
                    + service + " to " + r.name.flattenToShortString()
                    + " from pid=" + callingPid + " uid=" + callingUid
                    + " pkg=" + callingPackage);
            if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
                // In this case we are silently disabling the app, to disrupt as
                // little as possible existing apps.
                return null;
            }
            // This app knows it is in the new model where this operation is not
            // allowed, so tell it what has happened.
            UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
          // 这边就是抛出的异常消息
            return new ComponentName("?", "app is in background uid " + uidRec);
        }
    }
    ...
    ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    return cmp;
}
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    ServiceState stracker = r.getTracker();
    if (stracker != null) {
        stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
    }
    r.callStart = false;
    synchronized (r.stats.getBatteryStats()) {
        r.stats.startRunningLocked();
    }
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
    if (error != null) {
        return new ComponentName("!!", error);
    }
    ...
    return r.name;
}
4)ActivityManagerService启动服务校验

从上面ActiveService中的逻辑,可以看到是调用ActivityManagerService来对启动的服务做校验,接着看getAppStartModeLocked的调用逻辑堆栈,可以看到如果是版本从Android O开始的返回的状态值是APP_START_MODE_DELAYED_RIGID。

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
        int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
    ...
    if (uidRec == null || alwaysRestrict || uidRec.idle) {
        ...
        if (ephemeral) {
            // We are hard-core about ephemeral apps not running in the background.
            return ActivityManager.APP_START_MODE_DISABLED;
        } else {
            ...
            final int startMode = (alwaysRestrict) // alwaysRestrict值为false
                    ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
                    : appServicesRestrictedInBackgroundLocked(uid, packageName,
                            packageTargetSdk);
            ...
            return startMode;
        }
    }
    return ActivityManager.APP_START_MODE_NORMAL;
}
// Service launch is available to apps with run-in-background exemptions but
// some other background operations are not.  If we're doing a check
// of service-launch policy, allow those callers to proceed unrestricted.
int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
    // Persistent app?
    if (mPackageManagerInt.isPackagePersistent(packageName)) {
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid + "/" + packageName
                    + " is persistent; not restricted in background");
        }
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    // Non-persistent but background whitelisted?
    if (uidOnBackgroundWhitelist(uid)) {
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid + "/" + packageName
                    + " on background whitelist; not restricted in background");
        }
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    // Is this app on the battery whitelist?
    if (isOnDeviceIdleWhitelistLocked(uid)) {
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid + "/" + packageName
                    + " on idle whitelist; not restricted in background");
        }
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    // None of the service-policy criteria apply, so we apply the common criteria
    return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
// Unified app-op and target sdk check
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
    // Apps that target O+ are always subject to background check
    if (packageTargetSdk >= Build.VERSION_CODES.O) {
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
        }
        return ActivityManager.APP_START_MODE_DELAYED_RIGID;
    }
    // ...and legacy apps get an AppOp check
    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:
            return ActivityManager.APP_START_MODE_NORMAL;
        case AppOpsManager.MODE_IGNORED:
            return ActivityManager.APP_START_MODE_DELAYED;
        default:
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
    }
}

2. startForeground调用异常机制

1)ContextImpl.java

从调用入口来看startForegroundService和startService唯一的区别就是requireForeground参数值为true。

@Override
public ComponentName startForegroundService(Intent service) {
    warnIfCallingFromSystemProcess();
    // requireForeground参数为true
    return startServiceCommon(service, true, mUser);
}
2)ActiveServices.java

按照调用逻辑堆栈跟下去,可以查看到完整的调用逻辑函数堆栈信息,下面从ActiveServices类的内部类ServiceMap中开始,截取简化代码片段做说明,调用的函数堆栈信息用红色字体标注。

最终是发送延迟5s的消息来触发超时异常,和ANR埋炸弹的超时机制一样。

void rescheduleDelayedStartsLocked() {
    while (mDelayedStartList.size() > 0
            && mStartingBackground.size() < mMaxStartingBackground) {
        r.delayed = false;
        try {
            startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true);
        } catch (TransactionTooLargeException e) {
            // Ignore, nobody upstack cares.
        }
    }
}
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
    if (error != null) {
        return new ComponentName("!!", error);
    }
    return r.name;
}
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
        boolean whileRestarting, boolean permissionsReviewRequired)
        throws TransactionTooLargeException {
    if (r.app != null && r.app.thread != null) {
        sendServiceArgsLocked(r, execInFg, false);
        return null;
    }
    return null;
}
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
        boolean oomAdjusted) throws TransactionTooLargeException {
    while (r.pendingStarts.size() > 0) {
        if (r.fgRequired && !r.fgWaiting) {
            if (!r.isForeground) {
                scheduleServiceForegroundTransitionTimeoutLocked(r);
            } else {
            }
        }
    }
}
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 5*1000;
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);
}
void serviceForegroundTimeout(ServiceRecord r) {
    ProcessRecord app;
    synchronized (mAm) {
        if (!r.fgRequired || r.destroying) {
            return;
        }
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "Service foreground-required timeout for " + r);
        }
        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()");
    }
}

三、异常解决方法

1. startService限制解决方法

除了做8.1.0适配startService改为用startForegroundService之外,如果是低版本向高版本兼容最好用反射的方式来适配。

public static void startForegroundService(Context context,Intent intent){
    try {
        Method startForegroundServiceMethod =context.getClass().getMethod("startForegroundService", Intent.class);
        if (startForegroundServiceMethod != null) {
            startForegroundServiceMethod.invoke(context,intent);
        }
    } catch (IllegalAccessException e) {
    } catch (InvocationTargetException e) {     
    } catch (NoSuchMethodException e) {        
    }catch (Exception exception){
    }
}

2. startForeground异常解决方法

一般是把如下Notification适配显示逻辑放在Service的onStartCommand回调中,但是也遇到过在性能较差的设备上,因为应用的启动时间超过5秒而导致放在onStartCommand回调中的startForeground调用超时,遇到这种情况就把以下适配逻辑放在onCreate里面调用。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (notificationManager != null) {
       // CHANNEL_ID参数不能为null
        String CHANNEL_ID = "AppName";
        CharSequence name = getString(R.string.app_name);
        String description = getString(R.string.app_name);
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW);
        channel.setDescription(description);
        channel.enableLights(false);
        channel.enableVibration(false);
        channel.setVibrationPattern(new long[]{0});
        channel.setSound(null, null);
        notificationManager.createNotificationChannel(channel);
        Bundle bundle = new Bundle();
        Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
                .setSmallIcon(R.drawable.v3_icon)
                .setExtras(bundle)
                .setWhen(System.currentTimeMillis())
                .setTicker("App Foreground Service Started.")
                .setContentTitle("App Foreground Service")
                .setContentText("Foreground Service Started.")
                .build();
       // 这边的第一个参数值必须为1
        service.startForeground(1, notification);
    }
}

3. JobScheduler

把Service改为用JobScheduler实现,详细可以参考:Android(O) 8.0 怎样在后台启动service

4. 白名单

  • 有Persistent权限
  • 后台运行白名单
  • 电源白名单

从上面startService限制机制第四点,ActivityManagerService启动服务校验逻辑可以看出,如果应用具有Persistent权限或在白名单中,就有后台运行服务的特权,可以避免此限制。如果应用是直接集成在系统应用中,则可以配置Persistent权限或配置白名单解决。

四、其他参考资料

Android官网后台执行限制

Android(O) 8.0 怎样在后台启动service

Android 8.0 + Service开启方式兼容处理

Android O 行为变更适配方案

Android 8.0 Service源码分析:启动流程及后台限制详解

扩展阅读:

Android ANR详解

Android 子线程更新UI详解

Android Home键之后后台启动Activity延迟5秒

转载请注明出处:陈文管的博客 – Android 8(Oreo)后台启动Service限制解析

扫码或搜索:文呓

博客公众号

微信公众号 扫一扫关注

文章目录

  • 一、Android Oreo后台启动服务异常
    • 1. 使用startService在后台启动服务
    • 2. startForeground没调用或超时调用
  • 二、异常机制解析
    • 1. Android 8.1.0 startService限制机制
      • 1)ContextImpl.java
      • 2)ActivityManagerService.java
      • 3)ActiveServices.java
      • 4)ActivityManagerService启动服务校验
    • 2. startForeground调用异常机制
      • 1)ContextImpl.java
      • 2)ActiveServices.java
  • 三、异常解决方法
    • 1. startService限制解决方法
    • 2. startForeground异常解决方法
    • 3. JobScheduler
    • 4. 白名单
  • 四、其他参考资料
博客公众号

闽ICP备18001825号-1 · Copyright © 2025 · Powered by chenwenguan.com