本文针对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 8.0 Service源码分析:启动流程及后台限制详解
扩展阅读:
Android Home键之后后台启动Activity延迟5秒
转载请注明出处:陈文管的博客 – Android 8(Oreo)后台启动Service限制解析
扫码或搜索:文呓
微信公众号 扫一扫关注