Broadcast Receiver

介绍

  在Android系统中,系统会在某些特定情况下发送一系列的广播,例如开关机,开启关闭飞行模式,打开关闭网络连接等。而应用程序则可以使用BroadcastReceiver来接收这些消息并进行相应的操作,从而给用户较好的体验。例如关闭WIFI的时候应用可以提醒用户打开数据连接等,例如接收到开机广播的时候可以自动开启应用服务等。
  另外,除了系统外,Android应用本身也能发送广播。通过广播,可以轻易在应用间实现跨进程通信。当然,应用内也可以通过广播实现组件间的通信。
  而广播的载体则是Intent,通过Intent的一系列Action等进行区分不同的广播,同时也可以在Intent中存储数据进行数据的交换。

实现

  接收广播必须先定义一个具体的广播接收者,而实现方式则是通过继承BroadcastReceiver并实现其抽象方法onReceive(Context context, Intent intent)。应用接收的广播后就会在onReceive方法中进行处理。值得一提的是,该方法是运行在主线程中的,因此不能进行耗时操作。

public class MyReceiver extends BroadcastReceiver {
    private static final String TAG = "MyReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        StringBuffer sb = new StringBuffer();
        sb.append(intent.getAction() + "\r\n");
        sb.append(intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\r\n");
        Log.i(TAG, sb.toString());
    }
}
注册方式
静态注册

测试静态注册的时候最好使用安卓8(api 26)以下的设备

  静态注册是比较传统的注册方式,是在AndroidManifest中进行注册,就像Activity一样。使用这种注册方式,在广播到达的时候,即使当前应用程序没有处于启动状态,他也能接收到广播,系统会自动启动程序的广播接收器(上述实现的BroadcastReceiver)进行接收广播。这也是静态注册的优点,即不用担心应用是否开启,只要安装过软件之后,广播接收器就一直能够接收到广播。

<receiver
    android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.AIRPLANE_MODE"/>
        <action android:name="com.example.android.MY_RECEIVER"/>
    </intent-filter>
</receiver>

  如上为静态注册的示例,该广播接收器注册接收两种广播,分别是系统开启/关闭飞行模式时候的广播和自定义的广播。该注册方式主要是intent-filter标签,它是用于过滤各种广播保留所需广播的,广播通过action进行区分。
  注意的是,在Android 8.0(api 26)及以上版本中,静态注册的广播大部分被移除了,只保留少部分广播可以通过静态注册方式接收到。从这里也可以看出,官方并不推荐我们使用静态广播(为了省电)。

动态注册

  动态注册指的是在代码中进行注册,而并非在Manifest文件中进行注册。其实本质上也是一样,使用动态注册的时候也要添加IntentFilter对广播进行过滤,只保留对应action的广播进行响应。另外,动态注册的广播的效率是高于静态注册的,也就是说当广播发出后,会优先发送给动态注册的广播接收器,随后才会发送给静态注册的广播接收器。

BroadcastReceiver broadcastReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("cn.edu.ahut.MY_RECEIVE");
registerReceiver(broadcastReceiver, intentFilter);

  动态注册的方式也很简单,直接向IntentFilter中添加要响应的广播的action即可。这种启动方式依赖于启动组件的,即当启动的组件销毁的时候,动态注册的广播接收器就无法接收到广播了,并且动态注册的广播必须解除注册。
  如果使用的是Activity级别的Context调用registerReceiver,则Activity销毁则无法接收广播。若是使用Application级别的Context调用registerReceiver,则直到应用程序销毁前都能够接收到广播。

unregisterReceiver(broadcastReceiver);

  解除注册的广播通常放在对应注册的生命周期中。例如若是在onCreate中注册,则在onDestroy中解除注册。这样是为了防止由于生命周期的变化导致重复注册广播,另外,不要在onSaveInstanceState(Bundle)中解除注册,因为该方法只有在异常销毁活动的时候才会调用。

  通过以上两种方式中的一种,就可以成功注册一个用于接收广播的广播接收器。之前提过广播除了由系统发送外,还可以由应用程序发送,这样可以实现跨进程的交流。

发送广播

  在Activity中发送广播很简单,只需要定义一个intent,并在其中添加相应的广播的action就可以发送了。

Intent intent = new Intent();
intent.setAction("cn.edu.ahut.MY_RECEIVE");
// 发送无序广播
sendBroadcast(intent);

  上面的代码就是发送一个广播的过程,但这个广播发送的是无序的广播。所有的广播接收者都能够接收到该广播,并且接收的顺序是无序的。还有一种发送广播的方式,发送的是有序的广播,这种广播会根据广播接收器的优先级依次发送,即先发送给优先级高的广播接收者,再发送给优先级低的。

Intent intent = new Intent();
intent.setAction("cn.edu.ahut.MY_RECEIVE");
// 发送有序广播
sendOrderedBroadcast(intent, null);

  如下,广播接收器的优先级是在注册的时候定义的,数值是一个int值,默认为0,并且数值越大优先级越高。

// 静态注册方式
<receiver
    android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="1000">
        <action android:name="cn.edu.ahut.MY_RECEIVE" />
    </intent-filter>
</receiver>

// 动态注册方式
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("cn.edu.ahut.MY_RECEIVE");
intentFilter.setPriority(500);
registerReceiver(broadcastReceiver, intentFilter);

  有序广播是按照优先级顺序进行传播的,因此是可以被截断的。只有有序广播才能截断,截断后广播则不再向后传播,即低于该优先级的广播接收器接收不到该条广播。截断的方法应该在接收器的onReceive方法中调用。

abortBroadcast();

  截断广播后剩下的低优先级的广播接收器就不会收到该条广播,但是有时候又必须需要某个广播接收器接收到广播,这种情况下可以采用另一个发送广播的方法:

// 指定接收者的发送广播方法
sendOrderedBroadcast(
        Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
        Handler scheduler, int initialCode, String initialData,
        Bundle initialExtras)

  这个方法第三个参数是需要接收到广播的广播接收器,使用该方法发送的广播,即使传播过程中被截断,最后仍然能够接收到广播。但若是未被截断,最后广播接收器就能够收到两次相同的广播。这种指定广播接收者的方式只适用于同一个应用内,毕竟其他应用中的广播接收器对应的类是无法被访问到的。

  另外,在有序广播中,除了截断广播外,还可以在广播中添加自己的数据传递给下一个广播接收器。在广播中可通过下面的几个方法添加和获取广播中存储的数据。(可对比上面那个指定接收者的方法)

setResultData(String);
setResultCode(int);
setResultExtras(Bundle);
setResult(int code,String data,Bundle extra);

getResultCode();
getResultData();
//若是不存在数据,则根据参数返回对应的结果。true 返回一个空的Bundle,false返回null
getResultExtras(boolean)

  以上的两种广播都是全局广播,即所有注册过的广播接收器都能够接收到该广播。全局广播可以很方便的实现跨进程数据交流,但这也是他的一个缺点,其他应用程序可能会截断该广播,这将导致广播的传递出现问题。另外,甚至有些程序在接收到广播后将广播的容进行修改,这样目标广播接收器接收的数据就不是原始数据了,这对于应用而言是非常危险的。

静态注册的广播接收器在Android 8以后只能接收到极少的广播,自定义的广播无法接收。因此需要在发送广播的时候进行一些设置,使得静态注册的广播接收器也能接收到。
 Intent intent = new Intent();
 intent.setAction(“cn.edu.ahut.MY_RECEIVE”);
 // 方式1,添加特定的flags(这样其他程序中的广播接收器 也能接收发到该广播)
 intent.addFlags(0x01000000);
 // 方式2,设置组件名称(应用程序内部广播接收器接收广播)
 intent.setComponent(new ComponentName(this,MyReceiver,class));
 sendBroadcast(intent);
发送本地广播

  还有一种广播叫做本地广播,即广播只在本程序内部进行传播,其他外部程序组件将不会接收到该广播。这种广播限制了广播的传递范围,但是却有效增加了广播的安全性,并且还能够提高广播的传播速率。

具体使用如下:

// 发送广播
LocalBroadcastManager.getInstance(Context).sendBroadcast(intent);

// 注册本地广播
LocalBroadcastManager.getInstance(Context).registerReceiver(BroadcastReceiver,IntentFilter)

//解除本地广播注册
LocalBroadcastManager.getInstance(Context).unregisterReceiver(BroadcastReceiver);
总结

  广播是Android四大组件之一,从广播的发出位置来看,可分为系统广播和应用广播。系统广播是由系统在某些特定情况下发出的,例如在网络连接、开关机、数据连接等时候,系统都会发出对应的广播。应用广播是指应用本身发出的,该广播是应用进行传递消息的方式,可以作用于应用之间也可以作用在应用之内。
  应用广播又分为全局广播和本地广播,全局广播是指发出的广播所有的应用程序都可以接收到(满足对应的匹配规则),而本地广播只能够在本地传播,外部程序不能接收到,本地广播提高了广播的安全性和效率。
  广播接收器的注册方式分为静态注册和动态注册,静态注册是指在Manifest文件中注册,这种方式注册的广播接收器不依赖于应用程序,在程序未启动的时候也能够接收到广播。动态注册是在代码中进行注册,这种注册下的广播接收器依赖于注册的组件,在对应组件销毁后就无法接收广播,动态注册的广播必须手动解除注册。
  Android 8(api 26)以后,系统对广播进行了限制,静态注册的广播接收器只能够接收到极少的广播。因此鼓励使用动态注册的广播。