Android广播机制入门详解

2,711 阅读10分钟

广播机制的介绍

在学习这个之前,我们先了解一些概念。在Android中,Android应用与Android系统或者跟其他的Android应用之间的可以相互的发送广播消息,这跟发布-订阅设计模式相似。 这些广播会在所关注的事件发生时发送。举个例子来说,Android系统会在发生各种系统事件时会发送广播,比如说你发出一条短信、或者打出一个电话、或者你插上充电器、拔下充电器、电量变化等等系统都会发出广播。 这也就是我们所说的“系统广播”,具体的时候我们将在下面进行讲解。 广播机制有一个特点,就是不管接收方是否能接收到数据,也不关心对方如何处理数据。

系统广播

在上面我们也介绍了,由系统发出的广播,我们称之为系统广播。拿什么时候会发出广播?系统会在发生各种系统事件的时候自动发送广播,比如我们上面举得一些例子。 广播的消息体会被封装在Intent对象里面,这很重要,该对象的操作字符串会标识所发生的事件(例如 android.intent.action.AIRPLANE_MODE,用来表示区分你接收到的是什么事件) 还有一点就是你不关注这个类,你将不知道在哪里拿到系统广播发送的数据。

具体的哪些的常量我们可以打开Android SDK中的BROADCAST_ACTIONS.TXT 文件进行查阅。或者打开Intent的源码进行查看。

系统广播各个版本发生的更改

随着系统版本的不断更新和发展,谷歌也对Android的系统广播进行了一些调整。具体更改如下

Android 7.0

Android7.0(API级别24)及更高的版本不发送以下系统广播 ACTION_NEW_PICTURE 广播动作:相机拍摄了一张新图片,图片的入口已添加到相册。 ACTION_NEW_VIDEO 广播动作:摄像机录制了一段新的视频,该视频的入口已添加到相册。

此外,以 Android 7.0 及更高版本为目标平台的应用必须使用 registerReceiver(BroadcastReceiver, IntentFilter) 注册 CONNECTIVITY_ACTION 广播。无法在清单中声明接收器。

Android 8.0

从 Android 8.0(API 级别 26)开始,系统对清单声明的接收器施加了额外的限制。 如果您的应用以 Android 8.0 或更高版本为目标平台,那么对于大多数隐式广播(没有明确针对您的应用的广播),您不能使用清单来声明接收器。 当用户正在使用您的应用时,您仍可使用上下文注册的接收器。

Android 9

从 Android 9(API 级别 28)开始,NETWORK_STATE_CHANGED_ACTION 广播不再接收有关用户位置或个人身份数据的信息。 此外,如果您的应用安装在搭载 Android 9 或更高版本的设备上,则通过 WLAN 接收的系统广播不包含 SSID、BSSID、连接信息或扫描结果。要获取这些信息,请调用 getConnectionInfo()

接收系统广播

我们有两种方式可以接收系统广播,分别是清单声明的接收广播和上下文注册的接收广播。也就是隐式注册和显示注册。 注意:如果你的App的适配版本大于等于API26(Android 8.0)的话,那么,你将不能使用隐式广播的注册方式进行注册。除了一些特殊的广播例外,详情可点击官网链接查看。

隐式注册接收系统广播

接着跟大家演示一下如何注册隐私广播,打开清单文件,添加代码如下即可:

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

动态注册接收系统广播

首先,创建一个类并且继承自BroadcastReceiver接着实现它的onReceive(Context, Intent)函数,下面我们写一个例子来实现动态注册。修改MainActivity代码如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //3、实例化广播类
    MyBroadcastReceiver broadcastReceiver = new MyBroadcastReceiver();
    //3、创建一个IntentFilter,通过IntentFilter过滤出我们想要的数据
    IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    //这边添加的是一个开/关飞行模式系统发出的广播
    //具体开发中,我们可以根据实际需求去注册自己想要的广播
    filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    //1、注册广播
    this.registerReceiver(broadcastReceiver, filter);
}


//2、创建一个广播类
public static class MyBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "MyBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        //4、这边回调,接口数据
        Log.d(TAG, "intent action: " + intent.getAction());
        Log.d(TAG, "uri: " + intent.toUri(Intent.URI_INTENT_SCHEME));


    }
}

上面的代码我们已经创建好了一个广播接收器,现在我们将它运行起来可以看到如下日志,这里面包含了一些信息,我们可以从中找到我们想要的信息。

