Android四大组件:BroadcastReceiver 介绍

介绍

BroadcastReceiver 即广播组件,是 Android 的四大组件之一。用于监听和接收广播消息,并做出响应。有以下一些应用:

  • 不同组件之间的通信(应用内或不同应用之间)。
  • 多线程之间通信。
  • 与系统在特定情况下(例如,电话呼入时、网络可用时)的通信。

原理

Android 中的广播机制使用了观察者设计模式:基于消息的发布、订阅事件模型。因此,广播的发送者和接收者解耦,使得系统方便集成,更容易扩展。

模型中有三个角色:

  • 消息订阅者(广播接收者)
  • 消息发布者(广播发送者)
  • 消息中心(ActivityManagerService

整个模型过程如下:

  1. 广播接收者通过 Binder 机制在 AMS 中注册订阅广播。
  2. 广播发送者通过 Binder 机制向 AMS 发送广播。
  3. AMS 根据广播发送者要求(IntentFilter、Permission),在已注册列表中寻找适合的接收者。
  4. AMS 将广播发送到合适的广播接收者相应的消息循环队列中。
  5. 广播接收者通过消息循环拿到广播,并回调 onReceive() 方法。

注:广播发送者和接收者的执行是异步的,发送者不会关心有无接收者接收,也不确定接收者何时才能接收到。

使用

步骤1:自定义广播接收器

继承 BroadcastReceiver 基类,并复写抽象方法 onReceive()。默认情况下,广播接收器运行在主线程,因此 onReceive() 方法不能执行耗时操作,否则阻塞主线程导致 ANR 问题。

一个 BroadcastReceiver 对象只有在被调用 onReceive() 时才有效,当从该方法返回后 BroadcastReceiver 对象就结束了生命周期。在 onReceive() 方法里,不建议使用线程来执行耗时操作,因为当得到其他异步操作所返回的结果时,BroadcastReceiver 可能已经结束生命周期了。如果确实需要的话,可以用调用 goAsync() 方法,然后再新开一个线程去执行,但是仍不建议执行超过 10 秒的任务。对于耗时的操作,最好使用 startService() 来完成。

// 继承 BroadcastReceiver 基类
public class TestBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "TestBroadcastReceiver";

    // 复写 onReceive() 方法,接收到广播后,自动调用该方法
    @Override
    public void onReceive(Context context, Intent intent) {
        final PendingResult pendingResult = goAsync();
        Task asyncTask = new Task(pendingResult, intent);
        asyncTask.execute();
    }

    private static class Task extends AsyncTask<String, Integer, String> {

        private final PendingResult pendingResult;
        private final Intent intent;

        private Task(PendingResult pendingResult, Intent intent) {
            this.pendingResult = pendingResult;
            this.intent = intent;
        }

        @Override
        protected String doInBackground(String... strings) {
            StringBuilder sb = new StringBuilder();
            sb.append("Action: " + intent.getAction() + "\n");
            sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
            String log = sb.toString();
            Log.d(TAG, log);
            return log;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            // Must call finish() so the BroadcastReceiver can be recycled.
            pendingResult.finish();
        }
    }
}

步骤2:注册广播接收器

广播接收器的注册方式分为两种:静态注册、动态注册。

静态注册

静态注册会时刻监听广播,在系统常驻,不受到任何组件生命周期的影响,但是也有耗电、占内存等缺点。

广播接收器的静态注册由 PMS 负责,在应用安装时,PMS 负责按照一定的目录顺序,扫描手机中所有安装的应用,并将应用清单文件中有关注册广播的信息解析存储起来。

所以,静态注册方法是在清单文件 AndroidManifest 里通过 <receiver> 元素标签声明

<receiver android:name=".TestBroadcastReceiver"
          android:permission="android.permission.SEND_SMS">
    <intent-filter>
        <action android:name="android.intent.action.AIRPLANE_MODE"/>
    </intent-filter>
</receiver>

注:由于系统是在应用安装时根据清单文件中的声明注册接收器,所以静态注册的广播接收器,即使应用不在运行,也可以接收到广播。

但是 Android 3.1(API 12)开始系统在 Intent 与广播相关的 Flag 中增加了两个参数,用来标识是否包含停止运行的包。系统不管什么广播类型,都默认增加值为 FLAG_EXCLUDE_STOPPED_PACKAGES 的 Flag,表示不包含停止运行的包。

系统广播是系统直接发出,无法更改此 Flag 值,导致即使是静态注册的广播接收器,假如其所在的进程已退出,同样无法收到广播。

而自定义广播,可以通过修改此 Flag 为 FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的广播接收器,在应用停止运行时也可以接收到广播,并会启动应用进程。

动态注册

动态注册是通过 AMS 负责的,注册方式比较灵活,可以跟随组件的生命周期变化,可以在特定时间段内监听广播。使用方法是调用 registerReceiver() 方法注册广播接收器的监听,调用 unregisterReceiver() 方法注销

TestBroadcastReceiver testBroadcastReceiver = null;

@Override
protected void onResume() {
    super.onResume();

    // 1. 实例化 BroadcastReceiver 子类
    testBroadcastReceiver = new TestBroadcastReceiver();

    // 2. 实例化 IntentFilter,设置接收广播的类型
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

    // 3. 动态注册:调用 Context的registerReceiver() 方法
    registerReceiver(testBroadcastReceiver, intentFilter);
}

@Override
protected void onPause() {
    super.onPause();

    // 注销在 onResume() 中注册的广播
    unregisterReceiver(testBroadcastReceiver);
}

注:因为动态广播注册后不注销会导致内存泄漏,所以最好在 Activity 生命周期中几个成对出现的方法里注册和注销。推荐在 Activity 中的 onResume()中注册,onPause 中注销,因为这样可以在 Activity 不在前台显示时,停止监听,减少运行的开销。

注意:如果应用针对 Android 的最新版本,则需注意系统广播行为的多处修改:

Android 7.0(API 24)或更高版本,系统不再发送 ACTION_NEW_PICTUREACTION_NEW_VIDEO 广播,并且只有动态注册的广播接收器能接收到 CONNECTIVITY_ACTION 广播。

Android 8.0(API 26)或更高版本,系统对静态注册的接收器施加了额外限制,无法为大多数隐式广播声明接收器,但可以使用动态注册接收器。

Android 9(API 28)或更高版本NETWORK_STATE_CHANGED_ACTION 广播不会收到有关用户位置或个人身份数据的信息。自 Wi-Fi 的系统广播不包含 SSID,BSSID,连接信息或扫描结果。

步骤3:广播发送者向 AMS 发送广播

广播发送者通过 sendBroadcast() 方法向 AMS 发送包装了广播的 Intent 对象。广播一般主要分为以下五类:

  • Normal Broadcast 普通广播
  • System Broadcast 系统广播
  • Ordered Broadcast 有序广播
  • Sticky Broadcast 粘性广播
  • Local Broadcast 本地广播

普通广播(Normal Broadcast)

开发者自定义 Intent 的全局广播。若发送广播需要相应的权限,则广播接收器也需要相应的权限。发送广播方式如下:

Intent intent = new Intent();
// 对应 BroadcastReceiver 中 IntentFilter 定义的 Action
intent.setAction(TEST_BROADCAST_ACTION);
sendBroadcast(intent);

系统广播(System Broadcast)

Android 系统中定义了很多内置的广播,涉及到手机的一些基本操作(例如,开机、网络状态变化、拍照等),都会发送相应的系统广播。

每个广播都有特定的 IntentFilter,使用系统广播时,只需要在注册广播接收器处声明相关的 IntentFilter 条件即可,并不需要手动发送广播,系统会在执行相关操作时自动进行系统广播。

有序广播(Ordered Broadcast)

广播接收器按照一定顺序规则接收广播,先接收的广播接收器可以对广播进行修改或截断,再被其他的广播接收器接收,或终止广播。顺序规则如下:

  1. 按照 priority 属性值从大到小排序。
  2. priority 属性值相同时,动态注册的广播接收器优先于静态注册。
  3. 静态注册相同优先级的接收器,先扫描的优先级高。
  4. 动态注册相同优先级的接收器,先注册的优先级高。

注:优先级对无序广播同样有效,对于无序广播:动态注册优先级高于静态注册(无视优先级)。同一种注册方式 priority 属性大的优先级高。同一 priority 属性值下,静态注册先扫描的优先级高,动态注册先注册的优先级高。

发送有序广播与普通广播类似,但使用的是 sendOrderedBroadcast() 方法发送广播,在 onReceive() 中通过 setResultExtras() 给下一优先级的接收器传递数据,通过 getResultExtras() 取出上一优先级接收器传递来的数据,通过 abortBroadcast() 截断广播的发送。

通过 sendOrderedBroadcast() 方法可以指定最终广播接收器 ResultReceiver。如果比它优先级高的接收器不终止广播,则最终接受器会接收两次广播:第一次,按照标准的优先级接收,第二次,接收最终的广播。如果比它优先级高的接收器终止广播,那么它只是接收一次最终的广播。

粘性广播(Sticky Broadcast)

在Android 5.0(API 21)或更高版已失效。

本地广播(Local Broadcast)

由于 Android 中的广播可以跨应用直接通信,所以可能造成以下两个问题:

  • 其他应用发出与当前应用 IntentFilter 相匹配的广播,导致当前应用不断接收和处理一些无用甚至是恶意的广播。
  • 其他应用注册与当前应用一致的 IntentFilter 用于接收广播,导致可以截取当前应用广播的具体信息,存在安全性问题。

使用本地广播可以有效解决上面的问题,提高安全性和效率。使用本地广播的方式有两种:

将全局广播设置为本地广播
  1. 注册广播接收器时将 exported 属性设置为 false,只接收使当前应用发出的广播。
  2. 在广播发送和接收时,增设相应的自定义权限 permission,用于权限验证。
  3. 在 Android 4.0 及更高版本,发送广播时通过 Intent 的 setPackage() 方法指定包名,此广播只会发送到指定包中匹配的广播接收器。
使用 LocalBroadcastManager 类

使用方式与全局广播类似,区别是要使用 LocalBroadcastManager 的单例来调用 registerReceiver()unregisterReceiver 来注册和注销广播接收器,调用 sendBroadcast() 来发送广播。

// 注册应用内广播接收器
testBroadcastReceiver = new TestBroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
LocalBroadcastManager.getInstance(this).registerReceiver(testBroadcastReceiver, intentFilter);

// 注销应用内广播接收器
LocalBroadcastManager.getInstance(this).unregisterReceiver(testBroadcastReceiver);

// 发送应用内广播
Intent intent = new Intent();
intent.setAction(TEST_BROADCAST_ACTION);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

其他

对于不同注册方式的广播接收器,在回调 onReceive(Context context, Intent intent) 中的 context 返回值是不一样的:

  • 静态注册:context 返回的是 ReceiverRestrictedContext。
  • 动态注册的全局广播:context 返回的是 Activity Context。
  • 动态注册的本地广播:context 返回的是 Activity Context。
  • 动态注册的本地广播(LocalBroadcastManager):context 返回的是 Application Context。
posted @ 2019-08-26 19:25  银色子弹  阅读(715)  评论(0编辑  收藏  举报