看完startService的源码之后发现,只要我们的targetSDK设置成小于26的依然还是可以在8.0的手机上后台启动service的。来简单看下源码吧:
ContextImpl$startServiceCommon:
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) {
// ...
// 8.0其实就是这里加了个判断。
} else if (cn.getPackageName().equals("?")) {
throw new IllegalStateException(
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
}
ActivityManagerService$startService:
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
// ...
ComponentName res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
return res;
}
}
ActiveServices$startServiceLocked:
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
// ...
// retrieve是取回的意思
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false);
// ...
ServiceRecord r = res.record;
// If this isn't a direct-to-foreground start, check our ability to kick off an
// 这个方法是不允许后台启动服务的关键,第一次启动一个
// service会进来,因为r是刚new出来的,r.startRequested一定是false,其他还有地方把r.startRequested置为false的状态,这个状态很重要。
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) {
if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
// 如果是这种情况,不会启动服务而且什么提示都没有.
return null;
}
UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
// 只有这个地方才会返回ComponentName("?")
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
// .... 如果那里没被返回ComponentName("?"),这里会被置为true。
r.startRequested = true;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, neededGrants, callingUid));
final ServiceMap smap = getServiceMapLocked(r.userId);
boolean addToStarting = false;
// ...
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
return cmp;
}
ActiveServices$retrieveServiceLocked
private ServiceLookupResult retrieveServiceLocked(Intent service,
String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded = true, boolean callingFromFg = false, boolean isBindExternal = false) {
ServiceRecord r = null;
if (r == null) {
// ...
if (r == null && createIfNeeded) {
final Intent.FilterComparison filter
= new Intent.FilterComparison(service.cloneFilter());
final ServiceRestarter res = new ServiceRestarter();
// ...
// 创建的时候并没有设置startRequested这个变量,所以默认是false的。
r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
res.setService(r);
smap.mServicesByName.put(name, r);
smap.mServicesByIntent.put(filter, r);
}
}
if (r != null) {
// ... 这里省略的部分都是检验权限之类的
return new ServiceLookupResult(r, null);
}
return null;
}
ActivityManagerService$getAppStartModeLocked:
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict = false, boolean disabledOnly = false) {
UidRecord uidRec = mActiveUids.get(uid);
// 如果是在前台启动service,就不会进这里了,主要还是uidRec.idle这个值。
if (uidRec == null || alwaysRestrict || uidRec.idle) {
// ...
if (disabledOnly) {
return ActivityManager.APP_START_MODE_NORMAL;
}
final int startMode = (alwaysRestrict)
? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
: appServicesRestrictedInBackgroundLocked(uid, packageName,
packageTargetSdk);
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;
}
ActivityManagerService$appServicesRestrictedInBackgroundLocked:
这个方法主要是检查是否系统应用,白名单等
int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Persistent app?
if (mPackageManagerInt.isPackagePersistent(packageName)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// Non-persistent but background whitelisted?
if (uidOnBackgroundWhitelist(uid)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// Is this app on the battery whitelist?
if (isOnDeviceIdleWhitelistLocked(uid)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// None of the service-policy criteria apply, so we apply the common criteria
return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
ActivityManagerService$appRestrictedInBackgroundLocked:这个方法是关键
// 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
// 最重要的就是这里了,如果targerSDK >=26,才会返回不允许状态。
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);
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;
}
}
AppOpsService$noteOperation:
@Override
public int noteOperation(int code, int uid, String packageName) {
// ...
return noteOperationUnchecked(code, uid, resolvedPackageName, 0, null);
}
AppOpsService$noteOperationUnchecked
private int noteOperationUnchecked(int code, int uid, String packageName,
int proxyUid, String proxyPackageName) {
synchronized (this) {
Ops ops = getOpsRawLocked(uid, packageName, true);
Op op = getOpLocked(ops, code, true);
// private Op getOpLocked(Ops ops, int code, boolean edit = false) {
// Op op = ops.get(code);
// if (op == null) {
// ops是刚new出来的,肯定也没有code的值
// if (!edit) return null;
// op = new Op(ops.uidState.uid, ops.packageName, code);
// ops.put(code, op);
// }
// return op;
// }
// 这里其实是看是不是AppOpsManager一对一的那个权限列表,因为Op分为运行时权限和其他的一些
switchCode = code
final int switchCode = AppOpsManager.opToSwitch(code);
UidState uidState = ops.uidState;
// ...
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
// switchOp.mode是default mode,而OP_RUN_IN_BACKGROUND的default mode是allowed的
// 所以这里就返回了allowd的结果
if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
op.rejectTime = System.currentTimeMillis();
return switchOp.mode;
// ...
}
其他地方把ServiceRecord的startRequested置为false的情况:
①调用了stopService;
②service调用了stopSelf();
③系统杀掉了service。
总结就是service死掉之后这个状态就会被置为false.
一开始有这样的想法,既然成功启动过后startRequested会被置为true,那我们先在程序一打开的时候先去startService,后面在后台去调用的时候,不就不会进入那个分支了吗?但是行不通,因为系统会去杀掉后台的serivce.
8.0还有一个比较狠的是,启动了一个service,app退到后台后很快service会在短时间内(一分钟左右)就被系统干掉。原因是这样的,ActivityManagerService有个方法叫updateOomAdjLocked,用来计算进程adj值的,这个方法会被频繁调用,调用的时候还会发送一个调用idleUids 这个方法的广播,idleUids里面会去判断进程在后台的时间,接着会调用ActiveServices.stopInBackgroundLocked(uid);这个方法,把后台服务给杀掉,这个机制在8.0以前就有,但在8.0的时候是否要杀掉的逻辑变更了:
8.0以前是这样的:
这个很少进来的,一般是用户手动去设置里面不让这个进程在后台启动服务,就会进去那个分支。
而在8.0的时候就变成:
这个getAppStartModeLocked上面我们分析过了,只要targetSdk还是小于26那个分支就不会进去。一旦service进了stopping这个集合里,等会就会马上把它干掉: