Android 后臺(tái)限制啟動(dòng)Service、Activity與Notification、PendingIntent淺析

Android O之后,很多后臺(tái)啟動(dòng)的行為都開(kāi)始受限,比如O的時(shí)候,不能后臺(tái)啟動(dòng)Service,而在Android10之后,連Activity也加到了后臺(tái)限制中。在Android O 后臺(tái)startService限制簡(jiǎn)析中,層分析Android O之后,后臺(tái)限制啟動(dòng)Service的場(chǎng)景,一般而言,APP退到后臺(tái)(比如按Home鍵),1分鐘之后變?yōu)楹笈_(tái)APP,雖然進(jìn)程存活,但是已經(jīng)不能通過(guò)startService啟動(dòng)服務(wù),但是發(fā)送通知并不受限制,可以通過(guò)通知啟動(dòng)Service,這個(gè)時(shí)候,Service不會(huì)被當(dāng)做后臺(tái)啟動(dòng),同樣通過(guò)通知欄打開(kāi)Activity也不受限制? 為什么,直觀來(lái)講,通知已經(jīng)屬于用戶感知的交互,本就不應(yīng)該算到后臺(tái)啟動(dòng)。本文先發(fā)對(duì)比之前的Android O 后臺(tái)startService限制簡(jiǎn)析,分析下Service,之后再看Activity在Android10中的限制

本文基于android10-release

通知借助PendingIntent啟動(dòng)Service

可以模擬這樣一個(gè)場(chǎng)景,發(fā)送一個(gè)通知,然后將APP殺死,之后在通知欄通過(guò)PendingIntent啟動(dòng)Service,看看是否會(huì)出現(xiàn)禁止后臺(tái)啟動(dòng)Service的場(chǎng)景。

void notify() {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
    builder.setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis(),
            new Intent(this,
                    BackGroundService.class),
            PendingIntent.FLAG_UPDATE_CURRENT))
            .setContentText("content")...)  

    NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
                "Channel human readable title",
                NotificationManager.IMPORTANCE_DEFAULT);
        if (nm != null) {
            nm.createNotificationChannel(channel);
        }
    }
    nm.notify(1, builder.build());
}

實(shí)際結(jié)果是:點(diǎn)擊通知后Service正常啟動(dòng)。下面逐步分析下。

