android 10+从后台启动 Activity 的限制
限制后台启动activity
如果未满足相关条件,则后台不允许启动activity,并会打印如下相关的log:
// anything that has fallen through would currently be abortedSlog.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()) + "]");
限制的例外情况
在 Android 10 或更高版本上运行的应用只有在满足以下一项或多项条件时,才能启动 Activity:
ROOT_UID/SYSTEM_UID/NFC_UID等重要uid
final boolean useCallingUidState = originatingPendingIntent == null || checkedOptions == null || !checkedOptions.getIgnorePendingIntentCreatorForegroundState();.......if (useCallingUidState) { if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Activity start allowed for important callingUid (" + callingUid + ")"); } return false; } ......
home 进程
if (useCallingUidState) { ...... if (isHomeApp(callingUid, callingPackage)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG,"Activity start allowed for home app callingUid (" + callingUid + ")"); } return false; }
Ime窗口所在uid下的进程
if (useCallingUidState) { ...... // IME should always be allowed to start activity, like IME settings. final WindowState imeWindow = mRootWindowContainer.getCurrentInputMethodWindow(); if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Activity start allowed for active ime (" + callingUid + ")"); } return false; }
应用具有可见窗口,例如前台 Activity
应用是persistent系统进程,并正在执行UI操作
// 是persistent系统进程,并正在执行UI操作final boolean isCallingUidPersistentSystemProcess = callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;// 如果允许应用程序切换,则允许具有可见应用程序窗口的普通应用程序启动活动,// 或者将允许具有非应用程序可见窗口的动态壁纸等应用程序。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) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Activity start allowed: callingUidHasAnyVisibleWindow = " + callingUid + ", isCallingUidPersistentSystemProcess = " + isCallingUidPersistentSystemProcess); } return false;}
应用声明了START_ACTIVITIES_FROM_BACKGROUND 权限
特权system/priv-app/目录下的app才可以申请豁免
<!-- @SystemApi @hide Allows an application to start activities from background --><permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role" />
if (useCallingUidState) { // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission if (mService.checkPermission( START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG,"Background activity start allowed: START_ACTIVITIES_FROM_BACKGROUND " + "permission granted for uid " + callingUid); } return false; }
应用是Recent组件所在uid下进程(这里一般为home进程)
if (useCallingUidState) { ....... // don't abort if the caller has the same uid as the recents component if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callingUid (" + callingUid+ ") is recents"); } return false; }
应用是设备所有者
应用是在设备所有者模式下运行的设备政策控制器。示例用例包括完全托管的企业设备,以及数字标识牌和自助服务终端等专用设备。
if (useCallingUidState) { ...... // don't abort if the callingUid is the device owner if (mService.isDeviceOwner(callingUid)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callingUid (" + callingUid+ ") is device owner"); } return false; }
应用通过 CompanionDeviceManager API 与配套硬件设备相关联。
此 API 支持应用启动 API,以响应用户在配对设备上执行的操作。
if (useCallingUidState) { ...... // don't abort if the callingUid has companion device final int callingUserId = UserHandle.getUserId(callingUid); if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callingUid (" + callingUid+ ") is companion app"); } return false; }
用户已向应用授予 SYSTEM_ALERT_WINDOW 权限
**注意:**在 Android 10(Go 版本)上运行的应用无法获得SYSTEM_ALERT_WINDOW权限。
注意:如果应用程序以 API 级别 23 或更高级别为目标,则应用程序用户必须通过权限管理屏幕明确向应用程序授予此权限。
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW" android:label="@string/permlab_systemAlertWindow" android:description="@string/permdesc_systemAlertWindow" android:protectionLevel="signature|setup|appop|installer|pre23|development" />
if (useCallingUidState) { ...... // 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; } }
关于areBackgroundActivityStartsAllowed系列
如果此时我们没有 callerApp,则没有向 startActivity() 提供调用者。 基于 PendingIntent 的启动就是这种情况,因为创建者的进程可能未启动且处于活动状态。 如果是这种情况,我们会在调用者允许的情况下为 send() 调用者检索 WindowProcessController,以便我们可以根据其状态做出决定。
// 遗留行为允许使用调用者前台状态绕过 BAL 限制。final boolean balAllowedByPiSender = PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions);int callerAppUid = callingUid;if (callerApp == null && balAllowedByPiSender) { callerApp = mService.getProcessController(realCallingPid, realCallingUid); callerAppUid = realCallingUid;}if (callerApp != null && useCallingUidState) { // first check the original calling process if (callerApp.areBackgroundActivityStartsAllowed(appSwitchState)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callerApp process (pid = " + callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed"); } return false; } // 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); if (proc != callerApp && proc.areBackgroundActivityStartsAllowed(appSwitchState)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG,"Background activity start allowed: process " + proc.getPid() + " from uid " + callerAppUid + " is allowed"); } return false; } } }}
应用的某个 Activity 刚在不久前启动/结束
如果不允许应用程序切换,我们将忽略所有启动activity宽限期异常,因此应用程序无法在按下主页按钮后在 onPause() 中自行启动。
在停止应用程序切换的时间之后启动或finish activity的10s内可后台启动Activity
boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName, int appSwitchState, boolean isCheckingForFgsStart, boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime, long lastActivityLaunchTime, long lastActivityFinishTime) { if (appSwitchState == APP_SWITCH_ALLOW) { // 如果调用者中的任何activity最近启动或finish,则允许, final long now = SystemClock.uptimeMillis(); // 启动或finish有10s宽限 if (now - lastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS || now - lastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) { // 得在停止应用程序切换的时间之后启动或finish才行 if (lastActivityLaunchTime > lastStopAppSwitchesTime || lastActivityFinishTime > lastStopAppSwitchesTime) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period"); } return true; } if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start within "+ ACTIVITY_BG_START_GRACE_PERIOD_MS+ "ms grace period but also within stop app switch window"); } } }
具有后台启动activity特权的Active Instrumentation所在进程
// Allow if the proc is instrumenting with background activity starts privs. if (hasBackgroundActivityStartPrivileges) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: process instrumenting with background " + "activity starts privileges"); } return true; }
应用在前台任务的返回栈中拥有 Activity
android U或许会取消
boolean hasActivityInVisibleTask() { return (mActivityStateFlags & ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK) != 0; }
// Allow if the caller has an activity in any foreground task. if (hasActivityInVisibleTask && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: process has activity in foreground task"); } return true; }
应用中的某个服务被另一个可见应用绑定
请注意,绑定到服务的应用必须保持可见,以便后台应用成功启动 Activity。
// Allow if the caller is bound by a UID that's currently foreground. if (isBoundByForegroundUid()) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: process bound by foreground uid"); } return true; } private boolean isBoundByForegroundUid() { synchronized (this) { if (mBoundClientUids != null) { for (int i = mBoundClientUids.size() - 1; i >= 0; i--) { if (mUidHasActiveVisibleWindowPredicate.test(mBoundClientUids.get(i))) { return true; } } } } return false; }
@HotPath(caller = HotPath.START_SERVICE) boolean hasActiveVisibleWindow(int uid) { if (mVisibleActivityProcessTracker.hasVisibleActivity(uid)) { return true; } return mActiveUids.hasNonAppVisibleWindow(uid); }
应用收到系统的 PendingIntent 通知
对于服务和广播接收器的挂起 Intent,应用可在该挂起 Intent 发送几秒钟后启动 Activity
应用收到另一个可见应用发送的 PendingIntent
应用的 Service有带BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS的connection
需要声明START_ACTIVITIES_FROM_BACKGROUND权限
Android 10 (API 级别 29) 及更高版本对后台应用可启动 Activity 的时间施加限制。这些限制有助于最大限度地减少对用户造成的中断,并且可以让用户更好地控制其屏幕上显示的内容。
**注意:**为启动 Activity,系统仍会将运行前台服务的应用视为“后台”应用。
详情见BackgroundLaunchProcessController 介绍
// Allow if the flag was explicitly set. if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[Process(" + pid + ")] Activity start allowed: process allowed by token"); } return true; }
应用在具有相同uid的现有Task中启动活动
// Do not allow background activity start in new task or in a task that uid is not present. // Also do not allow pinned window to start single instance activity in background, // as it will recreate the window and makes it to foreground. boolean blockBalInTask = (newTask || !targetTask.isUidPresent(mCallingUid) || (LAUNCH_SINGLE_INSTANCE == mLaunchMode && targetTask.inPinnedWindowingMode())); if (mRestrictedBgActivity && blockBalInTask && handleBackgroundActivityAbort(r)) { Slog.e(TAG, "Abort background activity starts from " + mCallingUid); return START_ABORTED; }
如何申请豁免
权限相关
申请START_ACTIVITIES_FROM_BACKGROUND 权限
特权system/priv-app/目录下的app才可以申请豁免
<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
<permissions> <privapp-permissions package="com.android.xxx"> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> privapp-permissions>permissions>
申请SYSTEM_ALERT_WINDOW权限
**注意:**在 Android 10(Go 版本)上运行的应用无法获得SYSTEM_ALERT_WINDOW权限。
注意:如果应用程序以 API 级别 23 或更高级别为目标,则应用程序用户必须通过权限管理屏幕明确向应用程序授予此权限。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Service有特殊client绑定
应用中的某个服务被另一个可见应用绑定
应用中的某个服务被另一个应用以BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag绑定
另一个应用需要声明START_ACTIVITIES_FROM_BACKGROUND权限
来源地址:https://blog.csdn.net/xiaoyantan/article/details/128401740
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341