BackgroundLaunchProcessController 介绍

Android 10 (API 级别 29) 及更高版本对后台应用可启动 Activity
的时间施加限制。这些限制有助于最大限度地减少对用户造成的中断,并且可以让用户更好地控制其屏幕上显示的内容。 注意:为启动
Activity,系统仍会将运行前台服务的应用视为“后台”应用。

为了改进应用性能和用户体验,以 Android 12 或更高版本为目标平台的应用无法从用作通知 trampoline
的服务或广播接收器中启动 activity。换言之,当用户点按通知或通知中的操作按钮时,您的应用无法在服务或广播接收器内调用
startActivity()。

本文总结

  1. 豁免while-in-use和后台启动Activity
豁免场景后台启动Activity后台FGS的while-in-use时限
系统的 PendingIntent 通知trampoline白名单中允允许10s
可见应用发送的 PendingIntent允许允许10s
绑定服务时传入BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS允许允许Connection存活期间
  1. 系统的 PendingIntent 通知 同时也会豁免后台启动后台Service,时限为30s。
    当前有助于此过程的一组令牌被临时允许启动活动,即使它不在前台也是如此。 此映射的值是可选的(可以为空),用于追溯对通知令牌机制的授权。
  @GuardedBy("this")
    private @Nullable ArrayMap<Binder, IBinder> mBackgroundActivityStartTokens;

BackgroundLaunchProcessController 介绍

进程控制器决定进程是否可以后台启动activity或后台启动前台Service,这个类是android13从WindowProcessController中开始抽取出来,为了是不持有wm Lock。

  • 代码路径:frameworks/base/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
  • 类结构:
    在这里插入图片描述

添加或更新Token

后台启动的例外情况包含如下:

● 应用收到系统的 PendingIntent 通知。对于服务和广播接收器的PedingIntent,应用可在该PendingIntent 发送几秒钟后启动 Activity。
● 应用收到另一个可见应用发送的 PendingIntent。

添加或更新当前进程的mBackgroundActivityStartTokens,token的作用主要是校验后台启动FGS或后台启动Activity;且一般由广播分发或Service启动时去添加或更新,主要添加路径有如下几个:

  • 通知的PendingIntent触发添加;
  • 发送PendingIntent的app处于前台;
  • bind Service时带有BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS的flag(这个需要申请START_ACTIVITIES_FROM_BACKGROUND)

允许后台activity使用令牌 {@param entity} 启动。 或者,您可以提供 {@param
originatingToken} 如果您有一个这样的原始令牌,这对于在通知令牌的情况下追溯授权很有用。

    // entity:广播或service的实例
    // originatingToken: 如果是通知触发,则为NMS的ALLOWLIST_TOKEN
    void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
            @Nullable IBinder originatingToken) {
        synchronized (this) {
            if (mBackgroundActivityStartTokens == null) {
                mBackgroundActivityStartTokens = new ArrayMap<>();
            }
            mBackgroundActivityStartTokens.put(entity, originatingToken);
        }
    }

主要调用的地方如下:
在这里插入图片描述

添加的时序图如下,由BroadcastQueue或ServiceRecord触发
在这里插入图片描述

通知触发

几乎在所有情况下,后台应用都应显示有时效性的通知,以便向用户提供紧急信息,而非直接启动
Activity。此类通知的适用情形包括处理来电或正在响铃的闹钟。

通知入队时会检测是否有PendingIntent并遍历调用setPendingIntentAllowBgActivityStarts来设置PendingIntentRecord中的对应的mAllowBgActivityStarts;在sendInner发送到对应的Broadcast或Service中去更新上面说到的token。

        // temporarily allow apps to perform extra work when their pending intents are launched
        if (notification.allPendingIntents != null) {
            final int intentCount = notification.allPendingIntents.size();
            if (intentCount > 0) {
               .....
                for (int i = 0; i < intentCount; i++) {
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                    if (pendingIntent != null) {
                       ......
                        // 设置后台允许的token,这里包括activity&broadcast&service的flag
                        mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
                                ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
                                        | FLAG_SERVICE_SENDER));
                    }
                }
            }
        }

设置的流程如下时序图
在这里插入图片描述

由前面的flag可知,会加入如下三个列表,至于具体的添加过程下面会讲到。

    void setAllowBgActivityStarts(IBinder token, int flags) {
        if (token == null) return;
        if ((flags & FLAG_ACTIVITY_SENDER) != 0) {
            mAllowBgActivityStartsForActivitySender.add(token);
        }
        if ((flags & FLAG_BROADCAST_SENDER) != 0) {
            mAllowBgActivityStartsForBroadcastSender.add(token);
        }
        if ((flags & FLAG_SERVICE_SENDER) != 0) {
            mAllowBgActivityStartsForServiceSender.add(token);
        }
    }

移除Token

因通知而添加的Start Service或广播相关Token是有时间限制的,10s后均会被移除;即10s内这些trampoline可能会豁免相关的后台启动activity或FGS。

    void removeAllowBackgroundActivityStartsToken(Binder entity) {
        synchronized (this) {
            if (mBackgroundActivityStartTokens != null) {
                mBackgroundActivityStartTokens.remove(entity);
            }
        }
    }
广播相关Token移除
  • 无序广播执行performReceive 10s后移除Token;
  • 有序广播则需等第一个广播分发完成才开始post 10s移除;
Service相关Token移除
  • Start Service token移除:service相关的进程重启;Service启动10s后移除
  • Bind Service token移除:相关的Connection被移除
    在这里插入图片描述

时序图如下,也是由BroadcastQueue或ServiceRecord触发移除。
在这里插入图片描述

使用Token

android 12+通知 trampoline 限制

当用户与通知互动时,某些应用会启动一个应用组件来响应通知点按操作,该应用组件最终会启动用户最终看到并与之互动的 activity。此应用组件被称为通知 trampoline。
为了改进应用性能和用户体验,以 Android 12 或更高版本为目标平台的应用无法从用作通知 trampoline 的服务广播接收器中启动 activity。换言之,当用户点按通知或通知中的操作按钮时,您的应用无法在服务或广播接收器内调用 startActivity()
当您的应用尝试从充当通知 trampoline 的服务或广播接收器启动 activity 时,系统会阻止该 activity 启动,并在 Logcat 中显示以下消息:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \ this should be avoided for performance reasons.

对于以 Android 13(API 级别
33)及更高版本为目标平台的应用,出于用户体验和性能考虑,之前具有豁免角色的权限持有者(浏览器)将被禁止从广播接收器或服务启动
Activity 来响应通知和通知点击操作。

具体限制代码如下:

    private class NotificationTrampolineCallback implements BackgroundActivityStartCallback {
        @Override
        public boolean isActivityStartAllowed(Collection<IBinder> tokens, int uid,
                String packageName) {
            checkArgument(!tokens.isEmpty());
            for (IBinder token : tokens) {
                // 如果当前进程有不是通知传入的token,则直接允许
                if (token != ALLOWLIST_TOKEN) {
                    // We only block or warn if the start is exclusively due to notification
                    return true;
                }
            }
            String logcatMessage =
                    "Indirect notification activity start (trampoline) from " + packageName;
            if (blockTrampoline(uid)) {
                Slog.e(TAG, logcatMessage + " blocked");
                return false;
            } else {
                Slog.w(TAG, logcatMessage + ", this should be avoided for performance reasons");
                return true;
            }
        }
        private boolean blockTrampoline(int uid) {
            if (mRoleObserver != null && mRoleObserver.isUidExemptFromTrampolineRestrictions(uid)) {
                return CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK_FOR_EXEMPT_ROLES,
                        uid);
            }
            return CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
        }
