提到广播,因为我是农村的,首先想到的就是我们村大队的喇叭,如果平常有交电费,或者重要的事情,这个喇叭就会响起,全村的都能听见,可能有时候邻村的也能听见,足见广播的推送面积之大。

提到广播这种机制,学过iOS的都知道通知,我想这两种机制应该都是类似的。在iOS中,我们如果想监听系统的某些事件,比如APP退到后台,我们只需要在把监听对象添加到系统的通知中心中,并且关注APP退到后台的事件,就可以随时监听了。通知还有个好处就是,我们可以夸线程,夸界面的处理事件。

那么,在Android中,广播又是怎么回事呢,这篇文章就来详细分析一下广播在Android中的应用。(ps:广播还有另外一个名字——全局大喇叭~~)

广播机制简介

在广播机制中,存在两种角色:广播发送者广播接收者

我们来看看广播的使用场景:

  • 同一APP内部的同一组件内部的消息通信(单个或多个线程之间)
  • 同一APP内部的不同组件之间的消息通信(单个进程)
  • 同一APP具有多个进程的不同组件之间的消息通信
  • 不同APP之间的组件之间消息通信
  • Android系统在特定情况下与APP之间的消息通信

另外,Android中的广播主要可以分为两种类型:标准广播有序广播

标准广播

标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但是却不能被截断。标准广播的工作流程是:



有序广播

有序广播是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑处理执行完毕后,广播才会继续传播,而且优先级高的广播接收器可以优先接收到广播。并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。有序广播的工作流程是:



接收系统广播

Android内置了很多系统级别的广播,如果我们想要监听系统的这些广播,就需要用到广播接收器(BroadcastReceiver)。注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。

动态注册监听网络变化

凡是广播接收者都必须继承自BroadcastReceiver,并重写抽象方法onReceive(),当有广播到来时,onReceive()方法就会得到执行。

我们使用动态注册的方式去监听网络的变化。代码如下:

public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver mNetworkChangeReceiver;
@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");
mNetworkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(mNetworkChangeReceiver,intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mNetworkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver {
//实现接收的方法
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()){
Toast.makeText(context,"有网了!",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context,"没网了!",Toast.LENGTH_SHORT).show();
}
}
}
}

不要忘了,要在AndroidManifest.xml中添加网络权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

可以看到,我在MainActivity中定义了一个内部类NetworkChangeReceiver并继承BroadcastReceiver,重写了父类的onReceive()方法。每当网络发生变化的时候,onReceive()方法就会得到执行。

onCreate()方法中,我创建了一个IntentFilter的实例,并给它添加了一个值为android.net.conn.CONNECTIVITY_CHANGE的action,因外当网络状态发生变化时,系统会发出一条值为android.net.conn.CONNECTIVITY_CHANGE的广播。因为我们是动态注册广播,所以一定要在onDestroy()方法中调用unregisterReceiver()注销广播的监听。

静态注册实现开机启动

动态注册的广播接收器可以自由的控制注册与注销,在灵活性方面有很大的优势,但是它存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()方法里的。如果我们想在程序未启动的时候,就能接收到广播,那就要用到静态注册了。

首先我们需要创建一个广播接收器类,创建方式为:选择新建->Other->Broadcast Receiver。Android Studio会让我们选择谢谢东西,Exported表示是否允许这个广播接收器接收本程序以外的广播,Enabled表示是否启用这个广播接收器,这里我们都勾选。

public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"手机开机了",Toast.LENGTH_SHORT).show();
}
}

可以看到,BootCompleteReceiver类里面的代码很简单。

另外,静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用,但是注册这一步已经被Android Studio自动完成了。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiyongdong.broadreceiverdemo">
<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>
<!--广播接收者-->
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
</receiver>
</application>
</manifest>

目前,我们还不能接收到开机广播,因为我们还没有为它添加响应的action。添加代码如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiyongdong.broadreceiverdemo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--添加权限-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<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>
<!--广播接收者-->
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!--添加开机广播的监听-->
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>

