BroadcastReceiver 管理
Broadcast实质上是提供了一种更灵活的使用Intent的方式。
无论是启动Activity,还是Service,其实都是通过Intent的发送和过滤来调用应用中的功能,但是Activity、Service、ContentProvider所完成的功能是固定的
而对于BroadcastReceiver可以做任意的事情,同时广播的发送和接收方式也更加丰富灵活
理解BroadcastReceiver
BroadcastReceiver是一种很简单的组件,甚至在ActivityThread中都没有管理它的数据结构。
BroadcastReceiver的本质就是通过Intent来执行应用中的一个方法,在应用中并不需要一个长期存在的对象
虽然应用中没有专门的数据结构来管理BroadcastReceiver,但是在AMS中还是需要的,因为BroadcastReceiver可以在运行时动态向AMS注册,AMS中需要有数据结构来管理动态接收者
BroadcastReceiver可以通过两种方式接收广播
- 通过
AndroidManifest中的<receiver/>标签注册 - 通过
AMS的registerReceiver()接口注册
关于广播使用上的知识大家可以在官网学习:广播基础知识
我们看下BroadcastReceiver类相关的重要定义:
public abstract class BroadcastReceiver {
private PendingResult mPendingResult;
public abstract void onReceive(Context context, Intent intent);
public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
return res;
}
}
实现一个广播接收器只需要继承BroadcastReceiver,然后实现它的抽象方法onReceive()就可以了。这也是最简单的广播的用法。
不过从BroadcastReceiver的类定义中还有一个mPendingResult变量和goAsync()方法,它们有什么作用呢?
它们主要是针对当接收到广播后需要做一些耗时操作,而且还有可能需要返回处理结果的情况
- 我们知道
BroadcastReceiver对象的onReceive()如果长时间不返回,会引发ANR - 因此如果要执行耗时的操作,必须在其他线程中完成
mPendingResult变量的存在就是为了上述需求。而goAsync()方法提供了获取mPendingResult变量的接口。
从goAsync()方法的实现可以看到
- 方法在将
mPendingResult对象的指针返回后。同时也会将mPendingResult的值置为null
按照官方说法:
一旦执行了
goAsync()方法,在onReceive()结束前,需要将mPendingResult对象的指针传递到新线程中去,在新线程处理完成后必须调用mPendingResult对象的finish()方法来通知AMS。
我们看下PendingResult的finish()方法:
public final void finish() {
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
if (QueuedWork.hasPendingWork()) {
QueuedWork.queue(new Runnable() {
@Override public void run() {
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing broadcast after work to component " + mToken);
sendFinished(mgr);
}
}, false);
} else {
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing broadcast to component " + mToken);
sendFinished(mgr);
}
} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing broadcast to " + mToken);
final IActivityManager mgr = ActivityManager.getService();
sendFinished(mgr);
}
}
public void sendFinished(IActivityManager am) {
synchronized (this) {
...
try {
...
if (mOrderedHint) {
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
mAbortBroadcast, mFlags);
} else {
am.finishReceiver(mToken, 0, null, null, false, mFlags);
}
} catch (RemoteException ex) {
}
}
}
核心操作就是调用AMS的finishReceiver()方法,这才是当前广播接收器的结束的标志。
正常情况下(没有执行过goAsync())当onReceive()调用完成后,AMS检测到mPendingResult不为空,便会自动执行finish()方法,而对于执行过goAsync()的广播接收器,AMS便不会主动执行finish()了
这个
finish()方法很重要,本章最后也有它的身影
广播的分类
从广播的发送方式来划分,有4类广播。
- 普通广播:通过
Context中的方法sendBroadcast()和sendBroadcastAsUser()发送的广播属于普通广播。- 普通广播的特点是发送给系统当前的所有注册的接收者
- 接收者接收到广播的顺序也是不确定的
- 有序广播:通过
Context中的方法sendOrderedBroadcast()和sendOrderedBroadcastAsUser()发送的广播属于有序广播。- 有序广播的发送顺序是按照接收者的优先级来决定的,系统默认范围是
-1000-1000,但实际上没有明确的数值限定,可以到int最大值 - 接收器的优先级通过匹配的
intent-filter的android:priority属性来控制 - 具有相同优先级的接收器将按随机顺序运行(这部分书中和官网的描述并不一致,看完源码再说啦)
- 当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器
- 有序广播的发送顺序是按照接收者的优先级来决定的,系统默认范围是
- 粘性广播:通过
Context中的方法sendStickyBroadcast()和sendOrderedBroadcastAsUser()发送的广播属于粘性广播。- 和前两个广播不同,粘性广播能将广播发送给系统中以后注册的接收者,甚至是新安装应用中的接收者
- 官网解释出于安全因素,在
Android 9上,这两个接口被标记为了Deprecated - 不过这并不影响它在
Framework中的使用,我们看个官网的例子:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); // 对于一个粘性广播来说,注册监听时便可以得到当前电池状态的 Intent Intent batteryStatus = context.registerReceiver(null, ifilter); - 本地广播:通过
LocalBroadcastManager.sendBroadcast发送的广播属于本地广播- 本地广播会将广播发送给与发送者位于同一应用中的接收者
- 当不需要跨应用发送广播时,建议使用本地广播
- 这种实现方法的效率更高(无需进行进程间通信)
- 而且无需担心其他应用在收发广播时带来的任何安全问题
广播的数据结构
在AMS中,所有注册的广播接收者都放在成员变量mRegisteredReceivers中,定义如下:
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
mRegisteredReceivers是一个HashMap,其中
-
key为接收者的IBinder对象 -
value为接收者的对象ReceiverList- 因为一个接收者中可能包含多个
IntentFilter,所以ReceiverList是一个数组
final class ReceiverList extends ArrayList<BroadcastFilter>- 而
BroadcastFilter则是继承了IntentFilter,定义如下:
final class BroadcastFilter extends IntentFilter { final ReceiverList receiverList; // 所属 receiver 的引用 final String packageName; // 所在应用的包名 final String requiredPermission; // 发送广播时需要声明的权限字符串 final int owningUid; // 所在应用的 uid final int owningUserId; // 所在应用的 userid ... } - 因为一个接收者中可能包含多个
ReceiverList的关键定义如下:
final class ReceiverList extends ArrayList<BroadcastFilter>
implements IBinder.DeathRecipient {
final ActivityManagerService owner;
public final IIntentReceiver receiver; // 用户进程中定义的 IntentReceiver
public final ProcessRecord app; // 所属用户进程的 ProcessRecord 对象
public final int pid; // 所属用户进程的pid
public final int uid; // 所属用户进程的uid
public final int userId; // 用户id
BroadcastRecord curBroadcast = null; //
boolean linkedToDeath = false; // 是否注册了 binder 死亡监听
}
发送广播时,AMS中收到的广播消息首先会保存在mBroadcastQueues对象中,然后再发送给用户进程中的接收者。mBroadcastQueues是一个只有两个元素的数组,定义如下:
final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2];
此外,AMS中还定义了两个变量:
BroadcastQueue mFgBroadcastQueue;
BroadcastQueue mBgBroadcastQueue;
AMS在构造方法中将这两个变量和mBroadcastQueues集合进行了关联:
public ActivityManagerService(Context systemContext) {
...
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
"foreground", BROADCAST_FG_TIMEOUT, false);
mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
"background", BROADCAST_BG_TIMEOUT, true);
mBroadcastQueues[0] = mFgBroadcastQueue;
mBroadcastQueues[1] = mBgBroadcastQueue;
...
}
mFgBroadcastQueue用来保存带有FLAG_RECEIVER_FOREGROUND标志的广播,它要求接收者进程以foreground的优先级运行,这样执行的更快。- 如果不特别指定,一般的广播都会保存在
mBgBroadcastQueue中
我们看下BroadcastQueue类的主要结构:
public final class BroadcastQueue {
final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
}
mParallelBroadcasts用来保存所有的普通广播mOrderedBroadcasts用来保存所有的有序广播
粘性广播在哪里呢?
系统中所有的粘性广播都保存在AMS的成员变量mStickyBroadcasts中,定义如下:
final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
mStickyBroadcasts是一个SparseArray类型的数组,使用用户Id作为索引,保存的是ArrayMap对象ArrayMap对象储存的是某个用户发送的所有的粘性广播,每条记录以Intent的action字符串作为索引,保存的内容是一个ArrayList对象ArrayList对象其中储存了包含该action的Intent
广播的注册过程
动态注册是通过接口registerReceiver()完成的。应用注册receiver时,并不是直接调用AMS的接口来完成的,而是通过Context类中的方法,因为AMS的接口需要提供应用的ApplicationThread类的IBinder对象来作为参数,应用中得到这个对象比较麻烦。
Context中的registerReceiver()最终调用的是ContextImpl的registerReceiverInternal()方法,代码如下:
class ContextImpl{
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
...
try {
final Intent intent = ActivityManager.getService().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
broadcastPermission, userId, flags);
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
intent.prepareToEnterProcess();
}
return intent;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
registerReceiverInternal()方法最终调用的是AMS的registerReceiver()方法,代码如下:
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
int flags) {
// 确保不是 isolated 进程
enforceNotIsolatedCaller("registerReceiver");
ArrayList<Intent> stickyIntents = null;
ProcessRecord callerApp = null;
final boolean visibleToInstantApps
= (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
int callingUid

本文深入剖析BroadcastReceiver机制,包括其工作原理、应用场景及广播类型。详细介绍了动态与静态注册过程、粘性广播特性以及如何处理耗时操作。
最低0.47元/天 解锁文章
1776





