Android 广播机制浅析【1】注册广播

最近在分析一个广播 ANR 问题时,对广播机制的代码流程做了大致的l了解,作为 Android 四大组件之一,广播 在 Android 中的应用非常广泛,这里结合自己阅读源码过程中的笔记从代码流程上的做简要分析,对广播机制中涉及到进程进程优先级的切换等其他部分不做叙述。

本文为广播机制浅析系列第一篇,主要分析发送广播的流程。

广播简述

从处理时机的角度来看,广播被分为有序广播和无序广播。有序广播通过 Context#sendBroadcast() 发送,接收者逐个进行处理,接收顺序可以通过匹配的 intent-filter 的 android:priority 属性来控制,具有相同优先级的接收器将按随机顺序运行,期间可以被中断也可以进行数据的传输,如果处理耗时过长则会发生 ANR,无序广播通过 Context#sendBroadcast() 发送,会同时发送给所有的接收者来处理,不会发生 ANR。

从广播存在时间的角度,可以分为黏性广播和非黏性广播,黏性广播在被所有接收者处理完成后也不会消失,而是会一直保存在系统中,当有新接收者注册后,立即就会收到该广播。

还可以分为前台广播和后台广播,通过给 Intent 添加 flag 可以切换为前台广播:

1
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)

前台广播超时时间为 10s,后台广播为 60s。

注册

应用可以通过静态注册和动态注册来接收广播:

静态注册

在 AndroidManifest.xml 的 receiver 标签中声明:

1
2
3
4
5
6
<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>

MyBroadcastReceiver 需要继承 BroadcastReceiver,并实现 onReceive(Context, Intent) 方法,PMS 会在应用安装时注册接收器,该接收器会成为应用的一个独立入口点,即如果应用当前未运行,系统会启动应用并发送广播。系统会创建新的 BroadcastReceiver 组件对象来处理它接收到的每个广播。此对象仅在调用 onReceive(Context, Intent) 期间有效。一旦从此方法返回代码,系统便会认为该组件不再活跃。

动态注册

通过 Context 中提供的对应方法来注册广播接收者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// android.app.ContextImpl
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
return registerReceiverInternal(receiver, user.getIdentifier(),
filter, broadcastPermission, scheduler, getOuterContext(), 0);
}

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission, Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
...
if (scheduler == null) {
scheduler = mMainThread.getHandler(); // 主线程 Handler
}
rd = new LoadedApk.ReceiverDispatcher(receiver, context, scheduler, null, true).getIIntentReceiver();
final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), rd,
filter, broadcastPermission, userId, flags);
...
}

registerReceiverInternal 中参数 broadcastPermission 表示广播所需权限,scheduler 用于指定处理广播的线程,默认情况下为主线程。rd 是 android.app.LoadedApk.ReceiverDispatcher.InnerReceiver 类型:

1
2
3
4
static final class ReceiverDispatcher {
final static class InnerReceiver extends IIntentReceiver.Stub {
}
}

作为 Binder 服务端接收 AMS 在处理广播时的调用。接着则又是是通过 Binder 调用到 ActivityManagerService#registerReceiverWithFeature 中,此时就从注册广播应用进程走到了 SystemServer 进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
String callerFeatureId, IIntentReceiver receiver, IntentFilter filter,
String permission, int userId, int flags) {
...
synchronized(this) {
...
Iterator<String> actions = filter.actionsIterator(); //注册的广播
// mStickyBroadcasts 以 userId 为 key,保存了所有用户的黏性广播,这里提取该 userId
// 和 USER_ALL 下的所有黏性广播到 stickyIntents中
int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
while (actions.hasNext()) {
...
}
}

ArrayList<Intent> allSticky = null; //所有需要处理的黏性广播
if (stickyIntents != null) {
final ContentResolver resolver = mContext.getContentResolver();
// 查找匹配的黏性广播
for (int i = 0, N = stickyIntents.size(); i < N; i++) {
Intent intent = stickyIntents.get(i);
...
if (filter.match(resolver, intent, true, TAG) >= 0) {
...
allSticky.add(intent);
}
}
}
...
synchronized (this) {
...
//mRegisteredReceivers 以 IIntentReceiver 为 key 保存有所的接收者
ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
if (rl == null) {
rl = new ReceiverList(...);
//保存该接收者
mRegisteredReceivers.put(receiver.asBinder(), rl);
}
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
permission, callingUid, userId, instantApp, visibleToInstantApps);
rl.add(bf);
// 将所有匹配的黏性广播入队进行发送
if (allSticky != null) {
...
final int stickyCount = allSticky.size();
for (int i = 0; i < stickyCount; i++) {
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
//参数 _sticky 为 true
BroadcastRecord r = new BroadcastRecord(...);
// 将黏性广播添加到并行队列中
queue.enqueueParallelBroadcastLocked(r);
//开始分发
queue.scheduleBroadcastsLocked();
}
}
return sticky;
}
}

之前提到,黏性广播在发送后就会一直存在系统中,从这中可以看出,其是被保存在 ActivityManagerService 的成员变量 mRegisteredReceivers 中,并且当有用户在注册该广播监听时,立马就会收到。如果注册的事非黏性广播,则会将该接收者保存到 mRegisteredReceivers 中。

参考

Android Developer:广播概览