Android基础:BroadcastReceiver —— 实例以及使用总结

BroadcastReceiver目录.png

一. BroadcastReceiver简介

1.1 BroadcastReceiver定义(What?)

Android四大组件之一,是一种全局的监听器。

1.2 BroadcastReceiver作用(Why?)

  • 监听系统或应用发出(或接收)的广播信息,然后根据相应信息做逻辑处理。

常用监听系统广播信息如有电话打来时、网络状态发生变化时、手机电量发生变化时等。

  • 发送或接收少量或发送频率较低的数据。

因为使用BroadcastReceiver发送/接收大量数据开销较大,且由于其消息是异步的,所以有可能发生数据接收不到的情况。

  • 其它作用:
  1. 不同组件间的通信,包括同应用和不同应用间。如常用的ActivityService间通信。
  2. 多线程间通信。

二. 广播(Broadcast)的分类及使用(How?)

Android中广播主要可以分为五类:

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

2.1 普通广播(Normal Broadcast)

普通广播也就是开发者常用的自定义的广播,使用步骤:

  1. 首先需要一个接收广播的类,BroadcastReceiver是一个抽象类,抽象方法onReceive()必须实现:
public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"MyBroadcastReceiver Get Message",Toast.LENGTH_SHORT).show();
    }
}
  1. 如同其它的组件一样,自定义的BroadcastReceiver也需要在manifest中进行注册,其中<intent-filter>中的<action用于指定该receiver要接收的广播的类型,类型不对就接收不到了。此外还可以动态注册,后面再说:
    <receiver
        android:name=".MyBroadcastReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.sky.intent.action.MYBROADCASTRECEIVER"/>
        </intent-filter>
    </receiver>

android:enabled="true"表示是否可用
android:exported="true"表示是否接收其它应用发送的广播,如果该receiver有<intent-filter>属性,则默认为true,如果没有,则android:exported默认为false。

  1. 然后需要发送广播让MyBroadcastReceiver来接收,简单地定义一个按钮,点击设置Intent对象的action并发送广播。
    <Button
        android:onClick="sendBroadcast"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送广播"/>

这里设置Intent的action必须与MyBroadcastReceiver定义的action保持一致,如同其它组件运行一样直接调用。

    public static String ACTION_MYBROADCAST = "com.sky.intent.action.MYBROADCASTRECEIVER";
    ...
    public void sendBroadcast(View view){
        Intent intent = new Intent(ACTION_MYBROADCAST);
        sendBroadcast(intent);
    }

2.2 有序广播(Normal Broadcast)

定义:顾名思义,就是有顺序的广播,但是要注意的是这里的有序是对广播接收器而言的。也就是说广播的发送并没有顺序,接收者可以设定优先级的高低。
特点:
1、广播接收器按照优先级的高低来收到广播,高优先级的接收者先收到广播;
2、中途可以中断广播的向下传递;
3、高优先级的receiver可以修改向下传递的广播的内容。
使用:

  1. manifest中的<receiver中设定广播接收器的优先级,重要的是android:priority="100"
    <intent-filter android:priority="100">
        <action android:name="com.sky.intent.action.MYBROADCASTRECEIVER"/>
    </intent-filter>

然后再定义一个MyBroadcastReceiver2,重写onReceive()并设定优先级android:priority="99"

  1. 发送有序广播时使用以下方法:
    public void sendMyOrderdBroadcast(View view){
        Intent intent = new Intent(ACTION_MYBROADCAST);
        // 第二个参数为receiverPermission
        // 是一个字符串权限,设定后广播接收器注册该权限后才能收到
        sendOrderedBroadcast(intent,null);
    }

这样就可以弹出两次Toast信息。

  1. 拦截广播:在优先级较高的广播接收器MyBroadcastReceiveronReceive方法中调用abortBroadcast()方法。
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"MyBroadcastReceiver Get Message",Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }

就可以发现Toast只弹出一次,说明广播被拦截了。

  1. 修改向下传递的广播内容:

