Android---广播(Broadcast)---广播接收者的注册过程分析
来源:互联网 发布:电子商务网络推广 编辑:程序博客网 时间:2024/05/18 02:58
相信做过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中,等待后续如果有注册者的话就会继续把广播发送给它,更加详细的介绍可以参考http://blog.csdn.net/yihua0607/article/details/6890805 ,里面的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内部成员中,这些都是为了后续发送广播服务的。下一节我们就开始详细介绍广播的发送流程。
参考资料
http://www.cnblogs.com/lwbqqyumidi/p/4168017.html Android总结篇系列:Android广播机制
http://blog.csdn.net/luoshengyang/article/details/6730748 Android系统中的广播(Broadcast)机制简要介绍和学习计划
http://blog.csdn.net/luoshengyang/article/details/6737352 Android应用程序注册广播接收器(registerReceiver)的过程分析
http://blog.csdn.net/yihua0607/article/details/6890805 sendStickyBroadcast 的理解和使用
- Android---广播(Broadcast)---广播接收者的注册过程分析
- Android---广播(Broadcast)---广播发送的过程分析
- android 之 Broadcast(广播) BroadcastReceiver(广播接收者)
- Android 动态注册广播接收者
- Android广播之注册广播(包括静态广播和动态广播的注册)源码分析
- BroadCast广播接收者
- 广播接收者Broadcast Receiver
- 广播接收者--Broadcast
- 广播接收者(Broadcast Receiver)
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android中ExpandableListView的使用(一)
- 从程序员到项目经理(8):程序员加油站 -- 不要死于直率
- JSP 中 如何使用 spring:message 标签
- ceph format1格式image
- C++内存管理------>以对象管理资源(Effective C++)
- Android---广播(Broadcast)---广播接收者的注册过程分析
- linux四种查找命令的方法
- 利用 Chrome 开发者工具远程调试 Android 中的原生 WebView
- python中的爬虫神器 XPath 介绍
- 常用基础命令
- web开发中spring集成shiro进行权限管理
- 将下载到本地的JAR包手动添加到Maven仓库
- Neutron的介绍
- PHP学习笔记1:基础知识及WAMPServer自定义网站根目录