Android 广播是一种可 跨进程 的通信方式,系统中的每个程序都可以对自己感兴趣的广播进行注册,注册过后的程序只会接受到自己所关心的广播内容,这些广播可以来自系统,也可以来自系统里的各个程序。
Android 的广播可以分为两类,分别是
- 标准广播
- 有序广播
标准广播 是一种完全异步执行的广播,所有广播接收器都几乎同一时刻接收到该广播,这种广播的效率最高,但同时也就意味着它无法被拦截,如下图:
有序广播 是一种同步操作的广播,同一时刻只有一个广播接收器接收到广播,当这个广播接收器的处理逻辑执行完之后,广播才会继续传递,即此时的所有的广播接收器接收广播是有先后顺序的,优先级高的广播接收器可以优先接收到广播,同时也可以把广播拦截下来,如下图:
1 系统广播
Android 系统内置了很多系统级别的广播,应用程序可以通过监听这些广播来得到系统的状态信息。(比如系统开机会发送一条广播,断网会发送一条广播)
1.1 广播接收器
广播接收器可以自由地对自己感兴趣的广播进行注册,这样当相应的广播发送时,广播接收器就能够接收到该广播,并在内部处理相应的逻辑。
广播的注册方式有两种:
- 动态注册:在代码中进行注册
- 静态注册:在
AndroidMainfest.xml
文件中注册
而相应的逻辑处理就需要创建一个继承自 BroadCastReceiver
,具体的逻辑就是在重写父类的 onReceive
方法里面。
注意:在
onReceive
方法中不要添加过多的逻辑或耗时的操作,因为接收器中不允许开启线程,当onReceive
方法运行较长时间而没有结束时,程序就会报错。
因此,广播接收器更多的是在扮演一种打开程序其他组件的角色。
1.1.1 动态注册
下面是动态注册的代码,其中接收器的逻辑也作为内部类声明了:
- 首先创建
IntentFilter
实例,并调用addAction
方法选择哪个广播注册 - 创建对应接收器的实例
- 通过
registerReceiver
方法进行正式的注册 - 并且在活动生命周期的注销回调函数中,对广播进行注销
下面代码主要是对网络连接状态的改变进行注册广播:
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NewWorkChangeReceiver newWorkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建 IntentFilter,作用是筛选某个广播
intentFilter = new IntentFilter();
// 添加 网络状态变化 广播
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
// 注册广播
newWorkChangeReceiver = new NewWorkChangeReceiver();
registerReceiver(newWorkChangeReceiver, intentFilter);
}
/**
* 活动销毁时要注销广播接收器
*/
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(newWorkChangeReceiver);
}
/**
* 网络状态改变广播接收器
* 接收器类
* 继承 BroadcastReceiver 类,并重写 OnReceive 方法
*/
class NewWorkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 判断安卓版本
// 新版使用 getAllNetworks
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Network[] networks = connectivityManager.getAllNetworks();
NetworkInfo networkInfo;
for (Network mNetwork : networks) {
networkInfo = connectivityManager.getNetworkInfo(mNetwork);
if (networkInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
Toast.makeText(context, "网络可用", Toast.LENGTH_SHORT).show();
return;
}
}
} else {
//否则调用旧版本方法
if (connectivityManager != null) {
NetworkInfo[] info = connectivityManager.getAllNetworkInfo();
if (info != null) {
for (NetworkInfo anInfo : info) {
if (anInfo.getState() == NetworkInfo.State.CONNECTED) {
Toast.makeText(context, "网络可用", Toast.LENGTH_SHORT).show();
return;
}
}
}
}
}
Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show();
}
}
}
同时,Android 系统为了用户设备的安全和隐私,有严格的规定,如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限,如:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="top.seiei.aboutbroadcast">
<!-- 用户权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
注:如果只需保证只有处于栈顶的活动才能接收到广播,而非栈顶的活动不接收到广播,那么做法就可以是创建一个活动基类,在该活动基类中的
onResume
和onPause
方法中添加注册和注销广播的逻辑
1.1.2 静态注册
动态注册的广播接收器可以自由地控制注册和注销,但 必须在程序启动后才能接收到广播,想要程序还没启动就能接收到广播就需要 静态注册 了。
此时可以借助 Android Studio 的快捷方式,实现快速的静态注册,右键左侧项目放置对应接收器的包名,点击 New
-> Other
-> Broadcast Receiver
,此时会出现下面的对话框:
当中有两个勾选了的属性,分别是:
Exported
:是否允许该接收器接收 本程序以外 的广播Enabled
:是否启动该接收器
通过上述方法创建后,再打开 AndroidManifest.xml
,会发现 application
标签下多了以下标签:
<receiver
android:name=".receivers.BootCompleteReceiver"
android:enabled="true"
android:exported="true"></receiver>
此时还要说明该接收器用于接收哪些广播,所以修改后如下:
<receiver
android:name=".receivers.BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<!-- 接收哪些广播 -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
以上的代码表示该广播接收器接收设备启动时的广播,注意,监听系统开机广播也是需要声明权限的,所以还需要在 AndroidManifest.xml
文件中添加以下代码:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
2 自定义广播
上面介绍的都是如何接收系统广播,而接收自定义广播的操作也是类似的,那么自定义广播是如何创建和发送的。
这次使用到的还是 Intent
对象,步骤如下:
- 首先特有的广播名传入
Intent
类初始化Intent
,此时可以添加一些数据进行传递 - 调用
Context
的sendBroadcast
方法进行发送,此时所有注册了该广播的接收器都可以接收到该广播
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("top.seiei.MyFirstBroadcast");
intent.putExtra("txt", "传递过来的数据");
sendBroadcast(intent);
}
});
2.1 有序广播
上面开头就有讲到广播类型可以分为两种,标准广播和有序广播。而使用 sendBroadcast
方式发送的广播就是标准广播,而想要发送有序广播也很简单,就是使用 sendOrderedBroadcast
方法来代替 sendBroadcast
方法发送广播,sendOrderedBroadcast
方法接收两个参数,第一个参数是 Intent
,而第二个参数就是一个与权限相关的字符串,可以传入 null
,如:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("top.seiei.MyFirstBroadcast");
intent.putExtra("txt", "传递过来的数据");
sendOrderedBroadcast(intent, null);
}
});
在静态注册中,可以使用 android:priority
属性给广播接收器设置优先级,优先级比较高的广播接收器就先收到广播,如下代码将 TestReceiver
接收器的优先级设置为 100
:
<receiver
android:name=".receivers.TestReceiver"
android:enabled="true"
android:exported="true">
<!-- 接收哪些广播 -->
<intent-filter android:priority="100">
<action android:name="top.seiei.TestBroadcast"></action>
</intent-filter>
</receiver>
而在接收器逻辑中使用 abortBroadcast
方法可以 截断 这个广播的继续传递。
3 本地广播
上述所讲的广播都属于 系统全局广播,即发出的广播可以被其他任何程序接收到,这容易产生安全性问题,比如广播可能被其他程序截获,其他程序不停地发送垃圾广播等。
而 本地广播 就是用来解决这个问题,使用这个机制发出的广播只能在应用程序内部中进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,同时发送本地广播比发送系统全局广播更高效。使用本地广播主要就是使用 LocalBroadcastManager
来对广播进行管理,例子代码如下:
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalBroadcastManager localBroadcastManager;
private LocalReceiver localReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建 本地广播 管理对象实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("top.seiei.broadcasttest.LOCAL_BROADCAST");
// 本地广播管理实例发送广播
localBroadcastManager.sendBroadcast(intent);
}
});
// 创建 IntentFilter,作用是筛选某个广播
intentFilter = new IntentFilter();
intentFilter.addAction("top.seiei.broadcasttest.LOCAL_BROADCAST");
// 注册本地广播接收器
localReceiver= new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
/**
* 活动销毁时要注销广播接收器
*/
@Override
protected void onDestroy() {
super.onDestroy();
// 注销广播接收器
localBroadcastManager.unregisterReceiver(localReceiver);
}
/**
* 接收器类
* 继承 BroadcastReceiver 类,并重写 OnReceive 方法
*/
class LocalBroadcastManager extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "接收到本地广播", Toast.LENGTH_SHORT).show();
}
}
}
注:本地广播无法通过静态注册的方式来接收,这也很好理解,因为 静态注册主要就是为了让程序在未启动的情况下也能接收到广播,而发送本地广播的时候,该程序肯定是已经启动了,所以也就无需使用静态注册的功能。