https://developer.android.google.cn/guide/components/activities/background-starts?hl=lt
Android 10(API 级别 29)及更高版本对应用在后台运行时可以启动 activity 的时间施加了限制。这些限制有助于最大限度地减少对用户造成的干扰,并且可以让用户更好地控制其屏幕上显示的内容。
frameworks/base/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
boolean shouldAbortBackgroundActivityStart(
int callingUid,
int callingPid,
final String callingPackage,
int realCallingUid,
int realCallingPid,
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
boolean allowBackgroundActivityStart,
Intent intent,
ActivityOptions checkedOptions) {
return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
allowBackgroundActivityStart, intent, checkedOptions) == BAL_BLOCK;
}
/**
* @return A code denoting which BAL rule allows an activity to be started,
* or {@link BAL_BLOCK} if the launch should be blocked
*/
@BalCode
int checkBackgroundActivityStart(
int callingUid,
int callingPid,
final String callingPackage,
int realCallingUid,
int realCallingPid,
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
boolean allowBackgroundActivityStart,
Intent intent,
ActivityOptions checkedOptions) {
// don't abort for the most important UIDs
final int callingAppId = UserHandle.getAppId(callingUid);
final boolean useCallingUidState =
originatingPendingIntent == null
|| checkedOptions == null
|| !checkedOptions.getIgnorePendingIntentCreatorForegroundState();
if (useCallingUidState) {
if (callingUid == Process.ROOT_UID
|| callingAppId == Process.SYSTEM_UID
|| callingAppId == Process.NFC_UID) {
return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
callingUid, realCallingUid, intent, "Important callingUid");
}
// Always allow home application to start activities.
if (isHomeApp(callingUid, callingPackage)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ false, callingUid, realCallingUid, intent,
"Home app");
}
// IME should always be allowed to start activity, like IME settings.
final WindowState imeWindow =
mService.mRootWindowContainer.getCurrentInputMethodWindow();
if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ false, callingUid, realCallingUid, intent,
"Active ime");
}
}
// This is used to block background activity launch even if the app is still
// visible to user after user clicking home button.
final int appSwitchState = mService.getBalAppSwitchesState();
// don't abort if the callingUid has a visible window or is a persistent system process
final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
final boolean callingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid);
final boolean isCallingUidForeground =
callingUidHasAnyVisibleWindow
|| callingUidProcState == ActivityManager.PROCESS_STATE_TOP
|| callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
final boolean isCallingUidPersistentSystemProcess =
callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
// Normal apps with visible app window will be allowed to start activity if app switching
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
final boolean appSwitchAllowedOrFg =
appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
final boolean allowCallingUidStartActivity =
((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
&& callingUidHasAnyVisibleWindow)
|| isCallingUidPersistentSystemProcess;
if (useCallingUidState && allowCallingUidStartActivity) {
return logStartAllowedAndReturnCode(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, callingUid, realCallingUid, intent,
"callingUidHasAnyVisibleWindow = "
+ callingUid
+ ", isCallingUidPersistentSystemProcess = "
+ isCallingUidPersistentSystemProcess);
}
// take realCallingUid into consideration
final int realCallingUidProcState =
(callingUid == realCallingUid)
? callingUidProcState
: mService.mActiveUids.getUidState(realCallingUid);
final boolean realCallingUidHasAnyVisibleWindow =
(callingUid == realCallingUid)
? callingUidHasAnyVisibleWindow
: mService.hasActiveVisibleWindow(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;
// In the case of an SDK sandbox calling uid, check if the corresponding app uid has a
// visible window.
if (Process.isSdkSandboxUid(realCallingUid)) {
int realCallingSdkSandboxUidToAppUid =
Process.getAppUidForSdkSandboxUid(UserHandle.getAppId(realCallingUid));
if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX,
/*background*/ false, callingUid, realCallingUid, intent,
"uid in SDK sandbox has visible (non-toast) window");
}
}
// Legacy behavior allows to use caller foreground state to bypass BAL restriction.
final boolean balAllowedByPiSender =
PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions);
if (balAllowedByPiSender && realCallingUid != callingUid) {
final boolean useCallerPermission =
PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions);
if (useCallerPermission
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
realCallingUid,
-1,
true)
== PackageManager.PERMISSION_GRANTED) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
/*background*/ false, callingUid, realCallingUid, intent,
"realCallingUid has BAL permission. realCallingUid: " + realCallingUid);
}
// don't abort if the realCallingUid has a visible window
// TODO(b/171459802): We should check appSwitchAllowed also
if (realCallingUidHasAnyVisibleWindow) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
/*background*/ false, callingUid, realCallingUid, intent,
"realCallingUid has visible (non-toast) window. realCallingUid: "
+ realCallingUid);
}
// if the realCallingUid is a persistent system process, abort if the IntentSender
// wasn't allowed to start an activity
if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
/*background*/ false, callingUid, realCallingUid, intent,
"realCallingUid is persistent system process AND intent "
+ "sender allowed (allowBackgroundActivityStart = true). "
+ "realCallingUid: " + realCallingUid);
}
// don't abort if the realCallingUid is an associated companion app
if (mService.isAssociatedCompanionApp(
UserHandle.getUserId(realCallingUid), realCallingUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_PENDING_INTENT,
/*background*/ false, callingUid, realCallingUid, intent,
"realCallingUid is a companion app. "
+ "realCallingUid: " + realCallingUid);
}
}
if (useCallingUidState) {
// don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
if (ActivityTaskManagerService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND,
callingPid, callingUid) == PERMISSION_GRANTED) {
return logStartAllowedAndReturnCode(BAL_ALLOW_BAL_PERMISSION,
/*background*/ true, callingUid, realCallingUid, intent,
"START_ACTIVITIES_FROM_BACKGROUND permission granted");
}
// don't abort if the caller has the same uid as the recents component
if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, callingUid, realCallingUid,
intent, "Recents Component");
}
// don't abort if the callingUid is the device owner
if (mService.isDeviceOwner(callingUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, callingUid, realCallingUid,
intent, "Device Owner");
}
// don't abort if the callingUid has companion device
final int callingUserId = UserHandle.getUserId(callingUid);
if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
return logStartAllowedAndReturnCode(BAL_ALLOW_ALLOWLISTED_COMPONENT,
/*background*/ true, callingUid, realCallingUid,
intent, "Companion App");
}
// 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 logStartAllowedAndReturnCode(BAL_ALLOW_SAW_PERMISSION,
/*background*/ true, callingUid, realCallingUid,
intent, "SYSTEM_ALERT_WINDOW permission is granted");
}
}
// If we don't have callerApp at this point, no caller was provided to startActivity().
// That's the case for PendingIntent-based starts, since the creator's process might not be
// up and alive. If that's the case, we retrieve the WindowProcessController for the send()
// caller if caller allows, so that we can make the decision based on its state.
int callerAppUid = callingUid;
if (callerApp == null && balAllowedByPiSender) {
callerApp = mService.getProcessController(realCallingPid, realCallingUid);
callerAppUid = realCallingUid;
}
// don't abort if the callerApp or other processes of that uid are allowed in any way
if (callerApp != null && useCallingUidState) {
// first check the original calling process
@BalCode int balAllowedForCaller = callerApp
.areBackgroundActivityStartsAllowed(appSwitchState);
if (balAllowedForCaller != BAL_BLOCK) {
return logStartAllowedAndReturnCode(balAllowedForCaller,
/*background*/ true, callingUid, realCallingUid, intent,
"callerApp process (pid = " + callerApp.getPid()
+ ", uid = " + callerAppUid + ") is allowed");
}
// only if that one wasn't allowed, 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);
int balAllowedForUid = proc.areBackgroundActivityStartsAllowed(appSwitchState);
if (proc != callerApp
&& balAllowedForUid != BAL_BLOCK) {
return logStartAllowedAndReturnCode(balAllowedForUid,
/*background*/ true, callingUid, realCallingUid, intent,
"process" + proc.getPid()
+ " from uid " + callerAppUid + " is allowed");
}
}
}
}
// anything that has fallen through would currently be aborted
Slog.w(
TAG,
"Background activity start [callingPackage: "
+ callingPackage
+ "; callingUid: "
+ callingUid
+ "; appSwitchState: "
+ appSwitchState
+ "; isCallingUidForeground: "
+ isCallingUidForeground
+ "; callingUidHasAnyVisibleWindow: "
+ callingUidHasAnyVisibleWindow
+ "; callingUidProcState: "
+ DebugUtils.valueToString(
ActivityManager.class, "PROCESS_STATE_", callingUidProcState)
+ "; isCallingUidPersistentSystemProcess: "
+ isCallingUidPersistentSystemProcess
+ "; realCallingUid: "
+ realCallingUid
+ "; isRealCallingUidForeground: "
+ isRealCallingUidForeground
+ "; realCallingUidHasAnyVisibleWindow: "
+ realCallingUidHasAnyVisibleWindow
+ "; realCallingUidProcState: "
+ DebugUtils.valueToString(
ActivityManager.class, "PROCESS_STATE_", realCallingUidProcState)
+ "; isRealCallingUidPersistentSystemProcess: "
+ isRealCallingUidPersistentSystemProcess
+ "; originatingPendingIntent: "
+ originatingPendingIntent
+ "; allowBackgroundActivityStart: "
+ allowBackgroundActivityStart
+ "; intent: "
+ intent
+ "; callerApp: "
+ callerApp
+ "; inVisibleTask: "
+ (callerApp != null && callerApp.hasActivityInVisibleTask())
+ "]");
// log aborted activity start to TRON
if (mService.isActivityStartsLoggingEnabled()) {
mSupervisor
.getActivityMetricsLogger()
.logAbortedBgActivityStart(
intent,
callerApp,
callingUid,
callingPackage,
callingUidProcState,
callingUidHasAnyVisibleWindow,
realCallingUid,
realCallingUidProcState,
realCallingUidHasAnyVisibleWindow,
(originatingPendingIntent != null));
}
return BAL_BLOCK;
}
根据代码,在满足以下一个或多个条件时,在 Android 10 或更高版本上运行的应用可以启动 activity:
- 应用具有可见窗口,例如前台 activity。
- 从 Android 14 开始,这要求发送
PendingIntent
的应用通过传入带有 setPendingIntentBackgroundActivityStartMode (MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 的 ActivityOptions 软件包来明确选择启用。
- 从 Android 14 开始,这要求发送
- 该应用在前台任务的返回堆栈中有 activity。
-
应用在“最近使用的应用”屏幕上现有任务的返回堆栈中有一个 activity。
注意 :当此类应用尝试启动新的 activity 时,系统会将该 activity 置于应用的现有任务之上,但不会离开当前可见的任务。当用户稍后返回应用任务时,系统会启动新的 activity,而不是之前在应用任务之上的 activity。
-
应用有一个最近启动的 activity。
-
应用最近对一个 activity 调用了 finish()。这仅适用于在调用
finish()
时,应用在前台运行的 activity,或在前台任务的返回堆栈中具有 activity。 -
该应用具有以下某个受系统绑定的服务。 这些服务可能需要启动一个界面。
-
该应用的一项服务被另一个可见的应用绑定。绑定到该服务的应用必须保持可见,以便后台应用成功启动 activity。
注意 :从 Android 14 开始,如果绑定到该服务的应用以 Android 14 或更高版本为目标平台,它将不再允许具有该服务的应用默认启动后台 activity。应用必须传递
Context.BIND_ALLOW_ACTIVITY_STARTS
标志,以允许绑定服务应用启动后台 activity。 -
应用会从系统收到通知 PendingIntent。如果是服务和广播接收器的待处理 intent,则应用可以在待处理 intent 发送后启动 activity 几秒钟。
-
应用会收到来自其他可见应用发送的
PendingIntent
。注意 :从 Android 14 开始,以 Android 14 或更高版本为目标平台的应用在发送
PendingIntent
时必须选择允许启动后台 activity。若要选择接受,该应用应传递一个ActivityOptions
捆绑包setPendingIntentBackgroundActivityStartMode (ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
-
应用收到一条系统广播,在该广播中,应用应启动界面。例如 ACTION_NEW_OUTGOING_CALL 和 SECRET_CODE_ACTION。应用可在广播发送几秒钟后启动 Activity。
-
应用通过 CompanionDeviceManager API 与配套硬件设备相关联。此 API 可让应用启动 activity 以响应用户在配对设备上执行的操作。
-
应用被用户授予 SYSTEM_ALERT_WINDOW 权限。
注意 :在 Android 10(Go 版本)上运行的应用无法获得 SYSTEM_ALERT_WINDOW 权限。