相信做过Android开发的人一定接触过广播相关的操作,从注册广播接收者到发送广播等一系列过程,本系列文章主要是向大家介绍广播背后的源码分析过程,广播相关的注意事项以及如何去解决广播相关的问题,本系列总共分为三个部分:
- Android—广播(Broadcast)—广播接收者的注册过程分析
- Android—广播(Broadcast)—广播的发送过程分析
- Android—广播(Broadcast)—广播的注意事项及相关问题分析
Android 广播机制的实现采用了观察者模式,基于消息的发布和订阅的模型,整套流程的核心操作都在ActivityManagerService(以下简称AMS),大致的流程如下:
1.客户端将BroadcastReceiver通过Binder机制向AMS注册。
2.广播的发送者通过Binder机制向AMS发送广播。
3.AMS根据广播查找到相关的BroadcastReceiver,并通过发送消息将其放入消息循环队列中,然后通过异步方式来把广播发送给相应的接收者。
4.消息被执行时,最终会回调BroadcastReceiver中的onReceive()方法。
本系列是基于Android 6.0的代码进行分析,下面就先分析广播接收者的注册过程。
通常注册广播接收者只需要在Activity或者Service中调用registerReceiver()来实现,其实不管是从哪里调用,两者最终都是会调用到ContextImpl.java中的registerReceiver(),然后内部通过Binder机制调用到AMS中的registerReceiver()方法,其实从下面的流程图看出,整个注册过程还是比较简单的。
ContextImpl.java中的registerReceiver()最终调用到了registerReceiverInternal()方法:
ContextImpl.java
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context) {
//IIntentReceiver 接口对象rd是一个binder对象,会被传入到AMS中,AMS在收到相应广播后,就是通过
//这个rd再通知到客户端这边去接收广播
IIntentReceiver rd = null;
if (receiver != null) {
//mPackageInfo是一个LoadedApk对象, 其在ContextImpl初始化的时候就已经赋值了,context对象
//也即最开始调用registerReceiver()的activity或者service对象
if (mPackageInfo != null && context != null) {
if (scheduler == null) {
//也就是ActivityThread.java中的mH对象
scheduler = mMainThread.getHandler();
}
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
rd = new LoadedApk.ReceiverDispatcher(
receiver, context, scheduler, null, true).getIIntentReceiver();
}
}
try {
return ActivityManagerNative.getDefault().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName,
rd, filter, broadcastPermission, userId);
} catch (RemoteException e) {
return null;
}
}
通过mPackageInfo.getReceiverDispatcher()获取到IIntentReceiver接口对象rd,从下面实现可以看出,rd就是InnerReceiver类的对象。
LoadedApk.java
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
Context context, Handler handler,
Instrumentation instrumentation, boolean registered) {
synchronized (mReceivers) {
LoadedApk.ReceiverDispatcher rd = null;
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
if (registered) {
map = mReceivers.get(context);
if (map != null) {
rd = map.get(r);
}
}
if (rd == null) {
rd = new ReceiverDispatcher(r, context, handler,
instrumentation, registered);
if (registered) {
if (map == null) {
map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
mReceivers.put(context, map);
}
map.put(r, rd);
}
} else {
rd.validate(context, handler);
}
rd.mForgotten = false;
return rd.getIIntentReceiver();
}
}
ReceiverDispatcher(BroadcastReceiver receiver, Context context,
Handler activityThread, Instrumentation instrumentation,
boolean registered) {
...
//这个就是最终传给AMS端的binder代理对象
mIIntentReceiver = new InnerReceiver(this, !registered);
...
}
IIntentReceiver getIIntentReceiver() {
return mIIntentReceiver;
}
final static class InnerReceiver extends IIntentReceiver.Stub {
...
}
等到rd准备好后,在ContextImpl.registerReceiverInternal()最后调用了ActivityManagerNative.getDefault().registerReceiver()方法,并把相关对象传入到函数参数中,最终通过Binder调用到了AMS中的registerReceiver()方法里。
粗看registerReceiver()方法,发现函数体的实现代码比较偏多,经常分析framework代码就知道,framework中很多函数的实现代码都非常多,看上去确实挺让人崩溃的,对于实现代码比较偏多的函数,我个人认为比较好的分析方式是对函数进行分段来分析,先大致浏览函数的所有代码,对函数有一个大致的了解,然后按照执行顺序分出对应的几个大的段,针对每段进行具体的分析。
这里我将registerReceiver()分成四个部分来分析,分别是:检查调用者的合法性和获取对应的Uid及Pid,搜集sticky广播对应的Intent,给注册进来的receiver创建对应的对象并保存,发送sticky广播给receiver,下面就基于这四个部分来分析registerReceiver()的实现。
- 检查调用者的合法性和获取对应的Uid及Pid
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
enforceNotIsolatedCaller("registerReceiver");
ArrayList<Intent> stickyIntents = null;
ProcessRecord callerApp = null;
int callingUid;
int callingPid;
synchronized(this) {
if (caller != null) {
callerApp = getRecordForAppLocked(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when registering receiver " + receiver);
}
if (callerApp.info.uid != Process.SYSTEM_UID &&
!callerApp.pkgList.containsKey(callerPackage) &&
!"android".equals(callerPackage)) {
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
}
callingUid = callerApp.info.uid;
callingPid = callerApp.pid;
} else {
callerPackage = null;
callingUid = Binder.getCallingUid();
callingPid = Binder.getCallingPid();
}
这部分其实就是检查调用者所在的进程是否存在,如果callerApp不存在,也就是调用者所在的进程都没有,那肯定是不合法的,或者进程块callerApp存在,但是callerApp里面没有callerPackage,也即调用者的包名都不在进程里面,肯定也是不合法的。检查完后再获取对应的callingUid和callingPid,如果caller直接为null,那么此时的uid和pid直接取binder调用者的uid和pid即可。
- 搜集sticky广播对应的Intent
ActivityManagerService.java
userId = handleIncomingUser(callingPid, callingUid, userId,
true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
Iterator<String> actions = filter.actionsIterator();
if (actions == null) {
ArrayList<String> noAction = new ArrayList<String>(1);
noAction.add(null);
actions = noAction.iterator();
}
// Collect stickies of users
int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
while (actions.hasNext()) {
String action = actions.next();
for (int id : userIds) {
ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
if (stickies != null) {
ArrayList<Intent> intents = stickies.get(action);
if (intents != null) {
if (stickyIntents == null) {
stickyIntents = new ArrayList<Intent>();
}
stickyIntents.addAll(intents);
}
}
}
}
}
ArrayList<Intent> allSticky = null;
if (stickyIntents != null) {
final ContentResolver resolver = mContext.getContentResolver();
// Look for any matching sticky broadcasts...
//尽管上面已经根据action查找到了相应的intent,但是此处还是要继续检查intent是否跟filter相匹配
for (int i = 0, N = stickyIntents.size(); i < N; i++) {
Intent intent = stickyIntents.get(i);
// If intent has scheme "content", it will need to acccess
// provider that needs to lock mProviderMap in ActivityThread
// and also it may need to wait application response, so we
// cannot lock ActivityManagerService here.
if (filter.match(resolver, intent, true, TAG) >= 0) {
if (allSticky == null) {
allSticky = new ArrayList<Intent>();
}
allSticky.add(intent);
}
}
}
// The first sticky in the list is returned directly back to the client.
Intent sticky = allSticky != null ? allSticky.get(0) : null;
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
if (receiver == null) {
return sticky;
}
这里涉及到sticky这种类型的广播,大致意思就是这种类型的广播发出后会留在AMS中,等待后续如果有注册者的话就会继续把广播发送给它,更加详细的介绍可以参考 ,里面的mStickyBroadcasts是一个SparseArray类型的对象,其key即当前的User id,在后面介绍广播的发送时会知道,在发送sticky类型的广播时会记录到此变量中,不过这类广播从Android 5.0开始,相关的发送sticky广播函数都已经废弃了,现在用的比较多的主要都是一些框架内部的广播。
- 给注册进来的receiver创建对应的对象并保存
ActivityManagerService.java
synchronized (this) {
if (callerApp != null && (callerApp.thread == null
|| callerApp.thread.asBinder() != caller.asBinder())) {
// Original caller already died
return null;
}
ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
if (rl == null) {
rl = new ReceiverList(this, callerApp, callingPid, callingUid,
userId, receiver);
if (rl.app != null) {
rl.app.receivers.add(rl);
} else {
try {
receiver.asBinder().linkToDeath(rl, 0);
} catch (RemoteException e) {
return sticky;
}
rl.linkedToDeath = true;
}
mRegisteredReceivers.put(receiver.asBinder(), rl);
} else if (rl.uid != callingUid) {
throw new IllegalArgumentException(
"Receiver requested to register for uid " + callingUid
+ " was previously registered for uid " + rl.uid);
} else if (rl.pid != callingPid) {
throw new IllegalArgumentException(
"Receiver requested to register for pid " + callingPid
+ " was previously registered for pid " + rl.pid);
} else if (rl.userId != userId) {
throw new IllegalArgumentException(
"Receiver requested to register for user " + userId
+ " was previously registered for user " + rl.userId);
}
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
permission, callingUid, userId);
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadcast");
}
mReceiverResolver.addFilter(bf);
这部分算是registerReceiver()方法的核心部分了,这里的很多实现都是为了后续发送广播服务的。
在注册广播接收者时,除了要传入一个receiver外,还需要一个IntentFilter,用来告知要监听哪些广播,另外有可能同一个receiver,跟多个不同的IntentFilter一同注册进来,所以ReceiverList 是一个ArrayList,而BroadcastFilter是继承自IntentFilter,BroadcastFilter起到了把receiver跟IntentFilter关联起来的作用,mRegisteredReceivers是以receiver为key,ReceiverList为value的HashMap对象,用来存储一个receiver及其对应的所有IntentFilter,最后把bf加入到mReceiverResolver中,这几个变量在后续发送广播时查找广播的接收者会经常用到,
- 发送sticky广播给receiver
ActivityManagerService.java
// Enqueue broadcasts for all existing stickies that match
// this filter.
if (allSticky != null) {
ArrayList receivers = new ArrayList();
receivers.add(bf);
final int stickyCount = allSticky.size();
for (int i = 0; i < stickyCount; i++) {
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,
null, 0, null, null, false, true, true, -1);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
}
return sticky;
最后一部分是发送sticky类型的广播,根据intent得到对应的BroadcastQueue,然后调用其scheduleBroadcastsLocked()方法将广播发送出去,这部分也是下一篇需要重点讲解的内容,所以这里就先不详细展开了,请大家直接移步下一篇文档。
最后,整个广播接收者的注册过程就结束了,回顾一下,总的来说还是比较简单,客户端传入一个Binder对象给AMS服务端作为标识,然后服务端内部会根据传入的receiver以及filter新建或者创健相关对象并放入到指定的AMS内部成员中,这些都是为了后续发送广播服务的。下一节我们就开始详细介绍广播的发送流程。
参考资料
Android总结篇系列:Android广播机制
Android系统中的广播(Broadcast)机制简要介绍和学习计划
Android应用程序注册广播接收器(registerReceiver)的过程分析
sendStickyBroadcast 的理解和使用