同普通的Intent啟動(dòng)Service不同,這里的通知通過(guò)PendingIntent啟動(dòng),是不是只要PendingIntent就足夠了呢,并不是(后面分析)。通過(guò)通知啟動(dòng)Service的第一步是通過(guò)PendingIntent.getService獲得一個(gè)用于啟動(dòng)特定Service的PendingIntent:

    public static PendingIntent getService(Context context, int requestCode,
            @NonNull Intent intent, @Flags int flags) {
        return buildServicePendingIntent(context, requestCode, intent, flags,
                ActivityManager.INTENT_SENDER_SERVICE);
     }

    private static PendingIntent buildServicePendingIntent(Context context, int requestCode,
        Intent intent, int flags, int serviceKind) {
    String packageName = context.getPackageName();
    String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
            context.getContentResolver()) : null;
    try {
        intent.prepareToLeaveProcess(context);
        IIntentSender target =
            ActivityManager.getService().getIntentSender(
                serviceKind, packageName,
                null, null, requestCode, new Intent[] { intent },
                resolvedType != null ? new String[] { resolvedType } : null,
                flags, null, context.getUserId());
        return target != null ? new PendingIntent(target) : null;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

IIntentSender在APP端其實(shí)是一個(gè)Binder代理,這里是典型的Binder雙向通信模型,AMS端會(huì)為APP構(gòu)建一個(gè)PendingIntentRecord extends IIntentSender.Stub實(shí)體, PendingIntentRecord可以看做PendingIntent在AMS端的記錄,最終形成兩者對(duì)應(yīng)的雙向通信通道。之后通知就會(huì)通過(guò)nm.notify顯示在通知欄,這一步先略過(guò),先看最后一步,通過(guò)點(diǎn)擊通知啟動(dòng)Service,通知點(diǎn)擊這不細(xì)看,只要明白最后調(diào)用的是PendingIntent的sendAndReturnResult函數(shù),

public int sendAndReturnResult(Context context, int code, @Nullable Intent intent,
        @Nullable OnFinished onFinished, @Nullable Handler handler,
        @Nullable String requiredPermission, @Nullable Bundle options)
        throws CanceledException {
    try {
        String resolvedType = intent != null ?
                intent.resolveTypeIfNeeded(context.getContentResolver())
                : null;
        return ActivityManager.getService().sendIntentSender(
                mTarget, mWhitelistToken, code, intent, resolvedType,
                onFinished != null
                        ? new FinishedDispatcher(this, onFinished, handler)
                        : null,
                requiredPermission, options);
    } catch (RemoteException e) {
        throw new CanceledException(e);
    }
}

通過(guò)Binder最終到AMS端,查找到對(duì)應(yīng)的PendingIntentRecord,進(jìn)入其sendInner函數(shù),前文buildIntent的時(shí)候,用的是 ActivityManager.INTENT_SENDER_SERVICE,進(jìn)入對(duì)應(yīng)分支:

public int sendInner(int code, Intent intent, String resolvedType, IBinder whitelistToken,
        IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,
        String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) {


            if (whitelistDuration != null) {
              duration = whitelistDuration.get(whitelistToken);
            }
             <!--是否可以啟動(dòng)的一個(gè)關(guān)鍵點(diǎn) ,后面分析-->
            int res = START_SUCCESS;
            try {
            <!--duration非null才會(huì)執(zhí)行tempWhitelistForPendingIntent添加到白名單-->
                if (duration != null) {
                    int procState = controller.mAmInternal.getUidProcessState(callingUid);
                    
                    <!--u0_a16   2102  1742 4104448 174924 0    0 S com.android.systemui 通知是systemui進(jìn)程 優(yōu)先級(jí)高沒(méi)后臺(tái)問(wèn)題-->
                    if (!ActivityManager.isProcStateBackground(procState)) {
                        ...
                        <!--更新臨時(shí)白名單, duration設(shè)定白名單的有效時(shí)長(zhǎng),這個(gè)是在發(fā)通知的時(shí)候設(shè)定的-->
                        controller.mAmInternal.tempWhitelistForPendingIntent(callingPid, callingUid,
                                uid, duration, tag.toString());
                    } else {
                    }
                }
 
                ...
            case ActivityManager.INTENT_SENDER_SERVICE:
            case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
                try {
                    controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,
                            key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,
                            key.packageName, userId,
                            mAllowBgActivityStartsForServiceSender.contains(whitelistToken)
                            || allowTrampoline);
                } catch (RuntimeException e) {              ...
image

其實(shí)最后進(jìn)入controller.mAmInternal.startServiceInPackage,最后流到AMS的startServiceInPackage,接下來(lái)的流程在Android O 后臺(tái)startService限制簡(jiǎn)析分析過(guò),包括后臺(tái)限制的檢測(cè),不過(guò)這里有一點(diǎn)是前文沒(méi)分析的,

 int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
       ...        
       
       // Is this app on the battery whitelist?
        if (isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ false)) {
            return ActivityManager.APP_START_MODE_NORMAL;
        }

        // None of the service-policy criteria apply, so we apply the common criteria
        return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
    }

 */
boolean isOnDeviceIdleWhitelistLocked(int uid, boolean allowExceptIdleToo) {
    final int appId = UserHandle.getAppId(uid);

    final int[] whitelist = allowExceptIdleToo
            ? mDeviceIdleExceptIdleWhitelist
            : mDeviceIdleWhitelist;

    return Arrays.binarySearch(whitelist, appId) >= 0
            || Arrays.binarySearch(mDeviceIdleTempWhitelist, appId) >= 0
            || mPendingTempWhitelist.indexOfKey(uid) >= 0;
}

**那就是mPendingTempWhitelist白名單 **,這個(gè)是通知啟動(dòng)Service不受限制的關(guān)鍵。

image

前文說(shuō)過(guò),通知發(fā)送時(shí)會(huì)設(shè)定一個(gè)臨時(shí)白名單的有效存活時(shí)間,只有設(shè)置了,才能進(jìn)mPendingTempWhitelist,這是存活時(shí)間是從點(diǎn)擊到真正start中間所能存活的時(shí)間,如果在此間還未啟動(dòng),則判斷啟動(dòng)無(wú)效。有效存活時(shí)間是什么時(shí)候設(shè)置的,是發(fā)送通知的時(shí)候,而且,這個(gè)時(shí)機(jī)只在發(fā)送通知的時(shí)候,其他沒(méi)入口

  /Users/XXX/server/notification/NotificationManagerService.java:
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
        final int callingPid, final String tag, final int id, final Notification notification,
        int incomingUserId) {
        ...
    // Whitelist pending intents.
    if (notification.allPendingIntents != null) {
        final int intentCount = notification.allPendingIntents.size();
        if (intentCount > 0) {
            final ActivityManagerInternal am = LocalServices
                    .getService(ActivityManagerInternal.class);
            final long duration = LocalServices.getService(
                    DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
            for (int i = 0; i < intentCount; i++) {
                PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                if (pendingIntent != null) {
                <!--更新白名單機(jī)制的一環(huán) ,只有通過(guò)這個(gè)檢測(cè)才能加到mPendingTempWhitelist白名單-->
                    am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
                            WHITELIST_TOKEN, duration);
                }
            }
        }
    }

setPendingIntentWhitelistDuration會(huì)更新PendingIntentRecord的whitelistDuration列表,這個(gè)列表標(biāo)識(shí)著這個(gè)

    public void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whitelistToken,
            long duration) {

        synchronized (ActivityManagerService.this) {
            ((PendingIntentRecord) target).setWhitelistDurationLocked(whitelistToken, duration);
        }
    }
     
