问题描述
从 Android 9 开始,如果从 后台 调用 startService()
,它会抛出一个 IllegalStateException。我在开发者控制台中多次看到此异常:
java.lang.IllegalStateException:
at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1666)
at android.app.ContextImpl.startService (ContextImpl.java:1611)
在这些情况下,Google 建议改为在 5 秒内调用 startForegroundService()
和 startForeground()
。 See "Background execution limits"。
无论如何,从 前台 调用 startService()
是完全可以的。现在,我想知道 Android 究竟是如何识别/决定应用程序在前台而不是错误地抛出 IllegalStateException 的?
我开始挖掘 Android9/10 的源代码并将其与 8/7 进行比较,以发现如何修改 startService()
以识别它是否是从前台/后台调用的。但我相信在我之前的许多开发人员已经这样做了,如果他们能给出答案,我会很高兴。
解决方法
在 AOSP10 (10.0.0_r25) 中:
服务器端:
在来自 frameworks\base\services\core\java\com\android\server\am\ActiveServices.java 的 startServiceLocked 中:
// 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,forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG,"Background start not allowed: service "
+ service + " to " + r.shortInstanceName
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage + " startFg?=" + fgRequired);
......
// This app knows it is in the new model where this operation is not
// allowed,so tell it what has happened.
UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
return new ComponentName("?","app is in background uid " + uidRec);
}
然后在客户端:
在 ContextImpl.java 中作为您的日志:
else if (cn.getPackageName().equals("?")) {
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
,
在 this link into Android's soure code 之后,我们找到 getAppStartModeLocked()
:
int getAppStartModeLocked(int uid,String packageName,int packageTargetSdk,int callingPid,boolean alwaysRestrict,boolean disabledOnly) {
UidRecord uidRec = mActiveUids.get(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG,"checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
+ (uidRec != null ? uidRec.idle : false));
if (uidRec == null || alwaysRestrict || uidRec.idle) {
boolean ephemeral;
if (uidRec == null) {
ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
UserHandle.getUserId(uid),packageName);
} else {
ephemeral = uidRec.ephemeral;
}
if (ephemeral) {
// We are hard-core about ephemeral apps not running in the background.
return ActivityManager.APP_START_MODE_DISABLED;
} else {
if (disabledOnly) {
// The caller is only interested in whether app starts are completely
// disabled for the given package (that is,it is an instant app). So
// we don't need to go further,which is all just seeing if we should
// apply a "delayed" mode for a regular app.
return ActivityManager.APP_START_MODE_NORMAL;
}
final int startMode = (alwaysRestrict)
? appRestrictedInBackgroundLocked(uid,packageName,packageTargetSdk)
: appServicesRestrictedInBackgroundLocked(uid,packageTargetSdk);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG,"checkAllowBackground: uid=" + uid
+ " pkg=" + packageName + " startMode=" + startMode
+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid));
if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
// This is an old app that has been forced into a "compatible as possible"
// mode of background check. To increase compatibility,we will allow other
// foreground apps to cause its services to start.
if (callingPid >= 0) {
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}
if (proc != null &&
!ActivityManager.isProcStateBackground(proc.curProcState)) {
// Whoever is instigating this is in the foreground,so we will allow it
// to go through.
return ActivityManager.APP_START_MODE_NORMAL;
}
}
}
return startMode;
}
}
return ActivityManager.APP_START_MODE_NORMAL;
}
方法 appRestrictedInBackgroundLocked()
(也从 appServicesRestrictedInBackgroundLocked()
调用作为回退)决定 startMode
:
// Unified app-op and target sdk check
int appRestrictedInBackgroundLocked(int uid,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;
}
}
但是关于前景或背景的最终决定是在 ActivityManager.isProcStateBackground(uidRec.setProcState)
中完成的:
/** @hide Should this process state be considered a background state? */
public static final boolean isProcStateBackground(int procState) {
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
所以,这里第一个方法的这一部分获取了前景或背景的当前状态:
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}
,
当应用出现以下情况时,将允许从后台启动。
- 该应用具有持久性。只有预构建的系统应用可以执行此操作。
- 该应用不是 idle。当应用进程处于 background 一定时间后,它会被设置为空闲。
- 该应用处于空闲状态,但在白名单中。 backgroundWhitelistUid。只有具有系统 uid 的应用才能将应用添加到此列表中。