笔记:BroadcastReceiver的运行过程
广播概述
- 广播用来在组件之间传递消息,可以是同进程或跨进程。
- 广播机制是基于发布订阅的事件驱动模型,使用上比Binder通信(跨进程接口回调)更低耦合、简单。
- ActivityManagerService(简称AMS)作为广播消息发布订阅的注册中心,广播接收器(Broadcast Receiver,简称Receiver)以静态或动态方式注册到AMS。
- 广播底层实现就是Binder,包括使用Binder进行跨进程回调接口注册,发送广播时使用Binder异步通信发送广播给接收者所在进程。
- 继承Context的Service或Activity组件可以发送有序或无需广播到AMS。
- AMS把消息发送给接收此广播类型的Receiver。
- 有序广播根据Receiver优先级被接收,动态注册的先收到消息,而无需广播同时发送给所有Receiver。
- 广播的生命周期:动态注册的广播组件其生命周期和其使用者关联。静态注册的广播,每次收到广播时一个Receiver被创建,在主线程中执行其onReceive()方法,方法返回后,Receiver组件即等待销毁。
- 因为onReceive在主线程执行,所以耗时操作会引起ANR。
- 耗时操作应该启动一个Service去执行,不能是bindService()这样的,因为bindService()本身是和Service进行通信的方式,而不是增加Receiver存活时间的方式。onReceive()本身的执行就应该很短。startService()保证一个耗时操作放在Service中得已运行更长的时间得到执行。
NOTE:
使用Broadcast完成组件间的事件通知,在跨进程的情况下,比使用Binder进行跨进程接口回调要简单且更加低耦合。
案例
下面以在MyActivity中注册MyReceiver为例,MyReceiver接收Action为“com.hxw.bot.broadcast.ACTION”。
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
}
};
IntentFilter filter = new IntentFilter("com.hxw.bot.broadcast.ACTION");
registerReceiver(receiver, filter);
一个filter可以拦截多个Action。
广播注册过程
1. ContextWrapper.registerReceiver
2. ContextImpl.registerReceiver
LoadedApk mPackageInfo;
ActivityThread mMainThread;
...
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
return registerReceiverInternal(receiver, filter, broadcastPermission,
scheduler, getOuterContext());
}
...
private Intent registerReceiverInternal(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
...
}
}
try {
return ActivityManagerNative.getDefault().registerReceiver(
mMainThread.getApplicationThread(),
rd, filter, broadcastPermission);
...
}
mMainThread.getHandler()返回一个当前进程的主线程上的Handler,
scheduler用来将AMS回调rd方法从Binder线程池的线程中转到主线程中进行。
LoadedApk.getReceiverDispatcher()将MyReceiver包装成一个IIntentReceiver rd,
rd类型是InnerReceiver(继承自IIntentReceiver.Stub)——一个Binder对象,发送给AMS其代理,完成“广播接收器”回调接口注册。
参数context为ContextImpl.getOuterContext(),它返回MyActivity对象。也就是注册MyReceiver的
Context对象,MyReceiver和MyActivity关联。
LoadedApk类有一个字段mReceivers:
private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mReceivers
= new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
它以Receiver关联的Context对象(也就是执行注册的context对象)作为key,存储了对应context注册的所有的BroadcastReceiver对象。
class ReceiverDispatcher {
final IIntentReceiver.Stub mIIntentReceiver; // 作为跨进程接口实例的Binder对象
final BroadcastReceiver mReceiver; // MyReceiver
final Context mContext; // MyActivity对象
final Handler mActivityThread; // 主线程的handler
...
}
3. ActivityManagerProxy.registerReceiver
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver,
IntentFilter filter, String perm) throws RemoteException
将参数写入Parcel data,然后向AMS发起进程间通信REGISTER_RECEIVER_TRANSACTION。
4. AMS.registerReceiver
AMS.registerReceiver()响应REGISTER_RECEIVER_TRANSACTION。
ActivityManagerService {
/**
* Keeps track of all IIntentReceivers that have been registered for
* broadcasts. Hash keys are the receiver IBinder, hash value is
* a ReceiverList.
*/
final HashMap mRegisteredReceivers = new HashMap();
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver, IntentFilter filter,
String requiredPermission) throws RemoteException {
...
ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
...
BroadcastFilter bf = new BroadcastFilter(filter, rl, permission);
rl.add(bf);
mReceiverResolver.addFilter(bf);
}
}
参数receiver:
AMS收到的“广播接收器”是MyReceiver对应的InnerReceiver的BinderProxy。
AMS使用BroadcastFilter记录已经注册的Receiver:
class BroadcastFilter extends IntentFilter {
// Back-pointer to the list this filter is in.
final ReceiverList receiverList;
final String requiredPermission;
BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
String _requiredPermission) {
super(_filter);
receiverList = _receiverList;
requiredPermission = _requiredPermission;
}
}
BroadcastFilter保存了关联的filter、receiverList。
class ReceiverList extends ArrayList<BroadcastFilter>
implements IBinder.DeathRecipient {
final ActivityManagerService owner;
public final IIntentReceiver receiver;
...
}
ReceiverList记录了某个Receiver——IIntentReceiver对象所接收的所有广播。
因为一个MyReceiver这样的对象可以用来同时接收多种广播类型。
AMS最后使用mReceiverResolver来根据发送的广播对应的IntentFilter找到合适的receiver并
调用。
广播发送过程
在Service或Activity中,这里假设是MyService中,通知MyActivity更新进度:
int progress = 1;
...
Intent intent = new Intent("com.hxw.bot.broadcast.ACTION");
intent.putExtra("progress", progress);
sendBroadcast(intent);
阶段1:发送广播消息给AMS
广播发送者,即Activity或Service组件,将一个特点类型的广播发送给AMS。
这里是MyActivity,发送Action为"com.hxw.bot.broadcast.ACTION"的广播。
Step0:Service.sendBroadcast
广播使用intent对象描述,其Action即广播的类型,intent也可以携带必要的数据。
Step1:ContextWrapper.sendBroadcast
Step2:ContextImpl.sendBroadcast
@Override
public void sendBroadcast(Intent intent) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, false, false);
} catch (RemoteException e) {
}
}
Step3:ActivityManagerProxy.broadcastIntent
打包参数,向AMS发起进程间通信:
mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0);
阶段2:AMS找到接收者,通知其线程的消息队列处理广播
AMS收到一个广播后,找到与这个广播对应的接收者,将它们添加到广播调度队列。然后向创建AMS的线程的消息队列发送一个类型为BROADCAST_INTENT_MSG的消息。对于广播发送者来说,一个广播发送完成了。
AMS.unbroadcastIntent:同步和消息队列?
广播发送者向AMS发起的BROADCAST_INTENT_TRANSACTION操作是同步RPC,响应方法应该尽快返回。
对于Binder-IPC通信,AMS.unbroadcastIntent()的执行是在Binder线程中的,Binder线程一般也应该尽快执行完毕。
一个进程对应的Binder线程不止一个,所以AMS.unbroadcastIntent()是同步的,它将将广播的处理转为AMS被创建时的线程中消息队列对消息的处理,自身不执行广播的发送。
广播的发送是异步的,发送者不会等待AMS实际将广播发送给接收者操作完成。
阶段3:AMS消息队列处理BROADCAST_INTENT_MSG
当AMS所运行线程的消息队列中BROADCAST_INTENT_MSG消息被处理时,AMS从广播调度队列中找到需要接收此广播的广播接收者,并将对应的广播发送给它们所运行在的应用程序进程。
AMS.performReceiveLocked
static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky) throws RemoteException {
// Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null && app.thread != null) {
// If we have an app thread, do the call through that so it is
// correctly ordered with other one-way calls.
app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
data, extras, ordered, sticky);
} else {
receiver.performReceive(intent, resultCode, data, extras, ordered, sticky);
}
}
AMS发送给目标进程广播时,采用异步进程间通信方式。
发送给一个Binder对象的所有异步事务都保存在一个异步事务队列中,其中的事务每次只处理一个,就是队列头部的异步事务。所以,AMS发送给同一个应用程序进程的所有广播都可以被按照发送顺序来串行地接收和处理。
ApplicationThreadProxy.scheduleRegisteredReceiver():
mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
阶段4:接收者进程在主线程消息队列中响应广播
广播接收者所运行在的应用程序进程收到AMS发送的广播后,并不是直接将收到的广播分发给MyReceiver处理,而将广播封装为一个消息,发送到主线程的消息队列中。消息被处理时,应用程序进程在主线程中将消息所描述的广播发送给相应的广播接收者MyReceiver。
ApplicationThread.scheduleRegisteredReceiver
// This function exists to make sure all receiver dispatching is
// correctly ordered, since these are one-way calls and the binder driver
// applies transaction ordering per object for such calls.
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
boolean sticky) throws RemoteException {
receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky);
}
参数receiver就是注册过程提供的InnerReceiver(见LoadedApk.java)对象。
接收者所在线程(见ActivityThread.java)将intent所表示的广播封装为一个消息(android.os.Message),然后发送到主线程消息队列中。
Args.run
final class LoadedApk {
...
static final class ReceiverDispatcher {
final IIntentReceiver.Stub mIIntentReceiver; // 作为跨进程接口实例的Binder对象
final BroadcastReceiver mReceiver; // MyReceiver
final Context mContext; // MyActivity对象
final Handler mActivityThread; // 主线程的handler
...
final static class InnerReceiver extends IIntentReceiver.Stub {
final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
final LoadedApk.ReceiverDispatcher mStrongRef;
...
}
final class Args implements Runnable {
private Intent mCurIntent;
...
public void run() {
IActivityManager mgr = ActivityManagerNative.getDefault();
...
BroadcastReceiver receiver = mReceiver;
...
receiver.onReceive(mContext, intent);
...
mgr.finishReceiver(mIIntentReceiver,
mCurCode, mCurData, mCurMap, false);
}
}
}
}
这里,主线程执行Args.run()方法,得到关联的MyReceiver调用其onReceive()。
如果当前广播是有序广播,那么onReceive()执行完毕后调用mgr.finishReceiver()
通知AMS将广播传递给下一个接收者。
补充
- Binder线程池
- Binder异步通信
- sticky粘性广播
接收者可以接收到对应类型的它注册前的最后一个广播。
(本文使用Atom编写)
笔记:BroadcastReceiver的运行过程的更多相关文章
- 孙鑫MFC学习笔记3:MFC程序运行过程
1.MFC中WinMain函数的位置在APPMODUL.cpp APPMODUL.cpp中是_tWinMain,其实_tWinMain是一个宏#define _tWinMain WinMain 2.全 ...
- 【Hadoop】MapReduce笔记(一):MapReduce作业运行过程、任务执行
一.MR作业运行过程 JobClient的runJob()方法:新建JobClient实例,并调用其submitJob()方法.提交作业后,runJob()每秒轮询作业进度,如果发现上次上报后信息有改 ...
- openstack学习笔记一 虚拟机启动过程代码跟踪
openstack学习笔记一 虚拟机启动过程代码跟踪 本文主要通过对虚拟机创建过程的代码跟踪.观察虚拟机启动任务状态的变化,来透彻理解openstack各组件之间的作用过程. 当从horizon界面发 ...
- Torch-RNN运行过程中的坑 [2](Lua的string sub函数,读取中文失败,乱码?)
0.踩坑背景 仍然是torch-rnn/LanguageModel.lua文件中的一些问题,仍然是这个狗血的LM:encode_string函数: function LM:encode_string( ...
- 江太公:javascript count(a)(b)(c)(d)运行过程思考
昨天,我弟抛给我一个js的题,使用类似标题那样的调用方法计算a*b*c*d以致无穷的实现方法.思考了半天,终于理清了它的运行过程,记录于下: 函数体: <!DOCTYPE html> &l ...
- JAVA - JAVA编译运行过程
Java编译原理 *.java→*.class→机器码 java编译器 (编译) → 虚拟机(解释执行) → 解释器(翻译) → 机器码 1.Java编译过程与c/c++编译过程不同 Java编译程 ...
- HOWTO - Basic MSI安装包在安装运行过程中如何获取完整源路径
有朋友问到如何在一个Windows Installer安装包中获取安装包源路径,就是在安装包运行过程中动态获取*.msi所在完整路径. 这个问题分两类,如果我们的安装包只是一个*.msi安装文件,那么 ...
- DirectShow程序运行过程简析
这段时间一直在学习陆其明老师的<DirectShow开发指南>一书,书中对DirectShow的很多细节讲解清晰,但是却容易让人缺少对全局的把握.在学习过程中,整理了关于DirectSho ...
- Java Executor并发框架(二)剖析ThreadPoolExecutor运行过程
上一篇从整体上介绍了Executor接口,从上一篇我们知道了Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThrea ...
随机推荐
- 关于six.with_metaclass(ABCMeta, object)的理解
在学习Python过程中,看到了生成虚基类的方式, class PeopleBase(six.with_metaclass(ABCMeta, object)): @abstractmethod def ...
- [转]jquery.form.js的ajaxSubmit和ajaxForm使用
参考 http://www.cnblogs.com/popzhou/p/4338040.html 依赖的脚本文件 <script src="../Javascript/jquery-1 ...
- EL表达式与标签库
https://blog.csdn.net/panhaigang123/article/details/78428567
- eclipse中tomcat可以start启动,无法debug启动的解决
设置断点,进行程序调试,但是debug启动tomcat,却无法启动,并且会报超时异常. 原因可能是eclipse和tomcat启动时读取文件发生冲突 去掉所有的断点,然后重新debug启动,再设置断点 ...
- [leetcode]29. Divide Two Integers两整数相除
Given two integers dividend and divisor, divide two integers without using multiplication, divisio ...
- [leetcode]45. Jump Game II青蛙跳(跳到终点最小步数)
Given an array of non-negative integers, you are initially positioned at the first index of the arra ...
- eclipse-查看继承层次图/继承实现层次图
阅读代码时,如果想要看某个类继承了哪些类.实现了哪些接口.哪些类继承了这个类,恰巧这个类的继承实现结构又比较复杂,那么如果对开发工具不是很熟练,这个需求是比较难以实现的.eclipse中的type h ...
- Connection lost: The server closed the connection
想必很多初学者都会遇到这个问题 其实很简单.mysql有个机制,就是8小时无通信,myslq就会自动关闭数据; 解决方案(2选1): 或者: 1.定时去做一个查询,就是 select * from X ...
- 20172325 2018-2019-2 《Java程序设计》第八周学习总结
20172325 2018-2019-2 <Java程序设计>第八周学习总结 教材学习内容总结 一.堆 1.什么是堆? 具有两个附加属性的一个二叉树. 堆分为小顶堆和大顶堆. 最小堆:对每 ...
- Mad Libs游戏1
简单的输入输出 输入代码 name1=input('请输入姓名:') name2=input('请输入一个句子:') name3=input('请输入一个地点:') name4=input('请输入一 ...