文章首发至个人公众号:追风栈Binary
广播(Broadcast)是Android系统以及应用程序传递信号的一种机制。它灵活的穿梭在各个应用程序之间或者是单个应用程序的内部,一旦发现满足条件的接收者,就会去这个接收者中进行广播。广播作为Android四大组件之一,自然在系统的组成上也占据着重要的位置。广播的内容很精简,但是其内部的原理实现是十分复杂的,本文暂先讨论如何在应用中使用广播,原理部分等后续再来分析。
什么是Android 广播(Broadcast)
Android广播机制是Android系统中用于发送和接收广播消息的一种手段。这种机制的应用在Android系统中非常广泛,以下的几种场景大家肯定很熟悉:
手机快没电了,弹出了提示电量不足的消息窗口
逛淘宝的时候没信号,会弹出提示无法连接网络的提示信息
王者团战快推到水晶的时候,突然提示你手机内存不足
部分手机连上无线网络时也会弹出信息提示已经连上了WIFI
还有很多其他的应用场景,这些现象的背后都是Android广播机制在发挥作用,它传递着一种信号,使得能够接受到这个信号的事物会进行响应,完成后续的逻辑操作。
理解Android广播收发机制
既然是广播,那么说明肯定是存在广播发送者和广播接收者的。即我们首先需要解决两个问题:
广播是如何发送的?
谁可以被当做接收者?接收者如何接收广播?
在先回答这些问题之前,我们可以先通过一个现实生活中常见的例子,来帮助我们理解后续的概念。自己画了个图,以买彩票为例,用买彩票再兑奖的过程类比学习广播机制。
假定A、B、C三人购买彩票,由彩票中心开奖,最后C买的数字全部匹配成功,获得了彩票的一等奖。如下图所示:
那么广播过程可以按照如下步骤来理解:
A、B、C是购买者,他们购买彩票,并将彩票作为唯一抽奖凭证(标记自身需要监听的信号)
彩票购买站接受A、B、C三者的预测号码,并保存在数据库中(在注册组件进行广播注册)
彩票中心是彩票开奖的发起者,它随机生成一组中奖号码(广播的发起者)
A、B、C三者核对是否与彩票中心生成的号码一致,来检查自己是否中奖(自身监听与广播信号是否匹配)
C与彩票中心的号码一致,C中奖了(匹配成功,执行后续逻辑)
黑色加粗的文字部分是对应于广播机制的原理。广播是基于观察者设计模式来构建的,从上面的示例可以看出,广播的核心是注册和接收,更进一步,我们可以抽象出广播的结构模式。
这和买彩票的流程是完全一致的,对于广播的发送者而言,自身只需要定义一条信号发出后就可以不用管了。而接收者作为被动接收方,它首先需要声明自己"想接收到什么信息",再进行注册,这样当广播信号传递时,它会根据自身想接收的信号与传递来的信号进行匹配,匹配成功就会响应这个信号。例如Android系统是图中的广播发送方,在低电量的时候,触发低电量
广播,任何一个注册监听了系统低电量
广播的程序都会响应这条广播。
回到最上面的两个问题上来:
广播如何发送的?广播发送方设定条件,达到条件后触发该广播信号进行发送
任何声明了自身需要监听的广播信号并在注册机制中进行注册的事务都可以监听广播信号,并对完全匹配自身监听的广播信号进行响应
广播的类型
广播在执行方式的角度上可以分为两种:标准广播Normal Broadcasts
和有序广播Ordered Broadcasts
标准广播:一种完全异步执行的广播,所有的广播接收器都可以同时接收到这条广播,彼此没有先后之分,所有的接收者都是同等对待。另外,标准广播是无法被截断的。例如A、B、C三者买彩票就是一种标准广播,彩票中心发出中奖号码,A、B、C都是同一时间收到这个消息,谁也没办法去阻止对方去拦截这条消息。它的结构图如下:
有序广播:从名字上就可以看出来这是一种按照顺序执行广播的方式,它意味着在广播发出之后,同一时刻只有一个广播接收器能够收到广播。并且优先级高的广播接收器最先接收到广播。除此之外,接收到广播的接收器还可以选择截断正在传递的广播,后续的广播接收器就没法收到这条传递的广播了。它的结构图如下:
广播在注册方式上可以分为动态注册和静态注册两种方式
动态注册:指的是在程序代码中进行广播注册的过程。下图展示了基于动态注册方式的广播代码结构。
第一部分:使用
IntentFilter()
设置自身期望捕获到的广播信号,这个信号要和发出的广播信号一直才会响应第二部分:内部类
NetworkChangeReceiver
继承BroadcastReceiver
,凡是需要监听广播信号的,都必须继承BroadcastReceiver
类并实现其中的onReceive
方法。第三部分:
registerReceiver
将NetworkChangeReceiver
的对象和IntentFilter
的对象进行注册,以便接收传来的广播信号第四部分:
unregisterReceiver
取消注册,任何注册了广播信号的对象都必须要实现取消注册的功能
上面程序的广播实现在onCreate
方法中,这意味着程序必须打开才能接收到广播。
静态注册:指的是在AndroidManifest.XML
中注册广播接收器的过程。下面的程序结构展示了静态注册的实现
外层大框:这部分用
标签标注,表明这是一个广播接收器,在Android Studio
中,可以通过新建一个Broadcast Receiver
来自动完成该部分的编写。里层小框:同样也使用了
intent-filter
和action
,表明自身设置的广播信号。在使用权限时,务必要记得在上方声明自己所申请的权限,不然程序会直接崩溃
静态注册可以使程序不用开启便可以接收到广播信号,这个特性在某些场合会比动态注册更为适用。
广播在作用域上又可以分为全局广播和本地广播
全局广播:指的是可以被其他任何应用程序都能接收到的广播,比如说Android系统发出的电量不足的广播,所有应用程序都能收到。但是全局广播也存在着一些安全隐患,自身发出的广播能被任何应用程序接收,有时候想想都会觉得心里不安。
本地广播:指的是只能在应用程序内部传递的广播信号,并且广播接收器也只能接收来自本程序发出的广播。这是一种以应用程序为单位的封闭广播模式,外面的进不来,里面的也出不去,形成了一种封闭的广播环境。这种机制下的广播是十分安全的。后面会用一个例子来说明本地广播的实现。
广播的实现案例
全局广播实现很简单,定义发送者、接收者和广播信号即可。
//定义一个继承BroadcastReceiver的MyReceiver类,实现onReceive方法
//onReceive方法会在匹配成功后调用,这里让它简单的显示一下即可
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
Toast.makeText(context, "响应了自身设置的广播", Toast.LENGTH_SHORT).show();
}
}
//这个是在AndroidManifest文件中,设置一个自定义的广播"com.example.globalbroadcast.MY_TEST"
//当接收到这条广播时,就会进行匹配,如果匹配成功,就会调用上面的onReceive方法
//这个是在MainActivity中实现的,设置了一个Button,当做广播信号的发送方。
//广播也仍然是使用intent来发送的,当按下Button,就会通过intent发送一条广播
//注意,这里用到了sendBroadcast()方法,这将发出一个全局广播,所有广播接收器都能收到这条广播
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.testMyBroadcast);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.example.globalbroadcastdemo.MY_TEST");
sendBroadcast(intent);
}
});
}
}
前面我们提到说全局广播可能会存在一些安全问题,因此Android也提供了一种只在应用程序内部传递的广播机制:本地广播。本地广播的实现与全局广播的实现很类似,不过本地广播需要调用LocalBroadcastManager
来进行管理,在一些细节方面有些差异。
// 本地广播的实现,调用了LocalBroadcastManager
// 本地广播仅在本应用程序内传播,外部其他的应用程序接收不到该广播发出的信号
public class MainActivity extends AppCompatActivity {
/**
* intentFilter 意图过滤器,设置自身需要监听的广播信号
*/
private IntentFilter intentFilter;
/**
* localBroadcastManager 本地广播管理
*/
private LocalBroadcastManager localBroadcastManager;
/**
* localReceiver 本地广播接收器
*/
private LocalReceiver localReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取一个本地广播管理的对象
localBroadcastManager = LocalBroadcastManager.getInstance(this);
// 本地广播的发送者
Button button = findViewById(R.id.testMyBroadcast);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new
Intent("com.example.globalbroadcastdemo.MY_LOCAL");
localBroadcastManager.sendBroadcast(intent);
}
});
// 设置自身需要监听的广播信号
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.globalbroadcastdemo.MY_LOCAL");
// 注册本地广播监听器
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
//LocalReceiver 内部类,实现了onReceive方法
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "收到了本地发出的广播",
Toast.LENGTH_SHORT).show();
}
}
}
从上面两个例子可以看出,广播的调用实现是比较简单的。另外也要注意一点,本地广播只能通过动态的方式来实现,它无法采用静态注册来接收广播,因为本地广播只能在应用程序内传递,我们必须进入应用程序,才能发送或接收本地广播。而静态注册的广播一般是面向全局。
广播真的简单吗?
这篇文章在开始的时候就提及了这一点,从调用实现广播的方式来看,广播实现的确很简单:确定发送和接收、注册广播、重写onReceive
方法,没有太多的逻辑难点。但,实际情况真的是这样么?
在一些博客平台上搜索了Android广播的实现具体机理,才发现简单调用的背后蕴含着复杂的逻辑实现,不论是方法调用还是方法实现,都得花点时间去慢慢消化理解。
反过来,复杂实现的表面则是简单的接口调用。这种近乎完美的封装很好的诠释了类的设计意义:调用者只负责调用,而不需要时时刻刻去了解内部的实现细节。
总结
这篇文章对Android广播的调用实现做了一个简单的介绍,从如何使用的层面上介绍了广播的多种类型和典型广播的使用示例。但仍需要明白,一个事物,如果对它的理解仅仅停留在调用表面,那么迟早会在上面摔跟头。深入广播的Android源码,去看看这个过程到底发生了什么,Android是如何处理广播的,在更高的角度上看待问题,才能更好的规避出现的问题。