Broadcast之前/后台广播队列

4,205 阅读4分钟

文中的源代码版本为api23

ActivityManagerService中有两个广播队列:mFgBroadcastQueuemBgBroadcastQueue。它们都是BroadcastQueue的实例,只不过初始化时的入参有些不一样。

1. 区别


//ActivityManagerService.java
static final int BROADCAST_FG_TIMEOUT = 10 * 1000;
static final int BROADCAST_BG_TIMEOUT = 60 * 1000;

public ActivityManagerService(Context systemContext){
    //...
    mFgBroadcastQueue = new BroadcastQueue(this, 
                                    mHandler,
                                    "foreground",
                                    BROADCAST_FG_TIMEOUT,
                                    false);
    mBgBroadcastQueue = new BroadcastQueue(this,
                                    mHandler,
                                    "background",
                                    BROADCAST_BG_TIMEOUT,
                                    true);
    //...
}

//BroadcastQueue.java
BroadcastQueue(ActivityManagerService service,
            Handler handler,
            String name,//名称
            long timeoutPeriod,//超时时间
            boolean allowDelayBehindServices) {//是否允许等待上一个广播接收者所在的进程的Service结束流程
    mService = service;
    mHandler = new BroadcastHandler(handler.getLooper());
    mQueueName = name;
    mTimeoutPeriod = timeoutPeriod;
    mDelayBehindServices = allowDelayBehindServices;
}

可以看到前台广播与后台广播主要用三个方面的区别:名称、超时时间以及mDelayBehindServices的值。启动最主要的区别在于后面两个

  1. 超时时间 前台广播超时时间:10s 后台广播超时时间:60s 可以看到前/后台广播的超时时间差距非常大,一旦发生超时,就会进入broadcastTimeoutLocked方法,触发我们所熟知的ANR
  2. mDelayBehindServices 这个标志就比较有意思了,它对有序广播会有一定的影响。如果该变量为true,那么BroadcastQueue就可能陷入一种名为WAITING_SERIVCES的状态。相关代码在BroadcastQueue.finishReceiverLocked方法中(广播派发成功之后由接收者回调AMS时触发),

1.1 mDelayBehindServices字段

public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
    //...

    if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices
            && r.queue.mOrderedBroadcasts.size() > 0
            && r.queue.mOrderedBroadcasts.get(0) == r) {
        ActivityInfo nextReceiver;
        if (r.nextReceiver < r.receivers.size()) {
            Object obj = r.receivers.get(r.nextReceiver);
            nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
        } else {
            nextReceiver = null;
        }
        // Don't do this if the next receive is in the same process as the current one.
        // 当前接收者与下一个接收者必须不处于同一个进程
        // 才会进入该if语句内
        if (receiver == null || nextReceiver == null
                || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
                || !receiver.processName.equals(nextReceiver.processName)) {
            //...
            //如果当前用户有正在启动的后台服务
            //且待启动服务已经超过可一个阈值(hasBackgroundServices内部有做判断)
            //则进入WAITING_SERVICES状态
            if (mService.mServices.hasBackgroundServices(r.userId)) {
                Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
                r.state = BroadcastRecord.WAITING_SERVICES;
                return false;
            }
        }
    }
    //...
    return state == BroadcastRecord.APP_RECEIVE
            || state == BroadcastRecord.CALL_DONE_RECEIVE;

可以看到,如果当前的广播接收者和下一个广播接收者不处于同一个进程,且当前有正在启动的后台服务(用户维度)那么BroadcastQueue进入WAITING_SERVICES状态。 该状态对processNextBroadcast内对有序广播的处理以及广播超时方法broadcastTimeoutLocked都有一定的影响

  1. processNextBroadcast 之前我们分析过,在派发有序广播之前,BroadcastQueue首先会通过一个while循环找到下一个广播接收者。这个循环内部有一段代码:
if (r.state != BroadcastRecord.IDLE) {
    if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
            "processNextBroadcast("
            + mQueueName + ") called when not idle (state="
            + r.state + ")");
    return;
}

可见,当处于WAITING_SERVICES状态,有序广播的派发会被阻断。

  1. broadcastTimeoutLocked 广播超时的代码中有下面这个代码段:
BroadcastRecord br = mOrderedBroadcasts.get(0);
if (br.state == BroadcastRecord.WAITING_SERVICES) {
    //...
    br.curComponent = null;
    br.state = BroadcastRecord.IDLE;
    processNextBroadcast(false);
    return;
}

可见,对于处于WAITING_SERVICES状态的广播超时,并不会触发ANR,而是默默地将状态重新设置为IDLE,然后主动调用processNextBroadcast方法处理下一个消息。

mDelayBehindServices字段体现了AMS的一个策略:如果当前用户正在启动的后台服务数量超过阈值,那么会暂停后台广播队列中的有序广播的派发。

2. 广播队列选择策略

针对不同的IntentAMS会选用不同的广播队列,相关的逻辑在broadcastQueueForIntent方法中

//ActivityManagerService.java
BroadcastQueue broadcastQueueForIntent(Intent intent) {
    final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
    //...
    return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}

可以看到逻辑非常简单,就是判断了一下是否包含了FLAG_RECEIVER_FOREGROUND标志,如果是,则使用前台广播队列,否则使用后台广播队列。 参考源码中对于FLAG_RECEIVER_FOREGROUND的注解

/**
 * If set, when sending a broadcast the recipient is allowed to run at
 * foreground priority, with a shorter timeout interval.  During normal
 * broadcasts the receivers are not automatically hoisted out of the
 * background priority class.
 */
public static final int FLAG_RECEIVER_FOREGROUND = 0x10000000;

可以得到两个关键点

  1. 接收者将被允许以前台优先级运行
  2. 更短的时间间隔(也即是前面讲到的超时时间)

Intent默认情况下是不带FLAG_RECEIVER_FOREGROUND标志的,因此通常我们的广播都是在后台广播队列中进行传播的。

3. 总结

通过上面对于源码的分析,我们知道了前台广播队列与后台广播队列的区别

  1. 前台广播队列的超时时间比后台广播队列短很多
  2. 后台广播队列在当前用户后台启动服务超过阈值时会暂停广播
  3. 前台广播允许接收者以前台优先级运行

以及如何在发送广播的时候指定广播传输的队列:通过设置FLAG_RECEIVER_FOREGROUND标志即可。