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
能夠模擬這樣一個場景,發送一個通知,而後將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,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的狀態。
連續兩次啓動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喚起。
通知的進程是系統進程
u0_a16 2102 1742 4104448 174924 0 0 S com.android.systemui
複製代碼
系統進程不受限制,就是這麼流弊。
對於經過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);
}
複製代碼
做者:看書的小蝸牛
AAndroid Notification、PendingIntent與後臺啓動Service、Activity淺析
僅供參考,歡迎指正