Android进阶之深入理解BroadcastReceiver

1 概要

BroadcastReceiver组件是一种消息型组件,主要用于在不同组件乃至不同应用之间传递消息。因为它工作在系统内部,无法被用户所感知。

BroadcastReceiver也叫做广播,广播的注册方式有两种:静态注册和动态注册。静态注册指在AndroidManifest中注册广播,这种广播在应用安装时被系统解析,此种形式的广播不需要应用启动就可以接收到相应的广播。动态广播需要通过Context.registerReceiver()来实现,并且在不需要的时候通过Context.unRegisterReceiver()解除广播,此种形态的广播必须要应用启动才能注册并接收广播。在实际开发中通过Context的一系列send方法来发送广播,被发送的广播会被系统(Activity Manager Service)发送给合适的广播接受者,依据是intent-filter/permission。由此发现,BroadcastReceiver可以用来实现低耦合的观察者模式,一般来说不需要停止,也没有停止的概念。由于BroadcastReceiver运行在主线程,所以它不适合用来执行耗时操作。

2 实现原理

2.1 采用模型

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

2.2 模型讲解

模型中有3个角色:消息订阅者(广播接收者)、消息发布者(广播发布者)、消息中心(AMS,即Activity Manager Service),示意&原理图如下:
在这里插入图片描述
图片来源于:Android四大组件:BroadcastReceiver史上最全面解析

2.3 流程

在这里插入图片描述
图片来源于:Android四大组件:BroadcastReceiver史上最全面解析

3 使用流程

3.1 自定义全局广播接收者BroadcastReceiver

// 继承BroadcastReceivre基类
public class mBroadcastReceiver extends BroadcastReceiver {

  // 复写onReceive()方法,接收到广播后,则自动调用该方法
  @Override
  public void onReceive(Context context, Intent intent) {
        // 默认情况下,广播接收器运行在UI线程,因此onReceive()方法不能执行耗时操作,否则将导致ANR.
	    // 写入接收广播后的操作
    }
}

3.2 广播接收器注册

3.2.1 静态注册

(1)属性说明

<receiver 
    android:enabled=["true" | "false"]
	//此broadcastReceiver能否接收其他App的发出的广播
	//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
	//继承BroadcastReceiver子类的类名
    android:name=".mBroadcastReceiver"
	//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
    android:permission="string"
	//BroadcastReceiver运行所处的进程
	//默认为app的进程,可以指定独立的进程
	//注:Android四大基本组件都可以通过此属性指定自己的独立进程
    android:process="string" >

	//用于指定此广播接收器将接收的广播类型
	//本示例中给出的是用于接收网络状态改变时发出的广播
	<intent-filter>
		<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

(2)注册示例

<receiver 
    //此广播接收者类是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用于接收网络状态改变时发出的广播
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
3.2.2 动态注册
// 选择在Activity生命周期方法中的onResume()中注册
@Override
protected void onResume(){
     super.onResume();
     // 1. 实例化BroadcastReceiver子类&IntentFilter
     mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
     IntentFilter intentFilter = new IntentFilter();
     // 2. 设置接收广播的类型
	 intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
     // 3. 动态注册:调用Context的registerReceiver()方法
     registerReceiver(mBroadcastReceiver, intentFilter);
 }

// 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
// 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
@Override
protected void onPause() {
     super.onPause();
     //销毁在onResume()方法中的广播
     unregisterReceiver(mBroadcastReceiver);
}
3.2.3 两种注册方式的区别

在这里插入图片描述
图片来源于:Android四大组件:BroadcastReceiver史上最全面解析

4 广播发送者向AMS发送广播

4.1 广播的发送

广播是用”Intent标识,定义广播的本质 = 定义广播所具备的Intent,广播发送=广播发送者将此广播的Intent通过sendBroadcast()方法发送出去。

4.2 广播的类型

4.2.1 普通广播(Normal Broadcast或无序广播)

即开发者自身定义intent的广播(最常用)。发送广播使用如下:

Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//发送广播
sendBroadcast(intent);
<receiver 
    //此广播接收者类是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用于接收网络状态改变时发出的广播
    <intent-filter>
        <action android:name="BROADCAST_ACTION" />
    </intent-filter>
</receiver>
4.2.2 系统广播(System Broadcast)

Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播。每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:
  具体广播类型:Android常用系统广播
  注:当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播

4.2.3 有序广播(Ordered Broadcast)

(1)定义:发送出去的广播被广播接收者按照先后顺序接收,有序是针对广播接收者而言的。
(2)广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)

  • 按照Priority属性值从大-小排序;
  • Priority属性相同者,动态注册的广播优先;

(3)特点

  • 接收广播按顺序接收;
  • 先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
  • 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播。

(4)具体使用:有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式

sendOrderedBroadcast(intent);
4.2.4 App应用内广播(Local Broadcast)

(1)背景:Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
(2)冲突

  • 其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
  • 其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息,即会出现安全性 & 效率性的问题。

(3)解决方案:使用App应用内广播。App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高&效率高。
(4)具体使用(不能静态注册):使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例。

private String BROADCAST_ACTION = "com....action.balanceChanged";

// 1、注册广播接收器
//(1):实例化BroadcastReceiver子类&IntentFilter mBroadcastReceiver 
BroadcastReceiver broadcastReceiver = new BroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 
//(2):实例化LocalBroadcastManager的实例
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this);
//(3):设置接收广播的类型 
intentFilter.addAction(BROADCAST_ACTION);
//(4):调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册 
localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter);

