• 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() + "
    ");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "
    ");
                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。
  • 相关阅读:
    CR655 题解
    International Master 计划试题乱做 part1
    CR604 题解
    用js做分页,点击下一页时,直接跳到了最后一页——Number()的妙用
    RabbitMQ生产者消费者模型(二)
    线程的状态(二)
    Python服务端测试开发训练营
    RabbitMQ环境搭建(一)
    RabbitMQ死信队列机制(七)
    RabbitMQ限流机制(五)
  • 原文地址:https://www.cnblogs.com/theo/p/11414399.html
Copyright © 2020-2023  润新知