Android 后臺限制啟動Service、Activity與Notification、PendingIntent淺析

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

本文基于android10-release

通知借助PendingIntent啟動Service

可以模擬這樣一個場景,發(fā)送一個通知,然后將APP殺死,之后在通知欄通過PendingIntent啟動Service,看看是否會出現(xiàn)禁止后臺啟動Service的場景。

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());
}

實際結(jié)果是:點擊通知后Service正常啟動。下面逐步分析下。

同普通的Intent啟動Service不同,這里的通知通過PendingIntent啟動,是不是只要PendingIntent就足夠了呢,并不是(后面分析)。通過通知啟動Service的第一步是通過PendingIntent.getService獲得一個用于啟動特定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端其實是一個Binder代理,這里是典型的Binder雙向通信模型,AMS端會為APP構(gòu)建一個PendingIntentRecord extends IIntentSender.Stub實體, PendingIntentRecord可以看做PendingIntent在AMS端的記錄,最終形成兩者對應(yīng)的雙向通信通道。之后通知就會通過nm.notify顯示在通知欄,這一步先略過,先看最后一步,通過點擊通知啟動Service,通知點擊這不細(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);
    }
}

通過Binder最終到AMS端,查找到對應(yīng)的PendingIntentRecord,進入其sendInner函數(shù),前文buildIntent的時候,用的是 ActivityManager.INTENT_SENDER_SERVICE,進入對應(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);
            }
             <!--是否可以啟動的一個關(guān)鍵點 ,后面分析-->
            int res = START_SUCCESS;
            try {
            <!--duration非null才會執(zhí)行tempWhitelistForPendingIntent添加到白名單-->
                if (duration != null) {
                    int procState = controller.mAmInternal.getUidProcessState(callingUid);
                    
                    <!--u0_a16   2102  1742 4104448 174924 0    0 S com.android.systemui 通知是systemui進程 優(yōu)先級高沒后臺問題-->
                    if (!ActivityManager.isProcStateBackground(procState)) {
                        ...
                        <!--更新臨時白名單, duration設(shè)定白名單的有效時長,這個是在發(fā)通知的時候設(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

其實最后進入controller.mAmInternal.startServiceInPackage,最后流到AMS的startServiceInPackage,接下來的流程在Android O 后臺startService限制簡析分析過,包括后臺限制的檢測,不過這里有一點是前文沒分析的,

 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白名單 **,這個是通知啟動Service不受限制的關(guān)鍵。

image

前文說過,通知發(fā)送時會設(shè)定一個臨時白名單的有效存活時間,只有設(shè)置了,才能進mPendingTempWhitelist,這是存活時間是從點擊到真正start中間所能存活的時間,如果在此間還未啟動,則判斷啟動無效。有效存活時間是什么時候設(shè)置的,是發(fā)送通知的時候,而且,這個時機只在發(fā)送通知的時候,其他沒入口

  /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) {
                <!--更新白名單機制的一環(huán) ,只有通過這個檢測才能加到mPendingTempWhitelist白名單-->
                    am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
                            WHITELIST_TOKEN, duration);
                }
            }
        }
    }

setPendingIntentWhitelistDuration會更新PendingIntentRecord的whitelistDuration列表,這個列表標(biāo)識著這個

    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è)置存活時長-->
        whitelistDuration.put(whitelistToken, duration);
    }  ...
}

存活時長設(shè)置后,通過點擊,啟動Service Intent就會被放到mPendingTempWhitelist,從而避免后臺檢測。如果不走通知,直接用PendingIntent的send呢,效果其實跟普通Intent沒太大區(qū)別,也會受后臺啟動限制,不過多分析。

Android10后臺啟動Activity限制 (android10-release源碼分支)

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

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

}

時間到了,在Android Q的手機上startActivity會報如下異常:

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

大概意思就是:限制后臺應(yī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;
        }
        <!--有可見窗口及系統(tǒng)進程不受限制-->
        // don't abort if the callingUid has a visible window or is a persistent system process
        final int callingUidProcState = mService.getUidState(callingUid);
        <!--是否有可見窗口-->
        final boolean callingUidHasAnyVisibleWindow =
                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
        <!--CallingUid是否前臺展示-->
        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;
        ...
        <!--這個權(quán)限不一定是誰都能拿到-->
        // 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īng)用只有在滿足以下一個或多個條件時才能啟動Activity:常見的有如下幾種

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

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

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

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

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

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

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

hasActivityInVisibleTask 判斷前臺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可見或者即將可見,TOPActivity不一定是CallAPP的-->
        if (topActivity.visible || topActivity.isState(INITIALIZING)) {
            return true;
        }
    }
    return false;
}

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

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

連續(xù)兩次啟動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;
        }
        <!--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)用位于后臺,第一次啟動Activity會被當(dāng)做后臺啟動,但是ActiivityRecord仍然會被創(chuàng)建,同時State會被設(shè)置成INITIALIZING,并且位于當(dāng)前將要啟動Task的棧頂,

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

那么如果在后臺,再次通過startActivity啟動,當(dāng)前進程就會被認(rèn)為是在前臺,應(yīng)用就會被拉起,真是個奇葩bug。因為滿足如下條件。

 topActivity.isState(INITIALIZING)

這個時候,Activity就可以在后臺被啟動。其實Android10后臺限制啟動Activity的并非完全不讓啟動,只是延遲,再次APP可見的時候,依舊可以把之前未啟動的Activity喚起。

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

image.png

PendingIntent啟動Activity不受限制原理

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

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

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

通知啟動Service,然后在Service中是允許啟動Activity不受后臺限制(奇葩)

對于通過PendingIntent通知啟動的APP,短時間內(nèi)不算后臺啟動Activity

image

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

image

先更新一個標(biāo)識mHasStartedWhitelistingBgActivityStarts,就是是否允許Service后臺啟動Activity的標(biāo)識,這里是設(shè)置為true,此刻進程可能還未啟動,

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

等到后面進程啟動了在attach的時候會繼續(xù)走Service的啟動流程

image

這里因為mHasStartedWhitelistingBgActivityStarts被設(shè)置為true,

image

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

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

這樣在啟動Activity時候,判斷是否允許后臺啟動就直接返回true

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

這樣就構(gòu)建了允許后臺啟動Activity的場景,這個時限是10秒,10秒內(nèi)啟動Activity保證沒問題。

// 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;

因為之前啟動的時候,加了一個10s清理的監(jiān)聽回調(diào)

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

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

/**
 * 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進程存活,直接將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) {
                <!--如果進程死了,10s還沒到,進程就掛了,那么直接全部干掉,不考慮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é)

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

作者:看書的小蝸牛

AAndroid Notification、PendingIntent與后臺啟動Service、Activity淺析

僅供參考,歡迎指正

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

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

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