// 2、发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent)

// 3、取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(broadcastReceiver);


4.2.5 粘性广播(Sticky Broadcast)

由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。

5 避免在onReceive()中执行耗时操作

默认情况下,广播接收器是在主线程执行,因此onReceive()方法不能执行耗时操作,方法耗时超过10秒钟,将导致ANR
  **不能使用子线程来解决 , 因为BroadcastReceiver的生命周期很短 , 子线程可能还没有结束,BroadcastReceiver就先结束了。**BroadcastReceiver一旦结束 , 此时BroadcastReceiver所在进程很容易在系统需要内存时被优先杀死 , 因为它属于空进程 ( 没有任何活动组件的进程 )。如果它的宿主进程被杀死 , 那么正在工作的子线程也会被杀死 . 所以采用子线程来解决是不可靠的。
  可以使用IntentService 、 创 建HandlerThread或者调用Context的registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他Wroker线程执行onReceive的方法。
  Android性能优化之IntentService

6 在Android中用广播来更新UI界面好吗?

(1)BroadcastReceiver有生命周期,但很短。当它的onReceive()方法执行完成后,它的生命周期也就随之结束了。这时候由于BroadcastReceiver已经不处于active状态,所以极有可能被系统干掉。也就是说如果在onReceive()去开线程进行异步操作或者打开Dialog都有可能在没达到你要的结果时,进程就被系统杀掉了
(2)实际开发中其实大多数情况都是可以采用BroadcastReceiver来更新UI。
(3)对于超过10秒更新UI,不推荐这种方式。因为:Receiver也是运行在主线程的,不能做耗时操作。虽然超时时间有10秒,但不意味着所有的更新UI界面操作时间都在安全范围之内。
(4)对于频繁更新UI,也不推荐这种方式。因为:Android 广播的发送和接收都包含了一定的代价,它的传输都是通过Binder进程间通信机制来实现的,那么系统会为了广播能顺利传递而做一些进程间通信的准备。而且可能会由于其它因素导致广播发送和到达延迟。
(5)学习链接
用广播 BroadcastReceiver 更新 UI 界面真的好吗?

7 注意

(1)动态广播最好在Activity的onResume()注册、onPause()注销?
  因为onPause()在Activity销毁前一定会被执行(内存不足要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁),从而保证广播在Activity销毁前一定会被注销,从而防止内存泄露。但是在onPause()必须判断当前Activity是否已经销毁,销毁了才注销
(2)一个app被杀掉进程后,是否还能收到广播?
  静态注册的常驻型广播接收器还能接收广播,所以本地广播不能用静态注册。
(3)对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

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

8 转载链接&参考链接

Android四大组件:BroadcastReceiver史上最全面解析

Android:检测网络状态&监听网络变化

9 项目应用例子

(1)定义广播接收器

/**
 * 刷新activity的广播接收器
 */
public class RefreshReadActReceiver extends BroadcastReceiver {

    private static final String ACTION_FONT_CHANGED = "com...action.fontChanged";                 // 字体改变
    private static final String ACTION_LOGIN_STATUS_CHANGED = "com...action.loginStatusChanged";  // 登录状态改变

    private final RefreshObserver mObserver;

    public static IntentFilter createIntentFilter() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_FONT_CHANGED);
        intentFilter.addAction(ACTION_LOGIN_STATUS_CHANGED);
        return intentFilter;
    }

    public RefreshReadActReceiver(RefreshObserver observer) {
        this.mObserver = observer;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null) {
            return;
        }

        String action = intent.getAction();
        if (ACTION_FONT_CHANGED.equals(action)) {
            mObserver.fontChanged();
        } else if (ACTION_LOGIN_STATUS_CHANGED.equals(action)) {
            mObserver.loginStatusChanged();
        }
    }

    public interface RefreshObserver {
        void fontChanged();

        void loginStatusChanged();
    }
}

(2)注册广播&逻辑处理

public class GroupActivity extends AbstractMvpActivity {

    private static final String TAG = "GroupActivity";

    private LocalBroadcastManager mLocalBroadcastManager;
    private RefreshReadActReceiver mRefreshReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        registerReceiver();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
     
        unRegisterReceiver();
    }

    /**
     * 获取布局ID,在子类重写该方法
     */
    @Override
    protected int getLayoutId() {
        return R.layout.activity_group;
    }

    /**
     * 注册登录成功回调广播
     */
    protected void registerReceiver() {
        mRefreshReceiver = new RefreshReadActReceiver(new RefreshReadActReceiver.RefreshObserver() {
            @Override
            public void fontChanged() {

            }

            @Override
            public void loginStatusChanged() {
                // 刷新详情
                // getGroupData();
            }
        });
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
        mLocalBroadcastManager.registerReceiver(mRefreshReceiver, RefreshReadActReceiver.createIntentFilter());
    }

    /**
     * 反注册广播
     */
    protected void unRegisterReceiver() {
        if (mLocalBroadcastManager == null || mRefreshReceiver == null) {
            return;
        }

        try {
            mLocalBroadcastManager.unregisterReceiver(mRefreshReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }

        mRefreshReceiver = null;
        mLocalBroadcastManager = null;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值