27 个问题突破所有重难点,BroadcastReceiver 、ContentProvider 知多少?「建议收藏」

前言
- 距离上次更新过去一周多了,打破了之前两到三天一更的惯例,主要是要总结的内容太多了。
- 本篇文章将对
BroadcastReceiver开发中,可能用到的知识点,可能遇到的问题进行总结。 - 希望本文能帮助你揭开
Android开发过程中的难题。
最后,希望大家都能有所收获,欢迎食用!
文章目录

方便大家学习,我在 GitHub 上建立个 仓库
仓库内容与博客同步更新。由于我在
稀土掘金简书CSDN博客园等站点,都有新内容发布。所以大家可以直接关注该仓库,以免错过精彩内容!
一、BroadcastReceiver
BroadcastReceiver,顾名思义就是“广播接收者”的意思,它是Android四大基本组件之一。- 这种组件本质上是一种全局的监听器,用于监听系统全局的广播消息。
- 它可以接收来自系统和应用的的广播。
1.1 什么是 BroadcastReceiver

- 是四大组件之一, 主要用于接收
app发送的广播 - 内部通信实现机制:通过
android系统的Binder机制.
1.2 广播分为两种

1.2.1 无序广播

- 也叫标准广播,是一种完全异步执行的广播。
- 在广播发出之后,所有广播接收器几乎都会在同一时刻接收到这条广播消息,它们之间没有任何先后顺序,广播的效率较高。
- 优点: 完全异步, 逻辑上可被任何接受者收到广播,效率高
- 缺点: 接受者不能将处理结果交给下一个接受者, 且无法终止广播.
1.2.2 有序广播

- 是一种同步执行的广播。
- 在广播发出之后,同一时刻只有一个广播接收器能够收到这条广播消息,当其逻辑执行完后该广播接收器才会继续传递。
- 调用
SendOrderedBroadcast()方法来发送广播,同时也可调用abortBroadcast()方法拦截该广播。可通过<intent-filter>标签中设置android:property属性来设置优先级,未设置时按照注册的顺序接收广播。 - 有序广播接受器间可以互传数据。
- 当广播接收器收到广播后,当前广播也可以使用
setResultData方法将数据传给下一个接收器。 - 使用
getStringExtra函数获取广播的原始数据,通过getResultData方法取得上个广播接收器自己添加的数据,并可用abortBroadcast方法丢弃该广播,使该广播不再被别的接收器接收到。

- 总结
- 按被接收者的优先级循序传播
A > B > C, - 每个都有权终止广播, 下一个就得不到
- 每一个都可进行修改操作, 下一个就得到上一个修改后的结果.
1.2.3 最终广播者

Context.sendOrderedBroadcast ( intent , receiverPermission , resultReceiver , scheduler , initialCode , initialData , initialExtras )时我们可以指定resultReceiver为最终广播接收者.- 如果比他优先级高的接受者不终止广播, 那么他的
onReceive会执行两次 - 第一次是正常的接收
- 第二次是最终的接收
- 如果优先级高的那个终止广播, 那么他还是会收到一次最终的广播
1.2.4 常见的广播接收者运用场景

- 开机启动,
sd卡挂载, 低电量, 外拨电话, 锁屏等 - 比如根据产品经理要求, 设计播放音乐时, 锁屏是否决定暂停音乐.
1.3 BroadcastReceiver 的种类
1.3.1 广播作为 Android 组件间的通信方式,如下使用场景:
对前一部分 “ 请描述一下
BroadcastReceiver” 进行展开补充

APP内部的消息通信。不同
APP之间的消息通信。Android系统在特定情况下与 APP 之间的消息通信。广播使用了观察者模式,基于消息的发布 / 订阅事件模型。广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。
BroadcastReceiver 本质是一个全局监听器,用于监听系统全局的广播消息,方便实现系统中不同组件间的通信。
自定义广播接收器需要继承基类
BroadcastReceiver,并实现抽象方法onReceive ( context, intent )。默认情况下,广播接收器也是运行在主线程,因此onReceiver()中不能执行太耗时的操作( 不超过10s),否则将会产生ANR问题。onReceiver()方法中涉及与其他组件之间的交互时,可以使用发送Notification、启动Service等方式,最好不要启动Activity。
1.3.2 系统广播

Android系统内置了多个系统广播,只要涉及手机的基本操作,基本上都会发出相应的系统广播,如开机启动、网络状态改变、拍照、屏幕关闭与开启、电量不足等。在系统内部当特定时间发生时,系统广播由系统自动发出。常见系统广播
Intent中的Action为如下值:

- 短信提醒:
android.provider.Telephony.SMS_RECEIVED - 电量过低:
ACTION_BATIERY_LOW - 电量发生改变:
ACTION_BATTERY_CHANGED - 连接电源:
ACTION_POWER_CO
- 从
Android 7.0开始,系统不会再发送广播ACTION_NEW_PICTURE和ACTION_NEW_VIDEO,对于广播CONNECTIVITY_ACTION必须在代码中使用registerReceiver方法注册接收器,在AndroidManifest文件中声明接收器不起作用。 - 从
Android 8.0开始,对于大多数隐式广播,不能在AndroidManifest文件中声明接收器。
1.3.3 局部广播

- 局部广播的发送者和接受者都同属于一个
APP - 相比于全局广播具有以下优点:
- 其他的
APP不会受到局部广播,不用担心数据泄露的问题。 - 其他
APP不可能向当前的APP发送局部广播,不用担心有安全漏洞被其他APP利用。 - 局部广播比通过系统传递的全局广播的传递效率更高。
Android v4包中提供了LocalBroadcastManager类,用于统一处理 APP 局部广播,使用方式与全局广播几乎相同,只是调用注册 / 取消注册广播接收器和发送广播偶读方法时,需要通过LocalBroadcastManager类的getInstance()方法获取的实例调用。
1.4 BroadcastReceiver 注册方式

1.4.1 静态注册
在 AndroidManifest.xml 文件中配置。
<receiver android:name=".MyReceiver" android:exported="true">
<intent-filter>
<!-- 指定该 BroadcastReceiver 所响应的 Intent 的 Action -->
<action android:name="android.intent.action.INPUT_METHOD_CHANGED"
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
- 两个重要属性需要关注:

android: exported
其作用是设置此BroadcastReceiver能否接受其他APP发出的广播 ,当设为false时,只能接受同一应用的的组件或具有相同user ID的应用发送的消息。这个属性的默认值是由BroadcastReceiver中有无Intent-filter决定的,如果有Intent-filter,默认值为true,否则为false。android: permission
如果设置此属性,具有相应权限的广播发送方发送的广播才能被此BroadcastReceiver所接受;如果没有设置,这个值赋予整个应用所申请的权限。
1.4.2 动态注册
- 调用
Context的registerReceiver ( BroadcastReceiver receiver , IntentFilter filter )方法指定。
1.5 在 Mainfest 和代码如何注册和使用 BroadcastReceiver ? ( 一个 action 是重点 )

1.5.1 使用文件注册 ( 静态广播 )
- 只要
app还在运行,那么会一直收到广播消息

- 演示:
- 一个
app里: 自定义一个类继承BroadcastReceiver然后要求重写onReveiver方法
public class MyBroadCastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("MyBroadCastReceiver", "收到信息,内容是 : " + intent.getStringExtra("info") + "");
}
}
- 清单文件注册,并设置
Action, 就那么简单完成接收准备工作
<receiver android:name=".MyBroadCastReceiver">
<intent-filter>
<action android:name="myBroadcast.action.call"/>
</intent-filter>
</receiver>
1.5.2 代码注册 ( 动态广播 )
- 当注册的
Activity或者Service销毁了那么就会接收不到广播.

- 演示:
- 在和广播接受者相同的
app里的MainActivity添加一个注册按钮 , 用来注册广播接收者 - 设置意图过滤,添加
Action
//onCreate创建广播接收者对象
mReceiver = new MyBroadCastReceiver();
//注册按钮
public void click(View view) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("myBroadcast.action.call");
registerReceiver(mReceiver, intentFilter);
}
- 销毁的时候取消注册
@Override
protected void onDestroy() {
unregisterReceiver(mReceiver);
super.onDestroy();
}
1.5.3 在另一个 app , 定义一个按钮, 设置意图, 意图添加消息内容, 意图设置 action( ... ) 要匹配 , 然后发送广播即可.

public void click(View view) {
Intent intent = new Intent();
intent.putExtra("info", "消息内容");
intent.setAction("myBroadcast.action.call");
sendBroadcast(intent);
}
- 运行两个
app之后:
- 静态注册的方法: 另一
app直接发广播就收到了 - 动态注册的方法: 自己的
app先代码注册,然后另一个app直接发广播即可.-
1.6 BroadcastReceiver 的实现原理是什么?
Android中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型。

- 模型中主要有
3个角色:
- 消息订阅者( 广播接收者 )
- 消息发布者( 广播发布者 )
- 消息中心(
AMS,即Activity Manager Service)
1.6.1 原理:

广播接收者通过
Binder机制在AMS(Activity Manager Service) 注册;广播发送者通过
Binder机制向AMS发送广播;AMS根据广播发送者要求,在已注册列表中,寻找合适的BroadcastReceiver( 寻找依据:IntentFilter / Permission);AMS将广播发送到BroadcastReceiver相应的消息循环队列中;广播接收者通过消息循环拿到此广播,并回调
onReceive()方法。需要注意的是:广播的发送和接受是异步的,发送者不会关心有无接收者或者何时收到。
1.7 本地广播

本地广播机制使得发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接受来自本应用程序发出的广播,则安全性得到了提高。
本地广播主要是使用了一个
LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法。开发者只要实现自己的
BroadcastReceiver子类,并重写onReceive ( Context context, Intetn intent )方法即可。当其他组件通过
sendBroadcast()、sendStickyBroadcast()、sendOrderBroadcast()方法发送广播消息时,如果该BroadcastReceiver也对该消息“感兴趣”,BroadcastReceiver的onReceive ( Context context, Intetn intent )方法将会被触发。使用步骤:
- 调用 LocalBroadcastManager.getInstance() 获得实例
- 调用 registerReceiver() 方法注册广播
- 调用 sendBroadcast() 方法发送广播
- 调用 unregisterReceiver() 方法取消注册
1.7.1 注意事项:

- 本地广播无法通过静态注册方式来接受,相比起系统全局广播更加高效。
- 在广播中启动
Activity时,需要为Intent加入FLAG_ACTIVITY_NEW_TASK标记,否则会报错,因为需要一个栈来存放新打开的Activity。 - 广播中弹出
Alertdialog时,需要设置对话框的类型为TYPE_SYSTEM_ALERT,否则无法弹出。 - 不要在
onReceiver()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceiver()方法运行了较长时间而没有结束时,程序就会报错。
1.8 Sticky Broadcast 粘性广播

如果发送者发送了某个广播,而接收者在这个广播发送后才注册自己的 Receiver ,这时接收者便无法接收到刚才的广播
为此
Android引入了StickyBroadcast,在广播发送结束后会保存刚刚发送的广播(Intent),这样当接收者注册完Receiver后就可以继续使用刚才的广播。如果在接收者注册完成前发送了多条相同
Action的粘性广播,注册完成后只会收到一条该Action的广播,并且消息内容是最后一次广播内容。系统网络状态的改变发送的广播就是粘性广播。
- 粘性广播通过
Context的sendStickyBroadcast ( Intent )接口发送,需要添加权限 uses-permission android:name=”android.permission.BROADCAST_STICKY”- 也可以通过
Context的removeStickyBroadcast ( Intent intent )接口移除缓存的粘性广播
1.9 LocalBroadcastManager 详解
1.9.1 特点:

使用它发送的广播将只在自身APP内传播,因此你不必担心泄漏隐私数据;
其他
APP无法对你的APP发送该广播,因为你的APP根本就不可能接收到非自身应用发送的该广播,因此你不必担心有安全漏洞可以利用;比系统的全局广播更加高效。
1.9.2 源码分析 :

LocalBroadcastManager内部协作主要是靠这两个Map集合:MReceivers和MActions,当然还有一个 List 集合MPendingBroadcasts,这个主要就是存储待接收的广播对象。LocalBroadcastManager高效的原因主要是因为它内部是通过Handler实现的,它的sendBroadcast()方法含义并非和我们平时所用的一样,它的sendBroadcast()方法其实是通过handler发送一个Message实现的;既然它内部是通过
Handler来实现广播的发送的,那么相比于系统广播通过Binder实现那肯定是更高效了,同时使用Handler来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用;
1.9.3 BroadcastReceiver 安全问题

BroadcastReceiver设计的初衷是从全局考虑可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言BroadcastReceiver是存在安全性问题的 ( 恶意程序脚本不断的去发送你所接收的广播 ) 。为了解决这个问题LocalBroadcastManager就应运而生了。LocalBroadcastManager是Android Support包提供了一个工具,用于在同一个应用内的不同组件间发送Broadcast。LocalBroadcastManager也称为局部通知管理器,这种通知的好处是安全性高,效率也高,适合局部通信,可以用来代替Handler更新UI
1.9.4 广播的安全性
Android系统中的广播可以跨进程直接通信,会产生以下两个问题:
- 其他
APP可以接收到当前APP发送的广播,导致数据外泄。 - 其他
APP可以向当前APP放广播消息,导致APP被非法控制。

- 发送广播
- 发送广播时,增加相应的
permission,用于权限验证。 - 在
Android 4.0及以上系统中发送广播时,可以使用setPackage()方法设置接受广播的包名。 - 使用局部广播。
- 接受广播
- 注册广播接收器时,增加相应的
permission,用于权限验证。 - 注册广播接收器时,设置
android:exported的值为false。
- 使用局部广播
- 发送广播时,如果增加了
permission - 那接受广播的
APP必须申请相应权限,这样才能收到对应的广播,反之亦然。
1.9.5 使用 BroadcastReceiver 的好处

因广播数据在本应用范围内传播,你不用担心隐私数据泄露的问题。
不用担心别的应用伪造广播,造成安全隐患。
相比在系统内发送全局广播,它更高效。
1.10 如何让自己的广播只让指定的 app 接收?

- 在发送广播的
app端,自定义定义权限, 那么想要接收的另外app端必须声明权限才能收到.
- 权限, 保护层级是普通正常.
- 用户权限
<permission android:name="broad.ok.receiver" android:protectionLevel="normal"/>
<uses-permission android:name="broad.ok.receiver" />
- 发送广播的时候加上权限字符串
public void click(View view) {
Intent intent = new Intent();
intent.putExtra("info", "消息内容");
intent.setAction("myBroadcast.action.call");
sendBroadcast(intent, "broad.ok.receiver");
//sendOrderedBroadcast(intent,"broad.ok.receiver");
}
- 其他app接收者想好获取广播,必须声明在清单文件权限
<uses-permission android:name="broad.ok.receiver"/>
1.11 广播的优先级对无序广播生效吗?

- 优先级对无序也生效.
1.12 动态注册的广播优先级谁高?

- 谁先注册,谁就高
1.13 如何判断当前的 BrodcastReceiver 接收到的是有序还是无序的广播?

- 在
onReceiver方法里,直接调用判断方法得返回值
public void onReceive(Context context, Intent intent) {
Log.d("MyBroadCastReceiver", "收到信息,内容是 : " + intent.getStringExtra("info") + "");
boolean isOrderBroadcast = isOrderedBroadcast();
}
1.14 BroadcastReceiver 不能执行耗时操作

- 一方面
BroadcastReceiver一般处于主线程。- 耗时操作会导致
ANR
- 另一方面
BroadcastReceiver启动时间较短。- 如果一个进程里面只存在一个
BroadcastReceiver组件。并且在其中开启子线程执行耗时任务。 - 系统会认为该进程是优先级最低的空进程。很容易将其杀死。
总结
- 本文应该是全网最全面的
BroadcastReceiver知识总结了,如果有什么遗漏的地方,欢迎大家在评论区指出。 - 前前后后投入了大量时间来完成。希望大家通过本次阅读都能有所收获。
重点:关于Android的四大组件,到现在为止我才总结完Activity、Service、BroadcastRecevier、ContentProvider等,以及事件分发、滑动冲突、新能优化等重要模块,进行全面总结,欢迎大家关注 _yuanhao 的 博客园 ,方便及时接收更新
码字不易,你的点赞是我总结的最大动力!
由于我在「稀土掘金」「简书」「
CSDN」「博客园」等站点,都有新内容发布。所以大家可以直接关注我的GitHub仓库,以免错过精彩内容!一万多字长文,加上精美思维导图,记得点赞哦,欢迎关注 _yuanhao 的 博客园 ,我们下篇文章见!

