环境
在androidO上编译的项目,项目会监听锁屏/开屏的广播,调起独立进程的server,运行至此crash报错
报错关键字
Not allowed to start service Intent,app is in background uid UidRecord
分析
在androidO之前,从未发生过类似报错,初步怀疑是兼容性问题;从报错信息来看,能猜到因为app在一个后台的uid中导致不能开启一个服务;从栈信息来看很明显是startServiceAPI这个接口报的错;
那思路从追查日志报错的位置出发,ContextImpl方法startService
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
warnIfCallingFromSystemProcess();查看
/**
* Logs a warning if the system process directly called a method such as
* {@link #startService(Intent)} instead of {@link #startServiceAsUser(Intent, UserHandle)}.
* The "AsUser" variants allow us to properly enforce the user's restrictions.
*/
private void warnIfCallingFromSystemProcess() {
if (Process.myUid() == Process.SYSTEM_UID) {
Slog.w(TAG, "Calling a method in the system process without a qualified user: "
+ Debug.getCallers(5));
}
}
这个对应到crash信息中一个warning,主要是由于监听开锁屏广播,被调起的BroadcastReciver属于满足了Process.myUid() == Process.SYSTEM_UID,提示了一个waring.
startServiceCommon()查看
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("?")) {
//在这里看到了crash日志,错误类型也匹配,进入else的前提是cn.getPackageName().equals("?")
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
这个方法在调用的时候,就比androidO之前的版本多了一个boolean requireForeground参数,这是问题点。
return cn;cn是通过Binder方式跨进程获取的ActivityManagerNative.getDefault().startService,直接到ActivityManagerService中查看startService方法
@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;
}
}
mServices是ActiveServices的实例,查看startServiceLocked方法
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);
//在这里又看到另外一段日志,并且给要返回的compoentName赋值了一个?作为packageName了
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
…………
}
看一下进入条件是!r.startRequested && !fgRequired
r是ServiceRecord的实例,startRequested意义是否有明确的组件调用为flase。我们传入的fgRequired为false。
解决
问题的解决思路是把fgRequired改为true,就不会走到异常,返回一个package为"?"的ComponentName的对象,返回了正常的Component组件也不会出现crash的报错了。
AndroidO提供了一个新的接口调起Service
@Override
public ComponentName startForegroundService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, true, mUser);
}
这里调用startServiceCommon直接传true.在后续一系列的Binder调用中,直到分析问题的ActiveServices类startServiceLocked方法,requireForeground是AndroidO新加入的参数。
如下兼容就可以了如果要在低版本编译,需要反射调用startForegroundService
if (Build.VERSION.SDK_INT >= 26) {//Android8.0
context.startForegroundService(service);
} else {
context.startService(service);
}