Token豁免后台启动限制
  • 后台启动FGS使用中权限校验:进程有tokens即允许;
  • 后台启动activity:在进程有tokens基础上,进程有非通知传入的token;或在豁免trampoline白名单中;
    private boolean isBackgroundStartAllowedByToken(int uid, String packageName,
            boolean isCheckingForFgsStart) {
        synchronized (this) {
            // 该进程没有tokens,直接返回false
            if (mBackgroundActivityStartTokens == null
                    || mBackgroundActivityStartTokens.isEmpty()) {
                return false;
            }
            // 进程有tokens,如果是background FGS start则直接允许
            if (isCheckingForFgsStart) {
                // BG-FGS-start only checks if there is a token.
                return true;
            }
        	// 这个一般不会为null,为NotificationTrampolineCallback
            if (mBackgroundActivityStartCallback == null) {
                // We have tokens but no callback to decide => allow.
                return true;
            }
            // The callback will decide.
            // 通知服务中NotificationTrampolineCallback的isActivityStartAllowed方法来决定
            return mBackgroundActivityStartCallback.isActivityStartAllowed(
                    mBackgroundActivityStartTokens.values(), uid, packageName);
        }
    }
后台启动FGS
  • 后台启动FGS使用中权限校验:进程有tokens即允许;

当前进程10s内有相关通知触发的trampoline或者进程下的Service有带BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag的Connection即可允许。

    @HotPath(caller = HotPath.START_SERVICE)
    public boolean areBackgroundFgsStartsAllowed() {
        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(),
                true /* isCheckingForFgsStart */);
    }

在这里插入图片描述

后台启动activity
  • 后台启动activity:在进程有tokens基础上,进程有非通知传入的token;或在豁免trampoline白名单中;
    boolean areBackgroundActivityStartsAllowed(int appSwitchState) {
        return areBackgroundActivityStartsAllowed(appSwitchState,
                false /* isCheckingForFgsStart */);
    }

在这里插入图片描述

广播Token的添加和移除

添加

allowTrampoline介绍

应用收到另一个可见应用发送的 PendingIntent。

如果PendingIntent的caller处于可见状态,则跟通知添加的mAllowBgActivityStarts token的效果类似,也会最终添加上面说到的token。

// temporarily allow receivers and services to open activities from background if the
// PendingIntent.send() caller was foreground at the time of sendInner() call
final boolean allowTrampoline = uid != callingUid
    && controller.mAtmInternal.isUidForeground(callingUid)
    && isPendingIntentBalAllowedByCaller(options);

PendingIntentRecord发送时会校验传入的allowBackgroundActivityStart的值,满足如下其一为true

  • mAllowBgActivityStartsForBroadcastSender中存在传入的allowListToken
  • 满足上面的allowTrampoline
                case ActivityManager.INTENT_SENDER_BROADCAST:
                    try {
                        final boolean allowedByToken =
                                mAllowBgActivityStartsForBroadcastSender.contains(allowlistToken);
                        final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null;

                        // If a completion callback has been requested, require
                        // that the broadcast be delivered synchronously
                        int sent = controller.mAmInternal.broadcastIntentInPackage(key.packageName,
                                key.featureId, uid, callingUid, callingPid, finalIntent,
                                resolvedType, finishedReceiver, code, null, null,
                                requiredPermission, options, (finishedReceiver != null), false,
                                userId, allowedByToken || allowTrampoline, bgStartsToken,
                                null /* broadcastAllowList */);
广播分发给app之前触发更新或添加
    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
            return;
        }
        String msgToken = (proc.toShortString() + r.toString()).intern();
        // first, if there exists a past scheduled request to remove this token, drop
        // that request - we don't want the token to be swept from under our feet...
        mHandler.removeCallbacksAndMessages(msgToken);
        // ...then add the token
        proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
    }

移除