(1) MyBroadcastReceiveronReceive方法中接收信息,然后可以重新设置Bundle对象传递的信息。

    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"MyBroadcastReceiver Get Message",Toast.LENGTH_SHORT).show();
        //获取处理的的广播,普通广播不能获取处理
        //true代表如果前面的接收器没有存放数据,则自动创建一个空的Bundle对象
        //false则表示如果前面的接收器如果没有存放任何数据则返回null。
        Bundle bundle = getResultExtras(true);
        bundle.putString("message","Modify Message");
        setResultExtras(bundle);
    }

(2) MyBroadcastReceiver2onReceive中直接获取信息。这样就完成了信息的修改传递。

    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle = getResultExtras(true);
        Toast.makeText(context,"MyBroadcastReceiver2 Get Message"+bundle.get("message"),Toast.LENGTH_SHORT).show();
    }

2.3 系统广播(System Broadcast)

定义:Android系统内置各种广播,当手机状态发生变化时系统会发送广播。监听系统广播然后可以进行逻辑处理。
使用:设置广播接收器intent-filter的不同action,可以接收到系统不同状态所发送的广播。
例如下方是监听系统网络变化接收广播,另外要注意申请相应权限:

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    ...
    <receiver android:name=".MySysBroadcastReceiver">
        <intent-filter>
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
        </intent-filter>
    </receiver>
    @Override
    public void onReceive(Context context, Intent intent) {
        //**判断当前的网络连接状态是否可用*/
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = connectivityManager.getActiveNetworkInfo();
        if ( info != null && info.isAvailable()){
            //当前网络状态可用
            Toast.makeText(context,"网络连接已恢复",Toast.LENGTH_SHORT).show();//封装的Toast
        }else {
            //当前网络不可用
            Toast.makeText(context,"无网络连接",Toast.LENGTH_SHORT).show();//封装的Toast
        }
    }

不同的action对应不同的系统状态,列举如下:

系统操作 action name
监听网络变化 android.net.conn.CONNECTIVITY_CHANGE
关闭或打开飞行模式 android.intent.action.AIRPLANE_MODE
充电时或电量发生变化 android.intent.action.BATTERY_CHANGED
电池电量低 android.intent.action.BATTERY_LOW
电池电量充满 android.intent.action.BATTERY_OKAY
系统启动完成后(开机完成) android.intent.action.BOOT_COMPLETED
按下拍照按键 android.intent.action.CAMERA_BUTTON
设备当前设置被改变时(界面语言、设备方向等) android.intent.action.CONFIGURATION_CHANGED
插入耳机时 android.intent.action.HEADSET_PLUG
未正确移除SD卡但已取出来时(正确移除方法:设置--SD卡和设备内存--卸载SD卡) android.intent.action.MEDIA_BAD_REMOVAL
插入外部储存装置(如SD卡) android.intent.action.MEDIA_CHECKING
成功安装APK android.intent.action.PACKAGE_ADDED
成功删除APK android.intent.action.PACKAGE_REMOVED
屏幕关闭 android.intent.action.SCREEN_OFF
屏幕打开 android.intent.action.SCREEN_ON
用户解锁 android.intent.action.USER_UNLOCKED
用户锁屏 android.intent.action.USER_PRESENT
重启设备 android.intent.action.REBOOT
关闭系统时 android.intent.action.ACTION_SHUTDOWN

2.4 本地广播(Local Broadcast)

定义:Android引入的一套本地广播机制,使用该机制发出的广播只能在应用程序内部进行传递,并且广播也只能是APP内部的BroadcastReceiver的接收。
优点:

  • 高效率:本地广播的发送和接收都只在当前App进行。
  • 安全性高:全局广播存在许多问题,比如携带数据的广播有可能被其它应用截获、其它应用发送垃圾广播影响自身应用运行,而本地广播则没有这些问题。

使用:

  • 全局广播设置为本地广播:
  1. 注册广播时,将exported属性设置为false,即不予其它应用程序产生联系。
  2. 发送和接收广播时设置权限permission。
sendBroadcast(intent,"com.sky.broadcast_test.permission");

如果发送的broadcast带有permission,那么只有在那些manifest文件中包含了<uses-permission>的receiver才能够接受到;高版本需要设置权限级别。

    <uses-permission android:name="com.sky.broadcast_test.permission"/>
    <permission
        android:name="com.sky.broadcast_test.permission"
        android:protectionLevel="normal" />

如果在<receiver>中设置了<android:permission="">,那么只有在那些manifest文件中包含了<uses-permission>的broadcast才能够发送给它。

  1. 发送广播时指定广播接收器所在的包名。
intent.setPackage("packageName");
  • 使用LocalBroadcastManager来发送本地广播。步骤:
  1. 自定义一个广播,这里以内部类的形式写到了MainActivity
    class MyLocalBroadcastReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context,"MyLocalBroadcastReceiver Get Message",Toast.LENGTH_SHORT).show();
        }
    }
  1. 获取LocalBroadcastManager实例,创建自定义广播对象,动态注册广播。
    // 获取LocalBroadcastManager实例
    localBroadcastManager = LocalBroadcastManager.getInstance(this);

    // 设置IntentFilter的action
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(ACTION_MY_LOCAL_BROADCAST);

    // 动态注册广播
    myLocalBroadcastReceiver = new MyLocalBroadcastReceiver();
    localBroadcastManager.registerReceiver(myLocalBroadcastReceiver,intentFilter);
  1. 使用localBroadcastManager发送广播。
    public void sendMyLocalBroadcast(View view){
        // 使用localBroadcastManager发送广播
        Intent intent = new Intent(ACTION_MY_LOCAL_BROADCAST);
        localBroadcastManager.sendBroadcast(intent);
    }
  1. 解除注册,防止内存泄漏。
  @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解除注册,预防内存泄漏
        localBroadcastManager.unregisterReceiver(myLocalBroadcastReceiver);
    }

2.5 粘性广播(Sticky Broadcast)

被废弃,Android5.0 & API 21已经失效,不用管它。

三. BroadcastReceiver两种注册方式

3.1 静态注册

即在清单文件manifest中通过声明<receiver>注册。<receiver有很多属性,下面列表说明:

属性名称 作用
android:name="" 类名
android:enabled = [ true / false] 是否可用
android:exported= [ true / false] 此接收器是否接收其它应用广播,包含<intent-filter>标签默认为true
android:icon="" 图标
android:label="" 标签名称
android:permission="" 权限,具有该权限的发送者发送的广播才能被它接收
android:process="" Android四大组件都可以通过这个属性指定独立进程
<intent-filter> 过滤器

<intent-filter>中可以指定<action android:name=""/>用来作发送和接收的标记。

3.2 动态注册

就是在代码中注册BroadcastReceiver,具体步骤举例:

    // 1.实例化自定义广播,IntentFilter
    MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
    IntentFilter intentFilter = new IntentFilter();
    // 2.设置IntentFilter的action
    intentFilter.addAction("Your Action");
    // 3.使用Context的registerReceiver动态注册广播
    registerReceiver(myBroadcastReceiver,intentFilter);
    ...
    // 4.解除注册,防止内存泄漏
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(myBroadcastReceiver);
    }

3.3 两种注册方式的区别

这里借用一张总结的比较好的图来说明:

两种注册方式的区别.png

四. BroadcastReceiver原理

  • Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。

因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展。

  • 模型中有三个角色:
  1. 消息订阅者(BroadcastReceiver)
  2. 消息发布者(广播发布者)
  3. 消息中心(AMS,即Activity Manager Service
BroadcastReceiver原理示意.png
  • 原理描述:
  1. 广播接收者(BroadcastReceiver) 通过Binder机制在AMS注册
  2. 广播发送者 通过Binder机制向AMS发送广播
  3. AMS根据 广播发送者 要求,在已注册列表中,寻找合适的广播接收者

寻找依据:IntentFilter / Permission

  1. AMS将广播发送到合适的广播接收者相应的消息循环队列中;
  2. 广播接收者通过 消息循环 拿到此广播,并回调onReceive()

特别注意:广播发送者 和 广播接收者的执行 是 异步 的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到;

五. BroadcastReceiver回调Context

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

  • 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
  • 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
  • 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;

测试项目地址。
参考资料:

Android四大组件:BroadcastReceiver史上最全面解析
《第一行代码(第二版》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容