void setWhitelistDurationLocked(IBinder whitelistToken, long duration) {
    if (duration > 0) {
        if (whitelistDuration == null) {
            whitelistDuration = new ArrayMap<>();
        }
        <!--設(shè)置存活時(shí)長(zhǎng)-->
        whitelistDuration.put(whitelistToken, duration);
    }  ...
}

存活時(shí)長(zhǎng)設(shè)置后,通過(guò)點(diǎn)擊,啟動(dòng)Service Intent就會(huì)被放到mPendingTempWhitelist,從而避免后臺(tái)檢測(cè)。如果不走通知,直接用PendingIntent的send呢,效果其實(shí)跟普通Intent沒(méi)太大區(qū)別,也會(huì)受后臺(tái)啟動(dòng)限制,不過(guò)多分析。

Android10后臺(tái)啟動(dòng)Activity限制 (android10-release源碼分支)

Android10之后,禁止后臺(tái)啟動(dòng)Activity,Activity的后臺(tái)定義比Service更嚴(yán)格,延時(shí)10s,退到后臺(tái),便可以模擬后臺(tái)啟動(dòng)Activity,注意這里并沒(méi)有像Service限定到60之后,Activity的后臺(tái)限制更嚴(yán)格一些,直觀上理解:沒(méi)有可見(jiàn)窗口都可以算作后臺(tái),中間的間隔最多可能就幾秒,比如我們延時(shí)10s就能看到這種效果。

void delayStartActivity() {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            Intent intent = new Intent(LabApplication.getContext(), MainActivity.class);
            startActivity(intent);
        }
    }, 1000 * 10);

}

時(shí)間到了,在Android Q的手機(jī)上startActivity會(huì)報(bào)如下異常:

Background activity start [callingPackage: com.snail.labaffinity; callingUid: 10102; 
        
    *            isCallingUidForeground: false; 
    *            isCallingUidPersistentSystemProcess: false; 
    *            realCallingUid: 10102; 
    *            sRealCallingUidForeground: false; 
    *            isRealCallingUidPersistentSystemProcess: false; 
    *            originatingPendingIntent: null; 
    *            isBgStartWhitelisted: false; 

 intent: Intent { cmp=com.snail.labaffinity/.activity.MainActivity }; callerApp: ProcessRecord{f17cc20 4896:com.snail.labaffinity/u0a102}]

未正式發(fā)行的版本上還能看到如下Toast

image

大概意思就是:限制后臺(tái)應(yīng)用啟動(dòng)Activity。

