笔记: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 ...
随机推荐
- mysql实现IP与整形互转
- CentOS 7 无法yum安装解决方法
1)下载repo文件 wget http://mirrors.aliyun.com/repo/Centos-7.repo 2)备份并替换系统的repo文件 .repo /etc/yum.repos.d ...
- 数据预处理:独热编码(One-Hot Encoding)和 LabelEncoder标签编码
一.问题由来 在很多机器学习任务中,特征并不总是连续值,而有可能是分类值. 离散特征的编码分为两种情况: 1.离散特征的取值之间没有大小的意义,比如color:[red,blue],那么就使用one- ...
- maven pom.xml 项目报错
Failed to read artifact descriptor for org.springframework.boot:spring-boot-starter-web:jar:2.1.0.RE ...
- unity编程心得
1. 不要通过public变量 从工程面板 直接 拖 GameObjct 的引用, 当这样的public变量很多 ,子物体很多,又没有做成预制体,,别人重新移植这段功能会很麻烦,,应该用GameOb ...
- 在网站中使用UEditor富文本编辑器
UEditor是由百度WEB前端研发部开发的所见即所得的开源富文本编辑器,具有轻量.可定制.用户体验优秀等特点. 官网链接 进入到下载页面,选择相应的版本下载 这里我们使用ASP.NET开发,所以选择 ...
- Mysql添加新用户遇到的一些小问题
登陆命令:mysql -u root -p 添加本地用户:create user 'sheet'@'localhost' identified by '123456' ; 添加允许外网IP访问的用户 ...
- Linux 禁止普通用户su到root
Linux账户权限管理上为了防止普通用户通过su切换到root用户,需要修改/etc/pam.d/su和/etc/login.defs两个配置文件. Step1:修改 /etc/pam.d/su文件 ...
- fabric 在阿里云Ubuntu部署 注意
部署时候报 段错误: signal SIGSEGV: segmentation violation code=0x1 addr=0x63 pc=0x7fcd47490259] 解决方案: 更新Hype ...
- tiny4412 --uboot移植(2) 点灯
开发环境:win10 64位 + VMware12 + Ubuntu14.04 32位 工具链:linaro提供的gcc-linaro-6.1.1-2016.08-x86_64_arm-linux-g ...