一般是post到handler中延时10s去执行移除Token的操作,即可能的后台启动能力在10s内才有效。

    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
        // the receiver had run for less than allowed bg activity start timeout,
        // so allow the process to still start activities from bg for some more time
        String msgToken = (app.toShortString() + r.toString()).intern();
        // first, if there exists a past scheduled request to remove this token, drop
        // that request - we don't want the token to be swept from under our feet...
        mHandler.removeCallbacksAndMessages(msgToken);
        // ...then schedule the removal of the token after the extended timeout
        mHandler.postAtTime(() -> {
            synchronized (mService) {
                app.removeAllowBackgroundActivityStartsToken(r);
            }
        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
    }

无序广播在执行performReceive后即可postActivityStartTokenRemoval

                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
                maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
                maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                        new Intent(r.intent), r.resultCode, r.resultData,
                        r.resultExtras, r.ordered, r.initialSticky, r.userId,
                        filter.receiverList.uid, r.callingUid);
                // parallel broadcasts are fire-and-forget, not bookended by a call to
                // finishReceiverLocked(), so we manage their activity-start token here
                if (filter.receiverList.app != null
                        && r.allowBackgroundActivityStarts && !r.ordered) {
                    postActivityStartTokenRemoval(filter.receiverList.app, r);
                }

有序广播则需等第一个广播分发完成才开始post

    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
        .......
        if (r.allowBackgroundActivityStarts && r.curApp != null) {
            if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
                // if the receiver has run for more than allowed bg activity start timeout,
                // just remove the token for this process now and we're done
                r.curApp.removeAllowBackgroundActivityStartsToken(r);
            } else {
                // It gets more time; post the removal to happen at the appropriate moment
                postActivityStartTokenRemoval(r.curApp, r);
            }
        }

Service Token的添加和移除

Start Service Token的添加和移除

跟广播类似,通过mAllowBgActivityStartsForServiceSender传入allowBackgroundActivityStarts参数去start service。

                case ActivityManager.INTENT_SENDER_SERVICE:
                case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
                    try {
                        final boolean allowedByToken =
                                mAllowBgActivityStartsForServiceSender.contains(allowlistToken);
                        final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null;
                        controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,
                                key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,
                                key.packageName, key.featureId, userId,
                                allowedByToken || allowTrampoline, bgStartsToken);
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "Unable to send startService intent", e);
                    } catch (TransactionTooLargeException e) {
                        res = ActivityManager.START_CANCELED;
                    }
                    break;
            }

启动Service过程中因为传入的allowBackgroundActivityStarts为true,会去执行ServiceRecord的allowBgActivityStartsOnServiceStart相关流程,最终设置将当前的token添加到BackgroundLaunchProcessController的mBackgroundActivityStartTokens列表中。

        if (allowBackgroundActivityStarts) {
            r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken);
        }

