介绍

BroadcastReceiver 即广播组件,是 Android 的四大组件之一。用于监听和接收广播消息,并做出响应。有以下一些应用:

  • 不同组件之间的通信(应用内或不同应用之间)。
  • 多线程之间通信。
  • 与系统在特定情况下(例如,电话呼入时、网络可用时)的通信。

原理

Android 中的广播机制使用了观察者设计模式:基于消息的发布、订阅事件模型。因此,广播的发送者和接收者解耦,使得系统方便集成,更容易扩展。

模型中有三个角色:

  • 消息订阅者(广播接收者)
  • 消息发布者(广播发送者)
  • 消息中心(ActivityManagerService

整个模型过程如下:

  1. 广播接收者通过 Binder 机制在 AMS 中注册订阅广播。
  2. 广播发送者通过 Binder 机制向 AMS 发送广播。
  3. AMS 根据广播发送者要求(IntentFilter、Permission),在已注册列表中寻找适合的接收者。
  4. AMS 将广播发送到合适的广播接收者相应的消息循环队列中。
  5. 广播接收者通过消息循环拿到广播,并回调 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_PICTUREACTION_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)

广播接收器按照一定顺序规则接收广播,先接收的广播接收器可以对广播进行修改或截断,再被其他的广播接收器接收,或终止广播。顺序规则如下:

  1. 按照 priority 属性值从大到小排序。
  2. priority 属性值相同时,动态注册的广播接收器优先于静态注册。
  3. 静态注册相同优先级的接收器,先扫描的优先级高。
  4. 动态注册相同优先级的接收器,先注册的优先级高。

注:优先级对无序广播同样有效,对于无序广播:动态注册优先级高于静态注册(无视优先级)。同一种注册方式 priority 属性大的优先级高。同一 priority 属性值下,静态注册先扫描的优先级高,动态注册先注册的优先级高。

发送有序广播与普通广播类似,但使用的是 sendOrderedBroadcast() 方法发送广播,在 onReceive() 中通过 setResultExtras() 给下一优先级的接收器传递数据,通过 getResultExtras() 取出上一优先级接收器传递来的数据,通过 abortBroadcast() 截断广播的发送。

通过 sendOrderedBroadcast() 方法可以指定最终广播接收器 ResultReceiver。如果比它优先级高的接收器不终止广播,则最终接受器会接收两次广播:第一次,按照标准的优先级接收,第二次,接收最终的广播。如果比它优先级高的接收器终止广播,那么它只是接收一次最终的广播。

粘性广播(Sticky Broadcast)

在Android 5.0(API 21)或更高版已失效。

本地广播(Local Broadcast)

由于 Android 中的广播可以跨应用直接通信,所以可能造成以下两个问题:

  • 其他应用发出与当前应用 IntentFilter 相匹配的广播,导致当前应用不断接收和处理一些无用甚至是恶意的广播。
  • 其他应用注册与当前应用一致的 IntentFilter 用于接收广播,导致可以截取当前应用广播的具体信息,存在安全性问题。

使用本地广播可以有效解决上面的问题,提高安全性和效率。使用本地广播的方式有两种:

将全局广播设置为本地广播
  1. 注册广播接收器时将 exported 属性设置为 false,只接收使当前应用发出的广播。
  2. 在广播发送和接收时,增设相应的自定义权限 permission,用于权限验证。
  3. 在 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 介绍的更多相关文章

  1. Android 四大组件之“ BroadcastReceiver ”

    前言 Android四大组件重要性已经不言而喻了,今天谈谈的是Android中的广播机制.在我们上学的时候,每个班级的教室里都会装有一个喇叭,这些喇叭都是接入到学校的广播室的,一旦有什么重要的通知,就 ...

  2. [置顶] Android四大组件之BroadcastReceiver

    Android四大组件之BroadcastReceiver Broadcast Receiver 广播接收器,是一种负责接收广播消息并对消息做出响应的组件,和Service一样并不提供与用户交互的UI ...

  3. Android四大组件之一Service介绍-android学习之旅(十二)

    基本概念: service是android四大组件之一,运行在后台执行耗时操作,并不提供用户界面.其他组件如acticity可以通过startService启动该组件,也可以通过bindService ...

  4. Android四大组件之一“广播”

    前言 Android四大组件重要性已经不言而喻了,今天谈谈的是Android中的广播机制.在我们上学的时候,每个班级的教室里都会装有一个喇叭,这些喇叭都是接入到学校的广播室的,一旦有什么重要的通知,就 ...

  5. android四大组件--ContentProvider具体解释

    一.相关ContentProvider概念解析: 1.ContentProvider简单介绍 在Android官方指出的Android的数据存储方式总共同拥有五种,各自是:Shared Prefere ...

  6. 【转】android四大组件--ContentProvider详解

    一.相关ContentProvider概念解析: 1.ContentProvider简介在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences. ...

  7. Android四大组件与进程启动的关系(转)

    一. 概述 Android系统将进程做得很友好的封装,对于上层app开发者来说进程几乎是透明的. 了解Android的朋友,一定知道Android四大组件,但对于进程可能会相对较陌生. 一个进程里面可 ...

  8. android四大组件之Broadcast

    广播的概念 现实中:我们常常使用电台通过发送广播发布消息,买个收音机,就能收听 Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件.Androi ...

  9. Android 四大组件之再论service

    service常见的有2种方式,本地service以及remote service. 这2种的生命周期,同activity的通信方式等,都不相同. 关于这2种service如何使用,这里不做介绍,只是 ...

  10. Android四大组件及activity的四大启动模式

    Android四大组件 1. 广播接收者的两种类型: (1)系统广播接收者,就是继承BroadcastReceiver这个类,然后还要在清单文件中注册,注册之后给他一个action.当系统发生了这个a ...

随机推荐

  1. 微服务:Eureka+Zuul+Ribbon+Feign+Hystrix构建微服务架构

    原文地址:http://blog.csdn.net/qq_18675693/article/details/53282031 本案例将打架一个微服务框架,参考来源官方参考文档 微服务:是什么?网上有一 ...

  2. 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 ...

  3. [Linux] 纯净ubuntu快速搭建宝塔面板

    宝塔官方建议是纯净的系统,我使用docker运行一个ubuntu容器,模拟一个纯净的系统,这样也不会影响到我的其他服务. docker run --name baota -id -p 8888:888 ...

  4. 10. Go语言—for循环

    一.for循环 for 初始化语句;条件判断;变量修改 for i := 0; i < 100; i++{ fmt.Printf('i=%d\n',i) } // C 的 while 一样 fo ...

  5. [C2P1] Andrew Ng - Machine Learning

    About this Course Machine learning is the science of getting computers to act without being explicit ...

  6. MySQL学习笔记8——多表查询

    多表查询 多表查询 *合并结果集 *连接查询 *子查询 合并结果集 *要求被合并的表中,列的类型和列数相同(实际上是查询的结果集列类型和列数相同即可) *UNION,去除重复行 *UNION ALL, ...

  7. Paper | Spatially Adaptive Computation Time for Residual Networks

    目录 摘要 故事 SACT机制 ACT机制 SACT机制 实验 发表在2017年CVPR. 摘要 在图像检测任务中,对于图像不同的区域,我们可以分配不同层数的网络予以处理. 本文就提出了一个基于Res ...

  8. VS2017 Thrift编译出的Release版本的库调用报错LNK2001

    在使用thrift的过程中, 当我使用完thrift debug版本编译出来的库调试完成后, 改成release版本的时候, 就出现了如下错误, 莫名其妙啊, 同一套代码, 那只能是编译库的时候设置和 ...

  9. 查看xml源码的方法

    查看xml源码的方法 要通过查看源码才能看到xml源码 因为 print_r输出的时候 默认页面打开是html编码的...... 所以解析不了xml

  10. fastadmin表单提交后却没有关闭弹窗

    点击操作按钮弹出窗口,操作完之后提交表单,无论操作成功还是失败,窗口都不关闭,操作之后出现一个笑脸,3秒后回到弹框刚打开的样子 而我们想要的是这个效果: 在jS那里给这个按钮绑定一个事件即可实现