Android Notification、PendingIntent與後臺啓動Service、Activity淺析

Android O以後,不少後臺啓動的行爲都開始受限,好比O的時候,不能後臺啓動Service,而在Android10以後,連Activity也加到了後臺限制中。在Android O 後臺startService限制簡析中,層分析Android O以後,後臺限制啓動Service的場景,通常而言,APP退到後臺(好比按Home鍵),1分鐘以後變爲後臺APP,雖然進程存活,可是已經不能經過startService啓動服務,可是發送通知並不受限制,能夠經過通知啓動Service,這個時候,Service不會被當作後臺啓動,一樣經過通知欄打開Activity也不受限制? 爲何,直觀來說,通知已經屬於用戶感知的交互,本就不該該算到後臺啓動。本文先發對比以前的Android O 後臺startService限制簡析,分析下Service,以後再看Activity在Android10中的限制java

本文基於android10-releaseandroid

通知藉助PendingIntent啓動Service

能夠模擬這樣一個場景,發送一個通知,而後將APP殺死,以後在通知欄經過PendingIntent啓動Service,看看是否會出現禁止後臺啓動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());
}
複製代碼

實際結果是:點擊通知後Service正常啓動。下面逐步分析下。app

同普通的Intent啓動Service不一樣,這裏的通知經過PendingIntent啓動,是否是隻要PendingIntent就足夠了呢,並非(後面分析)。經過通知啓動Service的第一步是經過PendingIntent.getService得到一個用於啓動特定Service的PendingIntent:less

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構建一個PendingIntentRecord extends IIntentSender.Stub實體, PendingIntentRecord能夠看作PendingIntent在AMS端的記錄,最終造成二者對應的雙向通訊通道。以後通知就會經過nm.notify顯示在通知欄,這一步先略過,先看最後一步,經過點擊通知啓動Service,通知點擊這不細看,只要明白最後調用的是PendingIntent的sendAndReturnResult函數,ide

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端,查找到對應的PendingIntentRecord,進入其sendInner函數,前文buildIntent的時候,用的是 ActivityManager.INTENT_SENDER_SERVICE,進入對應分支:函數

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);
            }
			 <!--是否能夠啓動的一個關鍵點 ,後面分析-->
	        int res = START_SUCCESS;
	        try {
	        <!--duration非null纔會執行tempWhitelistForPendingIntent添加到白名單-->
	            if (duration != null) {
	                int procState = controller.mAmInternal.getUidProcessState(callingUid);
	                
	                <!--u0_a16   2102  1742 4104448 174924 0    0 S com.android.systemui 通知是systemui進程 優先級高沒後臺問題-->
	                if (!ActivityManager.isProcStateBackground(procState)) {
	                    ...
	                    <!--更新臨時白名單, duration設定白名單的有效時長,這個是在發通知的時候設定的-->
	                    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) {				...
複製代碼

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

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不受限制的關鍵。ui

前文說過,通知發送時會設定一個臨時白名單的有效存活時間,只有設置了,才能進mPendingTempWhitelist,這是存活時間是從點擊到真正start中間所能存活的時間,若是在此間還未啓動,則判斷啓動無效。有效存活時間是何時設置的,是發送通知的時候,並且,這個時機只在發送通知的時候,其餘沒入口this

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

setPendingIntentWhitelistDuration會更新PendingIntentRecord的whitelistDuration列表,這個列表標識着這個

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<>();
        }
        <!--設置存活時長-->
        whitelistDuration.put(whitelistToken, duration);
    }  ...
}
複製代碼

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

Android10後臺啓動Activity限制 (android10-release源碼分支)

Android10以後,禁止後臺啓動Activity,Activity的後臺定義比Service更嚴格,延時10s,退到後臺,即可以模擬後臺啓動Activity,注意這裏並無像Service限定到60以後,Activity的後臺限制更嚴格一些,直觀上理解:沒有可見窗口均可以算做後臺,中間的間隔最多可能就幾秒,好比咱們延時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}]
複製代碼

未正式發行的版本上還能看到以下Toast

大概意思就是:限制後臺應用啓動Activity。

核心邏輯在這一段 ActivityStarter

boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
            final String callingPackage, int realCallingUid, int realCallingPid,
            WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart, Intent intent) {
         <!--系統應用不受限制-->
        // 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;
        }
        <!--有可見窗口及系統進程不受限制-->
        // 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;
        ...
        <!--這個權限不必定是誰都能拿到-->
        // 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;
        }
        
        ...一些系統判斷
        
        <!--是否白名單-->
        // 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有懸浮窗權限-->
        // 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上運行的應用只有在知足如下一個或多個條件時才能啓動Activity:常見的有以下幾種

  • 具備可見窗口,例如在前臺運行的Activity。(前臺服務不會將應用限定爲在前臺運行。)

  • 該應用在前臺任務的返回棧中具備一項 Activity。(必須同前臺Activity位於同一個Task返回棧,若是兩個Task棧不行。)

  • 該應用已得到用戶授予的 SYSTEM_ALERT_WINDOW 權限。

  • pendingIntent臨時白名單機制,不攔截經過通知拉起的應用。

    經過通知,利用pendingIntent啓動 Activity。
      經過通知,在 PendingIntent中發送廣播,接收廣播後啓動 Activity。
      經過通知,在 PendingIntent中啓動 Service(必定能夠啓動Service),在 Service 中啓動 Activity。
    複製代碼
  • 該應用的某一項服務被其餘可見應用綁定(進程優先級其實一致)。請注意,綁定到該服務的應用必須在後臺對該應用保持可見,才能成功啓動 Activity。

這裏有一個比較有趣的點:若是應用在前臺任務的返回棧中具備一項Activity,並非說必定要本身APP的Activity在展現,而是說,當前展現的Task棧裏有本身的Activity就能夠,這點判斷以下

boolean areBackgroundActivityStartsAllowed() {
  		
  		<!--白名單-->
        // allow if the whitelisting flag was explicitly set
        if (mAllowBackgroundActivityStarts) {
            return true;
        }
        
        ...
       <!--是否有Actvity位於前臺任務棧中-->
        // 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嚴重依賴CallAPP的狀態,而Service更關心被啓動APP的狀態。

Android10後臺限制啓動Activity的系統bug

連續兩次啓動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;
}
複製代碼

若是應用位於後臺,第一次啓動Activity會被當作後臺啓動,可是ActiivityRecord仍然會被建立,同時State會被設置成INITIALIZING,而且位於當前將要啓動Task的棧頂,

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

那麼若是在後臺,再次經過startActivity啓動,當前進程就會被認爲是在前臺,應用就會被拉起,真是個奇葩bug。由於知足以下條件。

topActivity.isState(INITIALIZING)
複製代碼

這個時候,Activity就能夠在後臺被啓動。其實Android10後臺限制啓動Activity的並不是徹底不讓啓動,只是延遲,再次APP可見的時候,依舊能夠把以前未啓動的Activity喚起。

PendingIntent啓動Activity不受限制原理

通知的進程是系統進程

u0_a16        2102  1742 4104448 174924 0                   0 S com.android.systemui
複製代碼

系統進程不受限制,就是這麼流弊。

通知啓動Service,而後在Service中是容許啓動Activity不受後臺限制(奇葩)

對於經過PendingIntent通知啓動的APP,短期內不算後臺啓動Activity

從上面的註釋就能看出來,若是是經過通知啓動的,或者說若是是前臺應用觸發的sendInner,那麼短期內容許啓動Activity,雖然是經過Service啓動,可是若是是通知啓動的Service,那麼暫且算是看作應用位於前臺,以下:

先更新一個標識mHasStartedWhitelistingBgActivityStarts,就是是否容許Service後臺啓動Activity的標識,這裏是設置爲true,此刻進程可能還未啓動,

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

等到後面進程啓動了在attach的時候會繼續走Service的啓動流程

這裏由於mHasStartedWhitelistingBgActivityStarts被設置爲true,

就會走setAllowBackgroundActivityStarts 將mAllowBackgroundActivityStarts設置爲true

public void setAllowBackgroundActivityStarts(boolean allowBackgroundActivityStarts) {
    mAllowBackgroundActivityStarts = allowBackgroundActivityStarts;
}
複製代碼

這樣在啓動Activity時候,判斷是否容許後臺啓動就直接返回true

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

這樣就構建了容許後臺啓動Activity的場景,這個時限是10秒,10秒內啓動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清理的監聽回調

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部分須要再確認-->
                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);
}
複製代碼

總結

  • 經過通知啓動Service不受後臺限制的緣由是存在可更新PendingTempWhitelist白名單
  • 後臺啓動Activity嚴重依賴CallAPP的狀態,而Service更關心被啓動APP的狀態
  • 位於後臺,連續屢次startActivity就能夠啓動Activity,目前看是個系統bug
  • Android10後臺限制啓動Activity的並不是徹底不讓啓動,只是延遲,再次APP可見的時候,依舊能夠把以前未啓動的Activity喚起。

做者:看書的小蝸牛

AAndroid Notification、PendingIntent與後臺啓動Service、Activity淺析

僅供參考,歡迎指正

相關文章
相關標籤/搜索