在这里插入图片描述

    void allowBgActivityStartsOnServiceStart(@Nullable IBinder originatingToken) {
        mBgActivityStartsByStartOriginatingTokens.add(originatingToken);
        // 添加Token
        setAllowedBgActivityStartsByStart(true);
        if (app != null) {
            mAppForAllowingBgActivityStartsByStart = app;
        }
        // 创建Runnable,并在10s后post
        // This callback is stateless, so we create it once when we first need it.
        if (mCleanUpAllowBgActivityStartsByStartCallback == null) {
            // 创建
            mCleanUpAllowBgActivityStartsByStartCallback = () -> {
                synchronized (ams) {
                    // 如果这不是当前Service的最后一个Token
                    mBgActivityStartsByStartOriginatingTokens.remove(0);
                    if (!mBgActivityStartsByStartOriginatingTokens.isEmpty()) {
                        // There are other callbacks in the queue, let's just update the originating
                        // token
                        // 更新Token
                        if (mIsAllowedBgActivityStartsByStart) {
                            // mAppForAllowingBgActivityStartsByStart can be null here for example
                            // if get 2 calls to allowBgActivityStartsOnServiceStart() without a
                            // process attached to this ServiceRecord, so we need to perform a null
                            // check here.
                            if (mAppForAllowingBgActivityStartsByStart != null) {
                                mAppForAllowingBgActivityStartsByStart
                                        .addOrUpdateAllowBackgroundActivityStartsToken(
                                                this, getExclusiveOriginatingToken());
                            }
                        } else {
                            Slog.wtf(TAG,
                                    "Service callback to revoke bg activity starts by service "
                                            + "start triggered but "
                                            + "mIsAllowedBgActivityStartsByStart = false. This "
                                            + "should never happen.");
                        }
                    } else {
                        // 最后一个Token则移除当前进程Token
                        // Last callback on the queue
                        if (app == mAppForAllowingBgActivityStartsByStart) {
                            // The process we allowed is still running the service. We remove
                            // the ability by start, but it may still be allowed via bound
                            // connections.
                            setAllowedBgActivityStartsByStart(false);
                        } else if (mAppForAllowingBgActivityStartsByStart != null) {
                            // The process we allowed is not running the service. It therefore can't
                            // be bound so we can unconditionally remove the ability.
                            mAppForAllowingBgActivityStartsByStart
                                    .removeAllowBackgroundActivityStartsToken(ServiceRecord.this);
                        }
                        mAppForAllowingBgActivityStartsByStart = null;
                    }
                }
            };
        }
        // 10s后post执行
        // Existing callbacks will only update the originating token, only when the last callback is
        // executed is the grant revoked.
        ams.mHandler.postDelayed(mCleanUpAllowBgActivityStartsByStartCallback,
                ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
    }

Bind Service Token的添加和移除

添加或移除Token都是调用如下两个方法:

    void setAllowedBgActivityStartsByBinding(boolean newValue) {
        mIsAllowedBgActivityStartsByBinding = newValue;
        updateParentProcessBgActivityStartsToken();
    }

    private void updateParentProcessBgActivityStartsToken() {
        if (app == null) {
            return;
        }
        if (mIsAllowedBgActivityStartsByStart || mIsAllowedBgActivityStartsByBinding) {
            // if the token is already there it's safe to "re-add it" - we're dealing with
            // a set of Binder objects
            app.addOrUpdateAllowBackgroundActivityStartsToken(this, getExclusiveOriginatingToken());
        } else {
            app.removeAllowBackgroundActivityStartsToken(this);
        }
    }
添加

bindService的时候如果bind flag中添加了BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,会去添加或更新对应的Token。

            if ((flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) {
                s.setAllowedBgActivityStartsByBinding(true);
            }
移除

unbindService或Service被杀调用removeConnectionLocked时,会去check是否有BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,查看是否需要移除相关Token

            // And do the same for bg activity starts ability.
            if ((c.flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) {
                s.updateIsAllowedBgActivityStartsByBinding();
            }
    void updateIsAllowedBgActivityStartsByBinding() {
        boolean isAllowedByBinding = false;
        for (int conni = connections.size() - 1; conni >= 0; conni--) {
            ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
            for (int i = 0; i < cr.size(); i++) {
                if ((cr.get(i).flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) {
                    isAllowedByBinding = true;
                    break;
                }
            }
            if (isAllowedByBinding) {
                break;
            }
        }
        setAllowedBgActivityStartsByBinding(isAllowedByBinding);
    }

Token使用

Bg start FGS的while in use权限

在这里插入图片描述

        if (ret == REASON_DENIED) {
            final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
                if (pr.uid == callingUid) {
                    if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
                        return REASON_ACTIVITY_STARTER;
                    }
                }
                return null;
            });
            if (allowedType != null) {
                ret = allowedType;
            }
        }

Bg Start Activity

检查callerApp或者同一uid下的其他进程是否允许后台启动。
在这里插入图片描述

        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;
                    }
                }
            }
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值