文中的源代码版本为api23
ActivityManagerService中有两个广播队列:mFgBroadcastQueue和mBgBroadcastQueue。它们都是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的值。启动最主要的区别在于后面两个
- 超时时间
前台广播超时时间:10s
后台广播超时时间:60s
可以看到前/后台广播的超时时间差距非常大,一旦发生超时,就会进入
broadcastTimeoutLocked方法,触发我们所熟知的ANR。 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都有一定的影响
- 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状态,有序广播的派发会被阻断。
- 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. 广播队列选择策略
针对不同的Intent,AMS会选用不同的广播队列,相关的逻辑在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;
可以得到两个关键点
- 接收者将被允许以前台优先级运行
- 更短的时间间隔(也即是前面讲到的超时时间)
Intent默认情况下是不带FLAG_RECEIVER_FOREGROUND标志的,因此通常我们的广播都是在后台广播队列中进行传播的。
3. 总结
通过上面对于源码的分析,我们知道了前台广播队列与后台广播队列的区别
- 前台广播队列的超时时间比后台广播队列短很多
- 后台广播队列在当前用户后台启动服务超过阈值时会暂停广播
- 前台广播允许接收者以前台优先级运行
以及如何在发送广播的时候指定广播传输的队列:通过设置FLAG_RECEIVER_FOREGROUND标志即可。