由于Android系统自动完成后会发送一条值为android.intent.action.BOOT_COMPLETED的广播,因此需要在<intent-filter>标签中添加相应的action,当然,监听系统开机广播的权限也是需要声明的。如此,我们的APP便可监听手机的开机了。

发送自定义广播

说了这么多,一直都是系统在发广播,但是有时候我们根据业务需求要想发送自己的广播该这么办呢?前面说了,发广播的形式有两种,标准广播和有序广播,那就来看看这两种广播如何发送。

发送标准广播

在发送广播之前,我先定义一个广播接收器,这里约定action为com.guiyongdong.MY_BROADCAST,代码如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiyongdong.broadreceiverdemo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 添加权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<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">
...
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!--添加自定义的广播接收器-->
<action android:name="com.guiyongdong.MY_BROADCAST"/>
</intent-filter>
</receiver>
</application>
</manifest>

MyBroadcastReceiver广播接收器的代码如下:

public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"接收到自定义的广播",Toast.LENGTH_SHORT).show();
}
}

我在MainActivity中添加了一个按钮,按钮添加点击事件,会发送名为com.guiyongdong.MY_BROADCAST的广播,代码如下:

public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver mNetworkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.sendButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.guiyongdong.MY_BROADCAST");
sendBroadcast(intent);
}
});
}
}

重新运行,并点击按钮,效果图如下:



发送有序广播
public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver mNetworkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.sendButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.guiyongdong.MY_BROADCAST");
sendOrderedBroadcast(intent,null);
}
});
}
}

可以看到,发送有序广播只需要改动一行代码,即sendBroadcast()方法改成sendOrderedBroadcast()方法。sendOrderedBroadcast()方法接收两个参数,第一个参数任然是Intent,第二个参数是一个与权限相关的字符串,这里传null就行了。

如何定义有序广播的接收者的优先级呢?通过android:priority可以设置优先级。需要修改AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiyongdong.broadreceiverdemo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 添加权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<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">
...
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<!--修改优先级-->
<intent-filter android:priority="100">
<!--添加自定义的广播接收器-->
<action android:name="com.guiyongdong.MY_BROADCAST"/>
</intent-filter>
</receiver>
</application>
</manifest>

那么,当接收者接到广播,也已经处理好逻辑后,如果接收者不想让广播继续传递了呢?这时候,需要调用abdortBroadcast()方法,来中断传播。

public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"接收到自定义的广播",Toast.LENGTH_SHORT).show();
//中断传输
abortBroadcast();
}
}

本地广播

前面我所说的发送的广播都是全局广播,这些广播可以被其他任何应用程序接收到,并且我们也可以接收来自其他应用的广播,这样容易引起安全问题。

当然,Android已经为我们想到了这点,利用本地广播就可以解决广播的安全问题,而且这类广播只能在应用程序内部进行传递,并且广播接收者只能接收来自本应用程序发出的广播。

本地广播并不复杂,它主要使用LocalBroadcastManager来管理广播,并且提供了发送广播和注册广播接收器的方法,我们来看如何使用:

public class MainActivity extends AppCompatActivity {
private LocalBroadcastManager mLocalBroadcastManager;
private LocalReceiver mLocalReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建本地广播管理实例
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
Button sendLocalButton = (Button) findViewById(R.id.sendLocalButton);
sendLocalButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.guiyongdong.LOCAL_BROADCAST");
//发送本地广播
mLocalBroadcastManager.sendBroadcast(intent);
}
});
//注册广播
IntentFilter intentFilter1 = new IntentFilter();
intentFilter.addAction("com.guiyongdong.LOCAL_BROADCAST");
mLocalReceiver = new LocalReceiver();
mLocalBroadcastManager.registerReceiver(mLocalReceiver,intentFilter);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"接收到本地广播",Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
}
}

注意:本地广播是无法通过静态注册的方式来接收的。

总结一下本地广播的几点优势:

  • 可以明确知道正在发送的广播不会离开我们的程序,因此不用担心数据泄露。
  • 其他的程序无法将广播发送到我们程序内部,因此不用担心会有安全漏洞的隐患。
  • 发送本地广播比全局广播更加高效。