1. 程式人生 > >Android8.0繞過後臺啟動服務的限制

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這個集合裡,等會就會馬上把它幹掉:
在這裡插入圖片描述