image.png 接着我们打开手机的飞行模式,紧盯这日志输出框,我们可以看到系统又给我们发出了广播,如下所示:

image.png

一样的,我们可以在接收到系统的某些事件之后,做一些你自己的业务逻辑。

需要注意一点的是注册广播必须在上下文注册,也就是只能在集成自Context的类去注册。 我们当前所在的Activity就是间接继承自Context。 好了,到这边你应该学会了如何去接收系统的广播。

注销系统广播监听

上面所讲的例子存在一个问题,只要你的应用还存活,那么你的应用就会一直接收这个系统广播,那么问题来了。如果你想在特定的事件把广播注销掉,那我们应该怎么做?

要停止接收广播,很简单。只需要调用unregisterReceiver(android.content.BroadcastReceiver)即可。

那么我们现在修改MainActivity代码如下:


private MyBroadcastReceiver mBroadcastReceiver;
...
@Override
protected void onDestroy() {
    super.onDestroy();
    if (mBroadcastReceiver != null) {
        this.unregisterReceiver(mBroadcastReceiver);
    }
}

上面我们做了两个操作 1、在onDestroy()函数去注销广播,对应代码如下

image.png

发现需要传入一个BroadcastReceiver对象 2、将刚才实例化的MyBroadcastReceiver设置成全局变量,因为我们在onDestroy()时需要用到这个对象。设置完全局变量后,修改最终代码如下

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mBroadcastReceiver != null) {
        this.unregisterReceiver(mBroadcastReceiver);
    }
}

注意:需要注意注册和接收广播的位置,比如说,你在Activity的onCreate()函数中去注册广播,那么应该在onDestroy()去注销,以防止广播从Activity泄漏出去。又或者说你在onResmue()函数中去注册,那么你应该在onPause()中去注销。以防止多次注册广播。

自定义广播

上面我们展示介绍了接收系统广播,那么突然有一天你想自己发送一条广播怎么办?很简单,系统为我们提供了发送广播的api,具体如下:

1、sendBroadcast(),普通广播,这种广播会按随机的顺序发送广播。这种广播的效率比较高,但缺点是接收器不能将处理结果传递给下一个接收器,并且无法在中途终止广播。

2、sendOrderedBroadcast(),有序广播,这种广播有一个特点,它可以向下传递结果,也可以中断广播,使比它优先级更低的广播不能接受到广播。我们可以通过intent-filter 的 android:priority 属性来控制;具体的值在-1000到1000之前,按值的大小顺序执行。

3、LocalBroadcastManager.getInstance(context).sendBroadcast(),本地广播,如果你的应用不需要跨应用发送广播,不需要跨进程通信,那么建议使用本地广播。这种实现方法效率更改,而且不用担心其他应用在收发你的广播是带来的任何安全问题

4、sendStickyBroadcast,粘性广播,此方法在 API 级别 21 中已弃用。这种广播不安全,所有人都可以访问它,没有保护,任何人都可以修改它,以及其他的一些问题。建议使用非粘性广播。

使用标准广播

我们修改MainActivity代码如下:

private static final String TAG = "MainActivity";
private MyBroadcastReceiver mReceiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mReceiver = new MyBroadcastReceiver();
    IntentFilter intentFilter = new IntentFilter();
    //对应onSend()函数步骤2的action
    intentFilter.addAction("com.example.broadcast.MY_NOTIFICATION");
    registerReceiver(mReceiver, intentFilter);
}


private static final class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "getAction: " + intent.getAction());
        Log.d(TAG, "data: " + intent.getStringExtra("data"));
    }
}

//这边是点击事件的函数
public void onSend(View view) {
    //1、创建一个Intent对象
    Intent intent = new Intent();
    //2、设置广播的标识,用来匹配接收广播的action。我这边是以包名加广播动作名称为广播标识。大家可以随意,但是并不建议这么做
    intent.setAction("com.example.broadcast.MY_NOTIFICATION");
    //3、使用putExtra附加消息,一这边可以0个,或者多个
    intent.putExtra("data", "Notice me senpai!");
    sendBroadcast(intent);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mReceiver != null) {
        unregisterReceiver(mReceiver);
    }
}

代码中已经写了比较详细的注释了,大家可以动手实践一下,其用法跟我们接收系统广播是差不多的。 只是多了一个我们自己发送广播的操作。

使用有序广播

修改OrderActivity代码如下