核心邏輯在這一段 ActivityStarter

 boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
            final String callingPackage, int realCallingUid, int realCallingPid,
            WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart, Intent intent) {
         <!--系統(tǒng)應(yīng)用不受限制-->
        // don't abort for the most important UIDs
        final int callingAppId = UserHandle.getAppId(callingUid);
        if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
                || callingAppId == Process.NFC_UID) {
            return false;
        }
        <!--有可見(jiàn)窗口及系統(tǒng)進(jìn)程不受限制-->
        // don't abort if the callingUid has a visible window or is a persistent system process
        final int callingUidProcState = mService.getUidState(callingUid);
        <!--是否有可見(jiàn)窗口-->
        final boolean callingUidHasAnyVisibleWindow =
                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
        <!--CallingUid是否前臺(tái)展示-->
        final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
                || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
         <!--是否PersistentSystemProcess-->
        final boolean isCallingUidPersistentSystemProcess =
                callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
            return false;
        }
        // take realCallingUid into consideration
        final int realCallingUidProcState = (callingUid == realCallingUid)
                ? callingUidProcState
                : mService.getUidState(realCallingUid);
        final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
                ? callingUidHasAnyVisibleWindow
                : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid);
        final boolean isRealCallingUidForeground = (callingUid == realCallingUid)
                ? isCallingUidForeground
                : realCallingUidHasAnyVisibleWindow
                        || realCallingUidProcState == ActivityManager.PROCESS_STATE_TOP;
        final int realCallingAppId = UserHandle.getAppId(realCallingUid);
        final boolean isRealCallingUidPersistentSystemProcess = (callingUid == realCallingUid)
                ? isCallingUidPersistentSystemProcess
                : (realCallingAppId == Process.SYSTEM_UID)
                        || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        ...
        <!--這個(gè)權(quán)限不一定是誰(shuí)都能拿到-->
        // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
        if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                == PERMISSION_GRANTED) {
            return false;
        }
        // don't abort if the caller has the same uid as the recents component
        if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
            return false;
        }
        
        ...一些系統(tǒng)判斷
        
        <!--是否白名單-->
        // don't abort if the callerApp or other processes of that uid are whitelisted in any way
        
        if (callerApp != null) {
            // first check the original calling process
            if (callerApp.areBackgroundActivityStartsAllowed()) {
                return false;
            }
            // only if that one wasn't whitelisted, check the other ones
            final ArraySet<WindowProcessController> uidProcesses =
                    mService.mProcessMap.getProcesses(callerAppUid);
            if (uidProcesses != null) {
                for (int i = uidProcesses.size() - 1; i >= 0; i--) {
                    final WindowProcessController proc = uidProcesses.valueAt(i);
                    if (proc != callerApp && proc.areBackgroundActivityStartsAllowed()) {
                        return false;
                    }
                }
            }
        }
        <!--如果callAPP有懸浮窗權(quán)限-->
        // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
            Slog.w(TAG, "Background activity start for " + callingPackage
                    + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return false;
        }
        <!--其余全部禁止-->
        // anything that has fallen through would currently be aborted
        Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                + "; callingUid: " + callingUid
                + "; isCallingUidForeground: " + isCallingUidForeground
                + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
                + "; realCallingUid: " + realCallingUid
                + "; isRealCallingUidForeground: " + isRealCallingUidForeground
                + "; isRealCallingUidPersistentSystemProcess: "
                + isRealCallingUidPersistentSystemProcess
                + "; originatingPendingIntent: " + originatingPendingIntent
                + "; isBgStartWhitelisted: " + allowBackgroundActivityStart
                + "; intent: " + intent
                + "; callerApp: " + callerApp
                + "]");
        // log aborted activity start to TRON
        if (mService.isActivityStartsLoggingEnabled()) {
            mSupervisor.getActivityMetricsLogger().logAbortedBgActivityStart(intent, callerApp,
                    callingUid, callingPackage, callingUidProcState, callingUidHasAnyVisibleWindow,
                    realCallingUid, realCallingUidProcState, realCallingUidHasAnyVisibleWindow,
                    (originatingPendingIntent != null));
        }
        return true;
    }

