LocalBroadcastManager—创建更高效、更安全的广播
前言
在写Android应用时候,有时候或多或少的需要运用广播来解决某些需求,我们知道广播有一个特性,就是使用sendBroadcast(intent);
发送广播时,手机内所有注册了BroadcastReceiver
的应用都可以在接收到这个广播的,并在BroadcastReceiver
的onReceive()
方法进行匹配,而应用是否使用这个广播则是取决与我们定义的Action与广播接收者的是否匹配,也就是说平常我们使用的广播是全局的广播,谁都有权收到。所以这就有可能产生安全漏洞和隐私数据泄密:
1、假如别人反编译你的apk后知道了你的Action,那么第三方应用就可以发送与该Action匹配的广播,而你的应用也可以接收到,所以这就有可能被第三方利用这个来搞一些事
2、同样假如别人知道了你应用内的Action,当你使用广播来传输一些数据的时候,而其它应用也能接受到这个广播,通过Action匹配,就有可能获取到你的私密数据
通过上面分析,就说明了全局广播有两个地方需要注意,一是你的应用可以接收任何应用发出的广播,二是你的应用发出的广播可以被任何应用收到。
而通常来说我们使用广播大多数只是让它在自己的应用内传播,所以全局广播并不适合应用在此场景,而对这个问题,Google出了一个LocalBroadcastManager用来创建更高效、更安全的本地广播。
LocalBroadcastManager介绍
LocalBroadcastManager从名字上看就知道这个发送的广播只在本应用内传播,官方是这么介绍LocalBroadcastManager的:
- 使用它发送的广播将只在自身App内传播,因此你不必担心泄漏隐私数据
- 其它App无法对你的App发送该广播,因为你的App根本就不可能接收到非自身应用发送的该广播,因此你不必担心有安全漏洞可以利用
- 比系统的全局广播更加高效
LocalBroadcastManager使用
使用LocalBroadcastManager和平常使用系统的全局广播差不多,都得先注册然后再发送广播,最后取消注册。
注册广播
LocalBroadcastManager内部实现是一个单例模式(它的内部原理稍后讲),现在只先看看怎么使用LocalBroadcastManager,首先看看LocalBroadcastManager这个类拥有哪些方法:
可以看到只有我圈起来的方法才对外开放的,无非就是getInstance,注册,发送,取消注册这四个方法
先来看看注册这个方法:
LocalBroadcastManager.getInstance(Context context).registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
我们需要传入的有两个很重要的参数,一是广播接收器BroadcastReceiver,二是IntentFilter对象,这个IntentFilter是我们为这个广播接收器指定的
发送广播
LocalBroadcastManager.getInstance(Context context).sendBroadcast(Intent intent);
发送广播只需要我们传入一个Intent即可
取消注册
LocalBroadcastManager.getInstance(this).unregisterReceiver(BroadcastReceiver receiver);
取消注册只需要传入我们需要取消的广播接收器
代码实现
下面就看看使用LocalBroadcastManager的示例代码:
public class MainActivity extends AppCompatActivity {
private static final String LOCAL_ACTION = "com.sunzxy.demo.BROADCAST_ACTION";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册
LocalBroadcastManager.getInstance(this).registerReceiver(new MyMyBroadcastReceiver(),new IntentFilter(LOCAL_ACTION));
}
public void click(View view){
//发送广播
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(LOCAL_ACTION));
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消注册
LocalBroadcastManager.getInstance(this).unregisterReceiver(new MyMyBroadcastReceiver());
}
final class MyMyBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if(intent!=null && LOCAL_ACTION.equals(intent.getAction())){
Log.v("zxy",intent.getStringExtra("name"));
}
}
}
}
LocalBroadcastManager内部实现原理
同样,既然官方说了它更高效、发送的广播只会在本应用内传播从而保证了安全性,那么它内部是怎么实现的呢?
还是从LocalBroadcastManager的结构看起
我们从中可以看到比较重要的部分:
1、它内部有两个内部类,分别为ReceiverRecord和BroadcastRecord
2、它含有三个集合来管理
3、它内部有一个Handler对象
好了,知道了大概结构,那么接下来就是一一取出源码来分析了。
首先先看看LocalBroadcastManager的构造函数:
private static LocalBroadcastManager mInstance;
private final Handler mHandler;
public static LocalBroadcastManager getInstance(Context context) {
synchronized (mLock) {
if (mInstance == null) {
mInstance = new LocalBroadcastManager(context.getApplicationContext());
}
return mInstance;
}
}
private LocalBroadcastManager(Context context) {
mAppContext = context;
mHandler = new Handler(context.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_EXEC_PENDING_BROADCASTS:
executePendingBroadcasts();
break;
default:
super.handleMessage(msg);
}
}
};
}
可以看到它内部是由单例实现的而私有化构造函数,而构造函数里创建了一个Handler对象,Handler对象传入的Looper是MainLooper对象,意为这个Handler的工作线程是主线程,而在看executePendingBroadcasts()这个方法之前,我们先看看它内部的三个集合的作用:
private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
= new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
private final HashMap<String, ArrayList<ReceiverRecord>> mActions
= new HashMap<String, ArrayList<ReceiverRecord>>();
private final ArrayList<BroadcastRecord> mPendingBroadcasts
= new ArrayList<BroadcastRecord>();
这又涉及到两个内部类BroadcastRecord和ReceiverRecord,从名字上看就知道它们分别是广播记录实体类和接收器记录实体类:
private static class ReceiverRecord {
final IntentFilter filter;
final BroadcastReceiver receiver;
boolean broadcasting;
ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
filter = _filter;
receiver = _receiver;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(128);
builder.append("Receiver{");
builder.append(receiver);
builder.append(" filter=");
builder.append(filter);
builder.append("}");
return builder.toString();
}
}
private static class BroadcastRecord {
final Intent intent;
final ArrayList<ReceiverRecord> receivers;
BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
intent = _intent;
receivers = _receivers;
}
}
从这两个实体类的源码上看,ReceiverRecord是记录广播接收器(BroadcastReceiver)和它对应的过滤规则(IntentFilter),BroadcastRecord则是记录与发送的Intent匹配的ReceiverRecord的集合,因为一个Intent在它们对应的Action规则是一样的情况下它可以同时被多个广播接收器(BroadcastReceiver)接收。
回到那三个集合:
1、mReceivers:是一个HashMap类型的集合,BroadcastReceiver
作为key,而它的value则是一个ArrayList<IntentFilter>
集合,为什么是一个集合呢?因为一个广播接收器可以接收不同的Action的广播,这样看来它的功能很明显,就是维护着一张映射表,即维护着广播接收器(BroadcastReceiver)所对应的所有的过滤规则(IntentFilter)映射表,如下图:
2、mActions:也是一个HashMap类型的集合,它的key是一个Action,value则是一个ArrayList<ReceiverRecord>
对象,同样它也维护着一张映射表,即维护着Action所对应的所有ReceiverRecord对象映射表,其实说白了,就是可以接收该Action的所有广播接收器的映射,这个图恰好和上面的相反:
3、mPendingBroadcasts:这是一个List集合,存储的是BroadcastRecord
对象,而BroadcastRecord
对象又是存储着一个Intent和ReceiverRecord
集合,所以mPendingBroadcasts
这个集合的作用就是用来存储与发送的广播的Action匹配的ReceiverRecord
集合,而在执行处理广播的时候将会遍历这个集合对这里面的广播接收器一一进行广播的接收,说白了就是一个存储广播接收器的存储器(而这个广播接收器的IntentFilter必须和发送广播时的Intent匹配)。
好了,了解了上面的东西,现在我们再来看看注册广播方法的源码:
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
synchronized (mReceivers) {
//首先就是创建一个ReceiverRecord对象,传入的是我们的广播接收器和这个广播接收器的filter
ReceiverRecord entry = new ReceiverRecord(filter, receiver);
//得到这个广播接收器的IntentFilter过滤规则集合
ArrayList<IntentFilter> filters = mReceivers.get(receiver);
if (filters == null) {
//如果不存在的话则创建一个
filters = new ArrayList<IntentFilter>(1);
mReceivers.put(receiver, filters);
}
//把这个filter add进广播接收器对应的规则集合中去
filters.add(filter);
for (int i=0; i<filter.countActions(); i++) {
//然后遍历这个filter得到它的Action
String action = filter.getAction(i);
ArrayList<ReceiverRecord> entries = mActions.get(action);
if (entries == null) {
entries = new ArrayList<ReceiverRecord>(1);
mActions.put(action, entries);
}
//通过遍历得到Action后,一一分别对每个Action建立Action与ArrayList<ReceiverRecord>对应的映射表
entries.add(entry);
}
}
}
总结来说,registerReceiver()方法就做了两件事:
1、为传入的广播接收器添加指定的IntentFilter过滤规则
2、把IntentFilter里面的所有Action分别建立对ArrayList<ReceiverRecord>
的映射,也就是为相应的Action添加广播接收器,表示这个广播接收器可以接收此Action的广播
再看unregisterReceiver()方法:
public void unregisterReceiver(BroadcastReceiver receiver) {
synchronized (mReceivers) {
//在mReceivers表中移除key为receiver的对象
ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
if (filters == null) {
return;
}
//取出这个广播接收器的filter中含有的所有action,然后在mActions表中,分别得到这个action对应的广播接收器集合,再判断是否含有我们需要移除的receiver,如果有则移除
for (int i=0; i<filters.size(); i++) {
IntentFilter filter = filters.get(i);
for (int j=0; j<filter.countActions(); j++) {
String action = filter.getAction(j);
ArrayList<ReceiverRecord> receivers = mActions.get(action);
if (receivers != null) {
for (int k=0; k<receivers.size(); k++) {
if (receivers.get(k).receiver == receiver) {
receivers.remove(k);
k--;
}
}
if (receivers.size() <= 0) {
mActions.remove(action);
}
}
}
}
}
}
所以,unregisterReceiver()这个方法也做了两件事:
1、移除mReceivers表中广播接收器
2、移除mActions表中的广播接收器
再看看sendBroadcast()方法:
public boolean sendBroadcast(Intent intent) {
synchronized (mReceivers) {
final String action = intent.getAction();
final String type = intent.resolveTypeIfNeeded(
mAppContext.getContentResolver());
final Uri data = intent.getData();
final String scheme = intent.getScheme();
final Set<String> categories = intent.getCategories();
final boolean debug = DEBUG ||
((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
if (debug) Log.v(
TAG, "Resolving type " + type + " scheme " + scheme
+ " of intent " + intent);
//通过得到我们Intent的Action来获得该Action对应的所有的广播接收器的集合
ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
if (entries != null) {
if (debug) Log.v(TAG, "Action list: " + entries);
ArrayList<ReceiverRecord> receivers = null;
//然后遍历该集合,进行匹配
for (int i=0; i<entries.size(); i++) {
ReceiverRecord receiver = entries.get(i);
if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);
if (receiver.broadcasting) {
if (debug) {
Log.v(TAG, " Filter's target already added");
}
continue;
}
//当action,type,scheme,data,categories都完全相同时,这时匹配成功
int match = receiver.filter.match(action, type, scheme, data,
categories, "LocalBroadcastManager");
if (match >= 0) {
//匹配成功
if (debug) Log.v(TAG, " Filter matched! match=0x" +
Integer.toHexString(match));
if (receivers == null) {
receivers = new ArrayList<ReceiverRecord>();
}
//然后把匹配成功的ReceiverRecord对象添加到一个集合中去
receivers.add(receiver);
receiver.broadcasting = true;
} else {
if (debug) {
String reason;
switch (match) {
case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
default: reason = "unknown reason"; break;
}
Log.v(TAG, " Filter did not match: " + reason);
}
}
}
if (receivers != null) {
for (int i=0; i<receivers.size(); i++) {
receivers.get(i).broadcasting = false;
}
//最后再把存储匹配成功的ReceiverRecord对象集合添加到mPendingBroadcasts中去,而最终我们的广播接收是通过遍历mPendingBroadcasts这个集合来一一对这个集合里面的广播接收器进行广播的接收
mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
//这个非常重要,因为一开始我们就知道了在构造函数中会创建一个handler,而这个方法的名为sendBroadcast()其实不是真正的发送一个广播,而是通过handler来发送一个Message,然后在handlerMessage()回调方法中进行消息的处理,所以这也证实了这是一个本地广播,其它应用根本无法获取到,因为LocalBroadcastManager内部是通过Handler实现广播的发送的
mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
}
return true;
}
}
}
return false;
}
这个sendBroadcast()方法看起来比较长,其实一半的代码都是在做匹配,这段代码也做了三件事:
1、把我们发送的Intent的规则和mActions中对应action的
ArrayList<ReceiverRecord>
集合里面的广播接收器的规则进行匹配,匹配成功则加入一个匹配成功的集合中
2、把匹配成功的集合加入到mPendingBroadcasts集合中
3、最重要的一点,它其实是通过handler发送一个Message来实现的
最后再来看看一开始提到的executePendingBroadcasts()方法,这个方法是在Handler中处理消息用的,我们在调用LocalBroadcastManager的sendBroadcast()方法时,它实际上是通过handler发送一个Message,然后在executePendingBroadcasts()方法中进行广播的接收:
private void executePendingBroadcasts() {
while (true) {
BroadcastRecord[] brs = null;
synchronized (mReceivers) {
final int N = mPendingBroadcasts.size();
if (N <= 0) {
return;
}
brs = new BroadcastRecord[N];
//把mPendingBroadcasts集合转为数组
mPendingBroadcasts.toArray(brs);
//然后清空mPendingBroadcasts集合
mPendingBroadcasts.clear();
}
for (int i=0; i<brs.length; i++) {
BroadcastRecord br = brs[i];
for (int j=0; j<br.receivers.size(); j++) {
//然后通过循环遍历,得到mPendingBroadcasts集合中可以接收该广播的广播接收器进行广播的接收,通过调用onReceive()方法进行接收
br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
}
}
}
}
可以看到这个方法开了一个while循环进行轮询mPendingBroadcasts集合来进行广播的接收,处理完一遍后则会清空该集合,而下一次再有广播发送则重复这些动作。
LocalBroadcastManager总结
好了,LocalBroadcastManager的原理就分析完了,从中我们可以知道这么几点:
- LocalBroadcastManager高效的原因主要是因为它内部是通过Handler实现的,它的sendBroadcast()方法含义并非和我们平时所用的一样,它的sendBroadcast()方法其实是通过handler发送一个Message实现的(当然这个方法还有规则匹配等作用)
- 既然是它内部是通过Handler来实现广播的发送的,那么相比与系统广播通过Binder实现那肯定是更高效了,同时使用Handler来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用
- LocalBroadcastManager内部协作主要是靠这两个Map集合:mReceivers和mActions,当然还有一个List集合mPendingBroadcasts,这个主要就是存储待接收的广播对象
- 假如我们需要让一个广播接收器能接收多个不同Action的广播,我们可以在注册时候通过IntentFilter为该广播添加多条Action规则,这样只要符合其中一条该广播接收器就可以接收到了,如下:
//注册
IntentFilter filter = new IntentFilter();
filter.addAction("com.sunzxy.demo.BROADCAST_ACTION_1");
filter.addAction("com.sunzxy.demo.BROADCAST_ACTION_2");
filter.addAction("com.sunzxy.demo.BROADCAST_ACTION_3");
LocalBroadcastManager.getInstance(this).registerReceiver(new MyMyBroadcastReceiver(),filter);
LocalBroadcastManager—创建更高效、更安全的广播的更多相关文章
- Adaptive Execution如何让Spark SQL更高效更好用
1 背 景 Spark SQL / Catalyst 和 CBO 的优化,从查询本身与目标数据的特点的角度尽可能保证了最终生成的执行计划的高效性.但是 执行计划一旦生成,便不可更改,即使执行过程中发 ...
- Retrofit 2.0基于OKHttp更高效更快的网络框架 以及自定义转换器
时间关系,本文就 Retrofit 2.0的简单使用 做讲解 至于原理以后有空再去分析 项目全面.简单.易懂 地址: 关于Retrofit 2.0的简单使用如下: https://gitee.c ...
- 如何更高效地定制你的bootstrap
bootstrap已经作为前端开发必不可少的框架之一,应用bootstrap使得我们对布局.样式的设定变得非常简单.但bootstrap提供的默认样式往往不能满足我们的需求,从而定制化bootstra ...
- 如何使代码审查更高效【摘自InfoQ】
代码审查者在审查代码时有非常多的东西需要关注.一个团队需要明确对于自己的项目哪些点是重要的,并不断在审查中就这些点进行检查. 人工审查代码是十分昂贵的,因此尽可能地使用自动化方式进行审查,如:代码 ...
- AS-->如何更高效的使用 Gradle, 快速build apk
版权声明:欢迎转载,转载请注明出处;http://blog.csdn.net/angcyo 看本文之前,推荐先看我之前写的一篇文章: 传送门 日前Android Stuido 已经更新到 2.0.0 ...
- 25个让Java程序员更高效的Eclipse插件
Eclipse提供了一个可扩展插件的开发系统.这就使得Eclipse在运行系统之上可以实现各种功能.这些插件也不同于其他的应用(插件的功能是最难用代码实现的).拥有合适的Eclipse插件是非常重要的 ...
- 这些小工具让你的Android 开发更高效
在做Android 开发过程中,会遇到一些小的问题.尽管自己动手也能解决.可是有了一些小工具,解决这些问题就得心应手了,今天就为大家推荐一下Android 开发遇到的小工具,来让你的开发更高效. Vy ...
- 更强、更稳、更高效:解读 etcd 技术升级的三驾马车
点击下载<不一样的 双11 技术:阿里巴巴经济体云原生实践> 本文节选自<不一样的 双11 技术:阿里巴巴经济体云原生实践>一书,点击上方图片即可下载! 作者 | 陈星宇(宇慕 ...
- Pull Request 工作流——更高效的管理代码
目录 Pull Request 工作流--更高效的管理代码 1.问题 2.解决方案 3.Git分支流管理代码具体实施 3.1本地分支操作管理 3.1.1查看分支 3.1.2创建分支 3.1.3切换分支 ...
随机推荐
- 在Linux系统上获取命令帮助信息和划分man文档
使用历史命令history 打完以后前面会有顺序号的比如1 cd2 ls3 pwd如果需要重新执行cd命令则可以执行 !3 命令 命令补全功能 比如你要执行history命令 可以打上histo+键 ...
- 如何去掉修改Joomla、joomlart及其模版版权、标志、图标的方法
Joomla是遵循GNU通用公共授权(GPL)的自由软件,我们虽然不推荐将Joomla的所有版权删除,但有些必要的信息还是需要修改的,下面以JoomlArt.com 的JA_teline_iii_v2 ...
- Node.js TLS/SSL
Stability: 3 - Stable 可以使用 require('tls') 来访问这个模块. tls 模块 使用 OpenSSL 来提供传输层(Transport Layer)安全性和(或)安 ...
- Android的Intent机制详解
Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作.尽管 Intent 可以通过多种方式促进组件之间的通信,但其 基本用例主要包括以下三个: 启动 Activity: Activit ...
- Android图表库MPAndroidChart(八)——饼状图的扩展:折线饼状图
Android图表库MPAndroidChart(八)--饼状图的扩展:折线饼状图 我们接着上文,饼状图的扩展,增加折现的说明,来看下我们要实现的效果 因为之前对MPAndroidChart的熟悉,所 ...
- [error]error while loading shared libraries: libpcre.so.1 解决
nginx 安装好之后,启动的时候报错 [root@localhost nginx-1.6.2]# /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin ...
- ActiveMQ入门示例
1.ActiveMQ下载地址 http://activemq.apache.org/download.html 2.ActiveMQ安装,下载解压之后如下目录
- springMVC源码分析--动态样式ThemeResolver(二)
在上一篇博客springMVC源码分析--动态样式ThemeResolver(一)中我们介绍了多样式ThemeResolver的使用方法,接下来我们对源码进行简单的分析一下. ThemeResolve ...
- JAVA面向对象-----构造方法
我们人出生的时候,有些人一出生之后再起名字的,但是有些人一旦出生就已经起好名字的.那么我们在java里面怎么在对象一旦创建就赋值呢? 构造方法作用 构造方法作用:对对象进行初始化. 构造函数与普通的函 ...
- UNIX网络编程——原始套接字的魔力【续】
如何从链路层直接发送数据帧 上一篇里面提到的是从链路层"收发"数据,该篇是从链路层发送数据帧. 上一节我们主要研究了如何从链路层直接接收数据帧,可以通过bind函数来将原始套接字绑 ...