前言
我们在Android应尽量避免使用隐式Intent广播传递信息,为什么这么说?原因有下面几点:
- 意外接收:如果同时维护几个项目,不同项目中难免会存在代码复用的情况,这时若安装了两个注册过同样 Action 广播的APP,一个APP通过
Context.sendBroadcast()
发送的隐式广播也会被另一个APP接收到,并进行相应的操作,可能会产生意想不到的风险。 - 敏感信息外泄:发送的隐式广播,可能会被恶意应用注册监听该广播的 receiver 获取到Intent中传递的敏感信息,并进行其他危险的操作。
- Intent拦截:如果发送的广播为使用
Context.sendOrderedBroadcast()
方法发送的有序广播,优先级较高的恶意 receiver 若直接丢弃该广播,会导致服务无法正常使用,或者广播结果被填充恶意数据。
基于以上的几点,会发现使用隐式Intent广播风险很高,那么怎么解决这个问题呢?首先,我们需要明确广播是否仅限于应用内使用。若需要在应用间传递广播,应尽量避免传递敏感信息;否则,可以使用LocalBroadcastManager.sendBroadcast()
实现,这样就避免了意外接收广播,敏感信息外泄和Intent拦截的风险。
LocalBroadcastManager源码分析
LocalBroadcastManager的源码并不多,总共也就不到300行,我们来分析下是怎么它的实现。
单例模式
首先,从下面这几行代码会发现LocalBroadcastManager使用了延迟加载的单例模式保证类对象的唯一性。
1 |
|
内部类
再看LocalBroadcastManager的两个静态内部类。
1 | private static final class ReceiverRecord { |
1 | private static final class BroadcastRecord { |
ReceiverRecord类记录了Intent过滤器和BroadcastReceiver对象,BroadcastRecord记录了发送的Intent和一个ReceiverRecord的集合,这个集合是用来做什么的呢?目前猜测存放的应该是需要接收Intent的所有BroadcastReceiver对象,带着这个疑问继续看它的关键成员变量。
成员变量和关键函数
1 | private final HashMap<BroadcastReceiver, ArrayList<LocalBroadcastManager.ReceiverRecord>> mReceivers = new HashMap(); |
想理解这几个变量的作用,需要去看对应的代码。
下面的代码是LocalBroadcastManager#registerReceiver()
函数,
从代码注释中的 标识1 标识2 可以看出变量名为mReceivers的HashMap存放通过注册的BroadcastReceiver(广播接收者)对象和存放了广播接收者信息的ReceiverRecord。
从 标识4 标识5 不难看出变量名为mActions的HashMap存放的是通过LocalBroadcastManager#registerReceiver()
方法注册的Action事件和这个事件所对应的所有ReceiverRecord。
1 | // 注册BroadcastReceiver(广播接收者) |
怎么还有个变量没有解释呢?别着急,想知道另一个变量的作用,需要看另一个函数的代码,那就是sendBroadcast
函数。从下面的代码注释中,可以看出sendBroadcast
函数的作用就是从前面介绍的mActions两个集合中取出需要通知的广播接收者,使用 BroadcastRecord 进行记录,并触发执行executePendingBroadcasts
函数罢了。
1 | // 发送广播函数 |
而executePendingBroadcasts
从函数名和前面的逻辑就可以猜到这个函数肯定是通知广播接收器接收Intent,并执行各自操作的最终函数。研究下面这个函数的代码后发现我们的猜测果然没有错,函数就是通过循环调用广播接收器的回调函数来实现 _广播_ 的功能的。什么本地广播管理器嘛,名头这么大,原来本质上就是通过普通的回调实现的。
1 | void executePendingBroadcasts() { |
总结
前面通过分析LocalBroadcastManager的源码,我们对LocalBroadcastManager有了更深的理解。我们发现它并没有很复杂的逻辑实现,但却提供了很有用的功能。同时我们只是分析了它的大致逻辑实现原理,并没有对细节做分析,比如:synchronized关键字的使用,为什么通过Handler来触发执行函数?而executePendingBroadcasts函数中为什么要使用死循环来实现?这些细节就要自己去分析理解了,这样才能提高自己的代码水平。我当然已经想通了,哈哈~
LocalBroadcastManager封装
为了方便使用LocalBroadcastManager,我对LocalBroadcastManager进行了简单的封装,不仅有Java语言实现,还有Kotlin语言的实现。顺便说一下,Kotlin真好用!
Java实现
1 | import android.content.BroadcastReceiver; |
Kotlin实现
1 | "NOTHING_TO_INLINE", "unused", "DEPRECATION", "SpellCheckingInspection") ( |