27 个问题突破所有重难点,BroadcastReceiver 、ContentProvider 知多少?「建议收藏」的更多相关文章
- Activity 的 36 大难点,你会几个?「建议收藏」
前言 学 Android 有一段时间了,一直都只顾着学新的东西,最近发现很多平常用的少的东西竟让都忘了,趁着这两天,打算把有关 Activity 的内容以问题的形式梳理出来,也供大家查缺补漏. 本文中 ...
- 李洪强漫谈iOS开发[C语言-008]- C语言重难点
C语言学习的重难点 写程序的三个境界: 照抄的境界,翻译的境界,创新的境界 1 伪代码: 描述C语言的编程范式 范式: 规范的一种表示 对于C的范式学会的话,C, C++ Java 都会了 2 ...
- English--音标重难点
English|音标重难点 在拥有了,音标的元音与辅音的基础之后,需要对于这些音标进行加以区分,毕竟方言对于口型的影响非常的大. 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点 ...
- 这是一份非常适合收藏的Android进阶/面试重难点整理
写在前面 记得我大二时“不务正业”地自学Android并跟了老师做项目,到大三开始在目前的公司实习,至今毕业已有几年多,学习Android已经6.7年多了!但总感觉知识点很零散,并且不够深入,遇到瓶颈 ...
- 《十天学会 PHP》的重难点
记录一下我在学习<十天学会 PHP>(第六版)的过程中的遇到的重难点,该课程是学习制作一个简单的留言板. 准备工作 XAMPP(Apache + MySQL + PHP + PERL) 是 ...
- html和css的重难点知识
目录 html总难点总结: 1. 块级标签与内联标签的区别 1.1 块级标签: 1.2 内联标签: 2. 选择器 2.1 定义 2.2 选择器的分类 2.1 选择器的分类 3. css中margin, ...
- 老猿Python重难点知识博文汇总
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 除了相关教程外,老猿在学习过程中还写了大量的学习随笔,内容比较杂,文章内容也参差不齐,为了方便,老猿 ...
- Collection集合重难点梳理,增强for注意事项和三种遍历的应用场景,栈和队列特点,数组和链表特点,ArrayList源码解析, LinkedList-源码解析
重难点梳理 使用到的新单词: 1.collection[kəˈlekʃn] 聚集 2.empty[ˈempti] 空的 3.clear[klɪə(r)] 清除 4.iterator 迭代器 学习目标: ...
- 23 个重难点突破,带你吃透 Service 知识点「长达 1W+ 字」
前言 学 Android 有一段时间了,想必不少人也和我一样,平时经常东学西凑,感觉知识点有些凌乱难成体系.所以趁着这几天忙里偷闲,把学的东西归纳下,捋捋思路. 这篇文章主要针对 Service 相关 ...
随机推荐
- Spring Boot 整合视图层技术
这一节我们主要学习如何整合视图层技术: Jsp Freemarker Thymeleaf 在之前的案例中,我们都是通过 @RestController 来处理请求,所以返回的内容为json对象.那么如 ...
- 前端防止url输入地址直接访问页面
首先,解决这个问题要搞明白此url是从程序内部跳转还是直接在地址栏输入的,如果是程序内部跳转,那就好办啦.方法如下: 判断用户是否登录状态,是否携带token 使用router.beforeEach注 ...
- 使用 Chrome 对长网页(知乎、微信公众号文章)进行完整截图
当需要对一个较长的网页进行完整截图时,可以直接使用谷歌浏览器(Chrome)自带的截图功能完成,不需要依赖第三方截图软件. 1. 打开网页 以微信公众号的页面作为示例:https://mp.weixi ...
- ng的显示与隐藏
显示与隐藏有很多中方法,但是在ng中有自己的显示与隐藏的方法 ng-if 或者[hidden] 在此主要介绍的是[hidden] 在ng中需要摒弃dom操作的方法,使用[hidden] 使用方法: e ...
- 基于Docker和Golang搭建Web服务器
1 场景描述 基于centos7的docker镜像搭建golang开发环境 在docker容器内,使用golang实现一个Web服务器 启动docker容器,并在容器内启动Web服务器 我购买了一个最 ...
- JVM类加载过程与双亲委派模型
类加载过程 类加载过程为JVM将类描述数据从.class文件中加载到内存,并对数据进行解析和初始化,最终形成被JVM直接使用的Java类型.包含: 加载:获取该类的二进制字节流,将字节流代表的静态存储 ...
- Kafka 介绍
Apache Kafka是一个分布式流式平台. 流平台有三个关键的能力: 发布和订阅记录流,类似于消息队列或企业消息传递系统. 使用容错耐用的方式存储记录流. 记录产生时处理数据. Kafka主要是用 ...
- AutoCAD 2019 for mac 非常好用的CAD三维设计绘图软件
macOS下用什么cad软件?mac在哪下载cad软件? AutoCAD 2019 for mac 是一款非常好用的CAD三维设计绘图软件,可应用三维建模.CAD.渲染.动画.视觉特效和数字图像. A ...
- 【前端词典】4 个实用有趣的 JS 特性
前言 最近在学习的过程中发现了我之前未曾了解过的一些特性,发现有些很有趣并且在处理一些问题的时候可以给我一个新的思路. 这里我将这些特性介绍给大家. 4 个有趣的 JS 特性 利用 a 标签解析 UR ...
- HTML DIV充满整个屏幕
<!DOCTYPE html> <html> <head> <title>A Little Game!</title> <meta c ...