private static final String TAG = "OrderActivity";
private MyBroadcastReceiver mMyBroadcastReceiver;
private MyBroadcastReceiver2 mMyBroadcastReceiver2;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_order);
    mMyBroadcastReceiver = new MyBroadcastReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.example.broadcast.test");
    intentFilter.setPriority(100);
    registerReceiver(mMyBroadcastReceiver, intentFilter);


    mMyBroadcastReceiver2 = new MyBroadcastReceiver2();
    IntentFilter intentFilter2 = new IntentFilter();
    intentFilter2.addAction("com.example.broadcast.test");
    intentFilter2.setPriority(500);
    registerReceiver(mMyBroadcastReceiver2, intentFilter2);

}


public void onSend(View view) {
    Intent intent = new Intent();
    intent.setAction("com.example.broadcast.test");
    //发送顺序广播
    sendOrderedBroadcast(intent, null);
}


private static final class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "MyBroadcastReceiver getAction: " + intent.getAction());
    }
}


private static final class MyBroadcastReceiver2 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "MyBroadcastReceiver2 getAction: " + intent.getAction());
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mMyBroadcastReceiver != null) {
        unregisterReceiver(mMyBroadcastReceiver);
    }

    if (mMyBroadcastReceiver2 != null) {
        unregisterReceiver(mMyBroadcastReceiver2);
    }

}

我们这边创建了两个广播的接收器,与之前不同的是我们设置了setPriority()属性,这个属性大小决定了接收广播的顺序,这个值的取值范围为: 最小-1000:

public static final int SYSTEM_LOW_PRIORITY = -1000;

最大1000:

public static final int SYSTEM_HIGH_PRIORITY = 1000;

上面演示了普通的情况,接着我们来看一下把数据传递给下一个广播和终止传递的情况,修改两个广播接收者代码如下:

private static final class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "MyBroadcastReceiver getAction: " + intent.getAction());
        //拿到上一个广播发来的信息,也就是bundle
        Bundle bundle = getResultExtras(true);
        //取出数据
        String test = bundle.getString("test");
        Log.d(TAG, "onReceive: " + test);
    }
}


private static final class MyBroadcastReceiver2 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "MyBroadcastReceiver2 getAction: " + intent.getAction());
        //这边为优先级较高的广播接收者,我们在这边把数据传递给下一个广播
        Bundle bundle = new Bundle();
        bundle.putString("test", "传输数据");
        //传递数据给下一个广播
        setResultExtras(bundle);
    }
}

ok,以上代码我们演示了优先级较高的广播接收者如何传递数据给优先级较低的广播接收者。现在带大家实现一下优先级较高的广播接收者如何阻断广播,不给优先级较低的广播发送消息,修改两个广播接收者代码如下

  private static final class MyBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "MyBroadcastReceiver getAction: " + intent.getAction());
    }


    private static final class MyBroadcastReceiver2 extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "MyBroadcastReceiver2 getAction: " + intent.getAction());
            //这边为优先级较高的广播接收者,我们可以在这里阻断广播向优先级较低的广播传输
            //阻止广播继续往下传输
            abortBroadcast();
        }
    }

好了,上面的代码我们也演示了如何阻断广播向下传输,到这边,有序广播的用法我们已经讲解完毕了。

使用本地广播

修改LocalActivity代码如下:

private static final String TAG = "LocalActivity";

private LocalBroadcastManager mLocalBroadcastManager;
private LocalReceiver mLocalReceiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_local);
    mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.example.broadcast.LOCAL_BROADCAST");
    mLocalReceiver = new LocalReceiver();
    mLocalBroadcastManager.registerReceiver(mLocalReceiver, intentFilter);
}

public void onSend(View view) {
    Intent intent = new Intent();
    intent.setAction("com.example.broadcast.LOCAL_BROADCAST");
    // 发送本地广播
    mLocalBroadcastManager.sendBroadcast(intent); 
}


private static final class LocalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "收到本地广播");
    }
}


@Override
protected void onDestroy() {
    super.onDestroy();
    if (mLocalBroadcastManager != null) {
        mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
    }
}

这边发送本地广播的用法和上面差不多,这边就不过多的解释。这边需要记住一个特性,如果你需要跨应用或者跨组件通信的话,我们还是使用本地广播来进行通信,本地广播的效率会更高。并且使用本地广播不会带来任何的安全问题。本地广播可在您的应用中作为通用的发布/订阅事件总线,而不会产生任何系统级广播开销。

实际开发中经常用到吗?

除了系统广播,其它的广播几乎用不到。系统广播的只要是一些需求需要监听一些系统的状态来处理对应的逻辑。