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這個集合裡,等會就會馬上把它幹掉: