Android四大组件:BroadcastReceiver 介绍
介绍
BroadcastReceiver 即广播组件,是 Android 的四大组件之一。用于监听和接收广播消息,并做出响应。有以下一些应用:
- 不同组件之间的通信(应用内或不同应用之间)。
- 多线程之间通信。
- 与系统在特定情况下(例如,电话呼入时、网络可用时)的通信。
原理
Android 中的广播机制使用了观察者设计模式:基于消息的发布、订阅事件模型。因此,广播的发送者和接收者解耦,使得系统方便集成,更容易扩展。
模型中有三个角色:
- 消息订阅者(广播接收者)
- 消息发布者(广播发送者)
- 消息中心(
ActivityManagerService
)
整个模型过程如下:
- 广播接收者通过 Binder 机制在 AMS 中注册订阅广播。
- 广播发送者通过 Binder 机制向 AMS 发送广播。
- AMS 根据广播发送者要求(IntentFilter、Permission),在已注册列表中寻找适合的接收者。
- AMS 将广播发送到合适的广播接收者相应的消息循环队列中。
- 广播接收者通过消息循环拿到广播,并回调
onReceive()
方法。
注:广播发送者和接收者的执行是异步的,发送者不会关心有无接收者接收,也不确定接收者何时才能接收到。
使用
步骤1:自定义广播接收器
继承 BroadcastReceiver 基类,并复写抽象方法 onReceive()
。默认情况下,广播接收器运行在主线程,因此 onReceive()
方法不能执行耗时操作,否则阻塞主线程导致 ANR 问题。
一个 BroadcastReceiver 对象只有在被调用 onReceive()
时才有效,当从该方法返回后 BroadcastReceiver 对象就结束了生命周期。在 onReceive()
方法里,不建议使用线程来执行耗时操作,因为当得到其他异步操作所返回的结果时,BroadcastReceiver 可能已经结束生命周期了。如果确实需要的话,可以用调用 goAsync()
方法,然后再新开一个线程去执行,但是仍不建议执行超过 10 秒的任务。对于耗时的操作,最好使用 startService()
来完成。
// 继承 BroadcastReceiver 基类
public class TestBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "TestBroadcastReceiver";
// 复写 onReceive() 方法,接收到广播后,自动调用该方法
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Task asyncTask = new Task(pendingResult, intent);
asyncTask.execute();
}
private static class Task extends AsyncTask<String, Integer, String> {
private final PendingResult pendingResult;
private final Intent intent;
private Task(PendingResult pendingResult, Intent intent) {
this.pendingResult = pendingResult;
this.intent = intent;
}
@Override
protected String doInBackground(String... strings) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
return log;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish();
}
}
}
步骤2:注册广播接收器
广播接收器的注册方式分为两种:静态注册、动态注册。
静态注册
静态注册会时刻监听广播,在系统常驻,不受到任何组件生命周期的影响,但是也有耗电、占内存等缺点。
广播接收器的静态注册由 PMS 负责,在应用安装时,PMS 负责按照一定的目录顺序,扫描手机中所有安装的应用,并将应用清单文件中有关注册广播的信息解析存储起来。
所以,静态注册方法是在清单文件 AndroidManifest 里通过 <receiver>
元素标签声明:
<receiver android:name=".TestBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
注:由于系统是在应用安装时根据清单文件中的声明注册接收器,所以静态注册的广播接收器,即使应用不在运行,也可以接收到广播。
但是 Android 3.1(API 12)开始系统在 Intent 与广播相关的 Flag 中增加了两个参数,用来标识是否包含停止运行的包。系统不管什么广播类型,都默认增加值为
FLAG_EXCLUDE_STOPPED_PACKAGES
的 Flag,表示不包含停止运行的包。系统广播是系统直接发出,无法更改此 Flag 值,导致即使是静态注册的广播接收器,假如其所在的进程已退出,同样无法收到广播。
而自定义广播,可以通过修改此 Flag 为
FLAG_INCLUDE_STOPPED_PACKAGES
,使得静态注册的广播接收器,在应用停止运行时也可以接收到广播,并会启动应用进程。
动态注册
动态注册是通过 AMS 负责的,注册方式比较灵活,可以跟随组件的生命周期变化,可以在特定时间段内监听广播。使用方法是调用 registerReceiver()
方法注册广播接收器的监听,调用 unregisterReceiver()
方法注销:
TestBroadcastReceiver testBroadcastReceiver = null;
@Override
protected void onResume() {
super.onResume();
// 1. 实例化 BroadcastReceiver 子类
testBroadcastReceiver = new TestBroadcastReceiver();
// 2. 实例化 IntentFilter,设置接收广播的类型
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
// 3. 动态注册:调用 Context的registerReceiver() 方法
registerReceiver(testBroadcastReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
// 注销在 onResume() 中注册的广播
unregisterReceiver(testBroadcastReceiver);
}
注:因为动态广播注册后不注销会导致内存泄漏,所以最好在 Activity 生命周期中几个成对出现的方法里注册和注销。推荐在 Activity 中的
onResume()
中注册,onPause
中注销,因为这样可以在 Activity 不在前台显示时,停止监听,减少运行的开销。
注意:如果应用针对 Android 的最新版本,则需注意系统广播行为的多处修改:
Android 7.0(API 24)或更高版本,系统不再发送
ACTION_NEW_PICTURE
和ACTION_NEW_VIDEO
广播,并且只有动态注册的广播接收器能接收到CONNECTIVITY_ACTION
广播。Android 8.0(API 26)或更高版本,系统对静态注册的接收器施加了额外限制,无法为大多数隐式广播声明接收器,但可以使用动态注册接收器。
Android 9(API 28)或更高版本,
NETWORK_STATE_CHANGED_ACTION
广播不会收到有关用户位置或个人身份数据的信息。自 Wi-Fi 的系统广播不包含 SSID,BSSID,连接信息或扫描结果。
步骤3:广播发送者向 AMS 发送广播
广播发送者通过 sendBroadcast()
方法向 AMS 发送包装了广播的 Intent 对象。广播一般主要分为以下五类:
Normal Broadcast
普通广播System Broadcast
系统广播Ordered Broadcast
有序广播Sticky Broadcast
粘性广播Local Broadcast
本地广播
普通广播(Normal Broadcast)
开发者自定义 Intent 的全局广播。若发送广播需要相应的权限,则广播接收器也需要相应的权限。发送广播方式如下:
Intent intent = new Intent();
// 对应 BroadcastReceiver 中 IntentFilter 定义的 Action
intent.setAction(TEST_BROADCAST_ACTION);
sendBroadcast(intent);
系统广播(System Broadcast)
Android 系统中定义了很多内置的广播,涉及到手机的一些基本操作(例如,开机、网络状态变化、拍照等),都会发送相应的系统广播。
每个广播都有特定的 IntentFilter,使用系统广播时,只需要在注册广播接收器处声明相关的 IntentFilter 条件即可,并不需要手动发送广播,系统会在执行相关操作时自动进行系统广播。
有序广播(Ordered Broadcast)
广播接收器按照一定顺序规则接收广播,先接收的广播接收器可以对广播进行修改或截断,再被其他的广播接收器接收,或终止广播。顺序规则如下:
- 按照 priority 属性值从大到小排序。
- priority 属性值相同时,动态注册的广播接收器优先于静态注册。
- 静态注册相同优先级的接收器,先扫描的优先级高。
- 动态注册相同优先级的接收器,先注册的优先级高。
注:优先级对无序广播同样有效,对于无序广播:动态注册优先级高于静态注册(无视优先级)。同一种注册方式 priority 属性大的优先级高。同一 priority 属性值下,静态注册先扫描的优先级高,动态注册先注册的优先级高。
发送有序广播与普通广播类似,但使用的是 sendOrderedBroadcast()
方法发送广播,在 onReceive()
中通过 setResultExtras()
给下一优先级的接收器传递数据,通过 getResultExtras()
取出上一优先级接收器传递来的数据,通过 abortBroadcast()
截断广播的发送。
通过 sendOrderedBroadcast()
方法可以指定最终广播接收器 ResultReceiver。如果比它优先级高的接收器不终止广播,则最终接受器会接收两次广播:第一次,按照标准的优先级接收,第二次,接收最终的广播。如果比它优先级高的接收器终止广播,那么它只是接收一次最终的广播。
粘性广播(Sticky Broadcast)
在Android 5.0(API 21)或更高版已失效。
本地广播(Local Broadcast)
由于 Android 中的广播可以跨应用直接通信,所以可能造成以下两个问题:
- 其他应用发出与当前应用 IntentFilter 相匹配的广播,导致当前应用不断接收和处理一些无用甚至是恶意的广播。
- 其他应用注册与当前应用一致的 IntentFilter 用于接收广播,导致可以截取当前应用广播的具体信息,存在安全性问题。
使用本地广播可以有效解决上面的问题,提高安全性和效率。使用本地广播的方式有两种:
将全局广播设置为本地广播
- 注册广播接收器时将 exported 属性设置为 false,只接收使当前应用发出的广播。
- 在广播发送和接收时,增设相应的自定义权限 permission,用于权限验证。
- 在 Android 4.0 及更高版本,发送广播时通过 Intent 的
setPackage()
方法指定包名,此广播只会发送到指定包中匹配的广播接收器。
使用 LocalBroadcastManager 类
使用方式与全局广播类似,区别是要使用 LocalBroadcastManager 的单例来调用 registerReceiver()
和 unregisterReceiver
来注册和注销广播接收器,调用 sendBroadcast()
来发送广播。
// 注册应用内广播接收器
testBroadcastReceiver = new TestBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
LocalBroadcastManager.getInstance(this).registerReceiver(testBroadcastReceiver, intentFilter);
// 注销应用内广播接收器
LocalBroadcastManager.getInstance(this).unregisterReceiver(testBroadcastReceiver);
// 发送应用内广播
Intent intent = new Intent();
intent.setAction(TEST_BROADCAST_ACTION);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
其他
对于不同注册方式的广播接收器,在回调 onReceive(Context context, Intent intent)
中的 context 返回值是不一样的:
- 静态注册:context 返回的是 ReceiverRestrictedContext。
- 动态注册的全局广播:context 返回的是 Activity Context。
- 动态注册的本地广播:context 返回的是 Activity Context。
- 动态注册的本地广播(LocalBroadcastManager):context 返回的是 Application Context。
Android四大组件:BroadcastReceiver 介绍的更多相关文章
- Android 四大组件之“ BroadcastReceiver ”
前言 Android四大组件重要性已经不言而喻了,今天谈谈的是Android中的广播机制.在我们上学的时候,每个班级的教室里都会装有一个喇叭,这些喇叭都是接入到学校的广播室的,一旦有什么重要的通知,就 ...
- [置顶] Android四大组件之BroadcastReceiver
Android四大组件之BroadcastReceiver Broadcast Receiver 广播接收器,是一种负责接收广播消息并对消息做出响应的组件,和Service一样并不提供与用户交互的UI ...
- Android四大组件之一Service介绍-android学习之旅(十二)
基本概念: service是android四大组件之一,运行在后台执行耗时操作,并不提供用户界面.其他组件如acticity可以通过startService启动该组件,也可以通过bindService ...
- Android四大组件之一“广播”
前言 Android四大组件重要性已经不言而喻了,今天谈谈的是Android中的广播机制.在我们上学的时候,每个班级的教室里都会装有一个喇叭,这些喇叭都是接入到学校的广播室的,一旦有什么重要的通知,就 ...
- android四大组件--ContentProvider具体解释
一.相关ContentProvider概念解析: 1.ContentProvider简单介绍 在Android官方指出的Android的数据存储方式总共同拥有五种,各自是:Shared Prefere ...
- 【转】android四大组件--ContentProvider详解
一.相关ContentProvider概念解析: 1.ContentProvider简介在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences. ...
- Android四大组件与进程启动的关系(转)
一. 概述 Android系统将进程做得很友好的封装,对于上层app开发者来说进程几乎是透明的. 了解Android的朋友,一定知道Android四大组件,但对于进程可能会相对较陌生. 一个进程里面可 ...
- android四大组件之Broadcast
广播的概念 现实中:我们常常使用电台通过发送广播发布消息,买个收音机,就能收听 Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件.Androi ...
- Android 四大组件之再论service
service常见的有2种方式,本地service以及remote service. 这2种的生命周期,同activity的通信方式等,都不相同. 关于这2种service如何使用,这里不做介绍,只是 ...
- Android四大组件及activity的四大启动模式
Android四大组件 1. 广播接收者的两种类型: (1)系统广播接收者,就是继承BroadcastReceiver这个类,然后还要在清单文件中注册,注册之后给他一个action.当系统发生了这个a ...
随机推荐
- 微服务:Eureka+Zuul+Ribbon+Feign+Hystrix构建微服务架构
原文地址:http://blog.csdn.net/qq_18675693/article/details/53282031 本案例将打架一个微服务框架,参考来源官方参考文档 微服务:是什么?网上有一 ...
- ThinkPHP 3.2,配置 'URL_MODEL'=>2。 APP_DEBUG设为false,U函数生成的URL的index.php不能去掉,只有将APP_DEBUG改成true,才能去掉index.php,求解~~
ThinkPHP 3.2,配置 'URL_MODEL'=>2.APP_DEBUG设为false,U函数生成的URL的index.php不能去掉,只有将APP_DEBUG改成true,才能去掉in ...
- [Linux] 纯净ubuntu快速搭建宝塔面板
宝塔官方建议是纯净的系统,我使用docker运行一个ubuntu容器,模拟一个纯净的系统,这样也不会影响到我的其他服务. docker run --name baota -id -p 8888:888 ...
- 10. Go语言—for循环
一.for循环 for 初始化语句;条件判断;变量修改 for i := 0; i < 100; i++{ fmt.Printf('i=%d\n',i) } // C 的 while 一样 fo ...
- [C2P1] Andrew Ng - Machine Learning
About this Course Machine learning is the science of getting computers to act without being explicit ...
- MySQL学习笔记8——多表查询
多表查询 多表查询 *合并结果集 *连接查询 *子查询 合并结果集 *要求被合并的表中,列的类型和列数相同(实际上是查询的结果集列类型和列数相同即可) *UNION,去除重复行 *UNION ALL, ...
- Paper | Spatially Adaptive Computation Time for Residual Networks
目录 摘要 故事 SACT机制 ACT机制 SACT机制 实验 发表在2017年CVPR. 摘要 在图像检测任务中,对于图像不同的区域,我们可以分配不同层数的网络予以处理. 本文就提出了一个基于Res ...
- VS2017 Thrift编译出的Release版本的库调用报错LNK2001
在使用thrift的过程中, 当我使用完thrift debug版本编译出来的库调试完成后, 改成release版本的时候, 就出现了如下错误, 莫名其妙啊, 同一套代码, 那只能是编译库的时候设置和 ...
- 查看xml源码的方法
查看xml源码的方法 要通过查看源码才能看到xml源码 因为 print_r输出的时候 默认页面打开是html编码的...... 所以解析不了xml
- fastadmin表单提交后却没有关闭弹窗
点击操作按钮弹出窗口,操作完之后提交表单,无论操作成功还是失败,窗口都不关闭,操作之后出现一个笑脸,3秒后回到弹框刚打开的样子 而我们想要的是这个效果: 在jS那里给这个按钮绑定一个事件即可实现