Android8.0绕过后台启动服务的限制

Android8.0绕过后台启动服务的限制

看完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这个集合里,等会就会马上把它干掉:
在这里插入图片描述