按照Google要求,在Android Q上運(yùn)行的應(yīng)用只有在滿足以下一個(gè)或多個(gè)條件時(shí)才能啟動(dòng)Activity:常見(jiàn)的有如下幾種

  • 具有可見(jiàn)窗口,例如在前臺(tái)運(yùn)行的Activity。(前臺(tái)服務(wù)不會(huì)將應(yīng)用限定為在前臺(tái)運(yùn)行。)

  • 該應(yīng)用在前臺(tái)任務(wù)的返回棧中具有一項(xiàng) Activity。(必須同前臺(tái)Activity位于同一個(gè)Task返回棧,如果兩個(gè)Task棧不行。)

  • 該應(yīng)用已獲得用戶授予的 SYSTEM_ALERT_WINDOW 權(quán)限。

  • pendingIntent臨時(shí)白名單機(jī)制,不攔截通過(guò)通知拉起的應(yīng)用。

      通過(guò)通知,利用pendingIntent啟動(dòng) Activity。
      通過(guò)通知,在 PendingIntent中發(fā)送廣播,接收廣播后啟動(dòng) Activity。
      通過(guò)通知,在 PendingIntent中啟動(dòng) Service(一定可以啟動(dòng)Service),在 Service 中啟動(dòng) Activity。
    
  • 該應(yīng)用的某一項(xiàng)服務(wù)被其他可見(jiàn)應(yīng)用綁定(進(jìn)程優(yōu)先級(jí)其實(shí)一致)。請(qǐng)注意,綁定到該服務(wù)的應(yīng)用必須在后臺(tái)對(duì)該應(yīng)用保持可見(jiàn),才能成功啟動(dòng) Activity。

這里有一個(gè)比較有趣的點(diǎn):如果應(yīng)用在前臺(tái)任務(wù)的返回棧中具有一項(xiàng)Activity,并不是說(shuō)一定要自己APP的Activity在展示,而是說(shuō),當(dāng)前展示的Task棧里有自己的Activity就可以,這點(diǎn)判斷如下

  boolean areBackgroundActivityStartsAllowed() {
        
        <!--白名單-->
        // allow if the whitelisting flag was explicitly set
        if (mAllowBackgroundActivityStarts) {
            return true;
        }
        
        ...
       <!--是否有Actvity位于前臺(tái)任務(wù)棧中-->
        // allow if the caller has an activity in any foreground task
        if (hasActivityInVisibleTask()) {
            return true;
        }
        <!--被前臺(tái)APP綁定-->
        // allow if the caller is bound by a UID that's currently foreground
        if (isBoundByForegroundUid()) {
            return true;
        }
        return false;
    }

hasActivityInVisibleTask 判斷前臺(tái)TASK棧是否有CallAPP的Activity

private boolean hasActivityInVisibleTask() {
    for (int i = mActivities.size() - 1; i >= 0; --i) {
        TaskRecord task = mActivities.get(i).getTaskRecord();
        if (task == null) {
            continue;
        }
        ActivityRecord topActivity = task.getTopActivity();
        if (topActivity == null) {
            continue;
        }
        // If an activity has just been started it will not yet be visible, but
        // is expected to be soon. We treat this as if it were already visible.
        // This ensures a subsequent activity can be started even before this one
        // becomes visible.
        
        <!--只要是Task中的TOPActivity在展示,就判斷CallAPP可見(jiàn)或者即將可見(jiàn),TOPActivity不一定是CallAPP的-->
        if (topActivity.visible || topActivity.isState(INITIALIZING)) {
            return true;
        }
    }
    return false;
}

只要是Task中的TOPActivity在展示,就判斷CallAPP可見(jiàn)或者即將可見(jiàn),TOPActivity不一定是CallAPP的,比如APP打開(kāi)微信分享,如果直接上看APP是在后臺(tái),但是微信分享Activity沒(méi)有單獨(dú)開(kāi)一Activity Task,那么CallAPP還是被看做前臺(tái),也就是他還可以啟動(dòng)Activity,在前后臺(tái)的判斷上,更像下沉到Task維度,而不是Activity維度。同Service不同,Activity嚴(yán)重依賴CallAPP的狀態(tài),而Service更關(guān)心被啟動(dòng)APP的狀態(tài)。

Android10后臺(tái)限制啟動(dòng)Activity的系統(tǒng)bug

連續(xù)兩次啟動(dòng)Activity,后臺(tái)啟動(dòng)的限制會(huì)被打破

private boolean hasActivityInVisibleTask() {
    for (int i = mActivities.size() - 1; i >= 0; --i) {
        TaskRecord task = mActivities.get(i).getTaskRecord();
        if (task == null) {
            continue;
        }
        ActivityRecord topActivity = task.getTopActivity();
        if (topActivity == null) {
            continue;
        }
        <!--bug起源-->
        // If an activity has just been started it will not yet be visible, but
        // is expected to be soon. We treat this as if it were already visible.
        // This ensures a subsequent activity can be started even before this one
        // becomes visible.
        if (topActivity.visible || topActivity.isState(INITIALIZING)) {
            return true;
        }
    }
    return false;
}

如果應(yīng)用位于后臺(tái),第一次啟動(dòng)Activity會(huì)被當(dāng)做后臺(tái)啟動(dòng),但是ActiivityRecord仍然會(huì)被創(chuàng)建,同時(shí)State會(huì)被設(shè)置成INITIALIZING,并且位于當(dāng)前將要啟動(dòng)Task的棧頂,

  ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller,
            int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage, Intent _intent,
           ...
        setState(INITIALIZING, "ActivityRecord ctor");

那么如果在后臺(tái),再次通過(guò)startActivity啟動(dòng),當(dāng)前進(jìn)程就會(huì)被認(rèn)為是在前臺(tái),應(yīng)用就會(huì)被拉起,真是個(gè)奇葩bug。因?yàn)闈M足如下條件。

 topActivity.isState(INITIALIZING)

這個(gè)時(shí)候,Activity就可以在后臺(tái)被啟動(dòng)。其實(shí)Android10后臺(tái)限制啟動(dòng)Activity的并非完全不讓啟動(dòng),只是延遲,再次APP可見(jiàn)的時(shí)候,依舊可以把之前未啟動(dòng)的Activity喚起。

PS :更新日期2019-12-12:谷歌的補(bǔ)丁似乎修復(fù)了這個(gè)bug

image.png

PendingIntent啟動(dòng)Activity不受限制原理

通知的進(jìn)程是系統(tǒng)進(jìn)程

u0_a16        2102  1742 4104448 174924 0                   0 S com.android.systemui

系統(tǒng)進(jìn)程不受限制,就是這么流弊。

通知啟動(dòng)Service,然后在Service中是允許啟動(dòng)Activity不受后臺(tái)限制(奇葩)

對(duì)于通過(guò)PendingIntent通知啟動(dòng)的APP,短時(shí)間內(nèi)不算后臺(tái)啟動(dòng)Activity

image

從上面的注釋就能看出來(lái),如果是通過(guò)通知啟動(dòng)的,或者說(shuō)如果是前臺(tái)應(yīng)用觸發(fā)的sendInner,那么短時(shí)間內(nèi)允許啟動(dòng)Activity,雖然是通過(guò)Service啟動(dòng),但是如果是通知啟動(dòng)的Service,那么暫且算是看做應(yīng)用位于前臺(tái),如下:

image

先更新一個(gè)標(biāo)識(shí)mHasStartedWhitelistingBgActivityStarts,就是是否允許Service后臺(tái)啟動(dòng)Activity的標(biāo)識(shí),這里是設(shè)置為true,此刻進(jìn)程可能還未啟動(dòng),

// is this service currently whitelisted to start activities from background by providing
// allowBackgroundActivityStarts=true to startServiceLocked()?
private boolean mHasStartedWhitelistingBgActivityStarts;

等到后面進(jìn)程啟動(dòng)了在attach的時(shí)候會(huì)繼續(xù)走Service的啟動(dòng)流程

image

這里因?yàn)閙HasStartedWhitelistingBgActivityStarts被設(shè)置為true,

image

就會(huì)走setAllowBackgroundActivityStarts 將mAllowBackgroundActivityStarts設(shè)置為true

public void setAllowBackgroundActivityStarts(boolean allowBackgroundActivityStarts) {
    mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
}

這樣在啟動(dòng)Activity時(shí)候,判斷是否允許后臺(tái)啟動(dòng)就直接返回true

boolean areBackgroundActivityStartsAllowed() {
    // allow if the whitelisting flag was explicitly set
    if (mAllowBackgroundActivityStarts) {
        return true;
    }

這樣就構(gòu)建了允許后臺(tái)啟動(dòng)Activity的場(chǎng)景,這個(gè)時(shí)限是10秒,10秒內(nèi)啟動(dòng)Activity保證沒(méi)問(wèn)題。

// For how long after a whitelisted service's start its process can start a background activity
public long SERVICE_BG_ACTIVITY_START_TIMEOUT = DEFAULT_SERVICE_BG_ACTIVITY_START_TIMEOUT;

因?yàn)橹皢?dòng)的時(shí)候,加了一個(gè)10s清理的監(jiān)聽(tīng)回調(diào)

   ams.mHandler.postDelayed(mStartedWhitelistingBgActivityStartsCleanUp,
            ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);

到10s的時(shí)候回再次檢查一下是否需要清理掉,但是并非一定清理掉。

/**
 * Called when the service is started with allowBackgroundActivityStarts set. We whitelist
 * it for background activity starts, setting up a callback to remove the whitelisting after a
 * timeout. Note that the whitelisting persists for the process even if the service is
 * subsequently stopped.
 */
void whitelistBgActivityStartsOnServiceStart() {
    setHasStartedWhitelistingBgActivityStarts(true);
    if (app != null) {
        mAppForStartedWhitelistingBgActivityStarts = app;
    }

    // This callback is stateless, so we create it once when we first need it.
    if (mStartedWhitelistingBgActivityStartsCleanUp == null) {
        mStartedWhitelistingBgActivityStartsCleanUp = () -> {
            synchronized (ams) {
            <!--如果Service進(jìn)程存活,直接將start部分清理,但是bind部分需要再確認(rèn)-->
                if (app == mAppForStartedWhitelistingBgActivityStarts) {
                    // The process we whitelisted is still running the service. We remove
                    // the started whitelisting, but it may still be whitelisted via bound
                    // connections.
                    setHasStartedWhitelistingBgActivityStarts(false);
                } else  if (mAppForStartedWhitelistingBgActivityStarts != null) {
                <!--如果進(jìn)程死了,10s還沒(méi)到,進(jìn)程就掛了,那么直接全部干掉,不考慮ind-->
                    // The process we whitelisted is not running the service. It therefore
                    // can't be bound so we can unconditionally remove the whitelist.
                    mAppForStartedWhitelistingBgActivityStarts
                            .removeAllowBackgroundActivityStartsToken(ServiceRecord.this);
                }
                mAppForStartedWhitelistingBgActivityStarts = null;
            }
        };
    }

    // if there's a request pending from the past, drop it before scheduling a new one
    ams.mHandler.removeCallbacks(mStartedWhitelistingBgActivityStartsCleanUp);
    ams.mHandler.postDelayed(mStartedWhitelistingBgActivityStartsCleanUp,
            ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
}

總結(jié)

  • 通過(guò)通知啟動(dòng)Service不受后臺(tái)限制的原因是存在可更新PendingTempWhitelist白名單
  • 后臺(tái)啟動(dòng)Activity嚴(yán)重依賴CallAPP的狀態(tài),而Service更關(guān)心被啟動(dòng)APP的狀態(tài)
  • 位于后臺(tái),連續(xù)多次startActivity就可以啟動(dòng)Activity,目前看是個(gè)系統(tǒng)bug (2019-12-12實(shí)驗(yàn) 新補(bǔ)丁似乎修復(fù)了這個(gè)功能 ,但是國(guó)內(nèi)ROM可能還有這個(gè)問(wèn)題(如果不更新的話))
  • Android10后臺(tái)限制啟動(dòng)Activity的并非完全不讓啟動(dòng),只是延遲,再次APP可見(jiàn)的時(shí)候,依舊可以把之前未啟動(dòng)的Activity喚起。
  • 通過(guò)通知啟動(dòng)Service,Service內(nèi)部不10s內(nèi)是允許后臺(tái)啟動(dòng)Activity的,超過(guò)十秒就可能掛了

作者:看書的小蝸牛

AAndroid Notification、PendingIntent與后臺(tái)啟動(dòng)Service、Activity淺析

僅供參考,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容