Broadcast -- " 品种 "和" 用法 "


BroadcastReceiver 是 Android 系统的四大组件之一,本质上就是一个全局的监听器,用于监听系统全局的广播消息,可以方便的实现系统中不同组件之间的通信。

广播一般有两种注册方式,分别为:" 动态注册 "" 静态注册 ",直接看代码示范!

一、动态注册(跟随 Activity 的生命周期)

新建一个 BroadcastTest 项目,修改 MainActivity :

package com.example.marco.broadcasttest;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;

    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(networkChangeReceiver, intentFilter);    // 注册广播
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);                // 注销广播
    }


    private class NetworkChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "network changes", Toast.LENGTH_LONG).show();
        }
    }
}

动态注册 的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是!!!它也存在一个缺点:必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在 onCreate() 方法中的。那么有没有什么方法可以让程序在未启动的情况下就能接收到广播呢?这就是我们即将看到的 静态注册


二、静态注册(注册完成后一直运行)

(1)新建一个 BroadcastReceiver:

package com.example.marco.broadcasttest;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class BootCompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
    }
}

(2)静态广播接收器一定要在 AndroidManifest.xml 文件中注册才可以使用,如果你使用 Android Studio 快捷方式创建了这个广播接收器,那么系统会自动在 AndroidManifest.xml 中注册好这个广播接收器:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.marco.broadcasttest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"                  // 是否启用这个广播接收器
            android:exported="true"></receiver>     // 是否允许这个广播接收器接收本程序以外的广播
    </application>

</manifest>

(3)此时广播接收器还未能接收到开机广播,需要添加 Action 和 权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.marco.broadcasttest">

    // 添加权限
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            // 添加相应的 Action
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>

</manifest>


三、你知道的广播有几种?

3.1 普通广播(NormalBroadcast)

普通广播是一种完全 异步执行 的广播,在广播发出之后,所有的广播接收器 几乎会同时接收到这条广播消息。此类广播效率较高而且 不能截断

NormalBroadcast.png

发送广播方式如下:

    // 发送端
    Intent intent = new Intent();
    intent.setAction(BROADCAST_ACTION);    // 对应 BroadcastReceiver 中 intentFilter 的 action
    sendBroadcast(intent);                 // 发送广播

    // 接收端
    // 广播接收器静态注册
    <receiver 
        android:name=".MyBroadcastReceiver" >              // 此广播接收者类是 MyBroadcastReceiver
        <intent-filter>
            <action android:name="BROADCAST_ACTION" />
        </intent-filter>
    </receiver>

3.2 有序广播(OrderedBroadcast)

有序广播是一种 同步执行 的广播,广播发出之后,优先级高的广播接收器就可以先接收到广播消息,执行完该广播接收器的逻辑后,可以选择 截断正在传递的广播或者继续传递,如果广播消息被截断,之后的广播接收器则无法收到广播消息。有序广播中的 “ 有序 “是针对广播接收者而言的。有序广播的定义过程与普通广播无异,只是其发送方式变为:sendOrderedBroadcast()

OrderedBroadcast.png

发送广播方式如下:

    // 定义优先级的方式 1
    <receiver 
        android:name=".MyBroadcastReceiver" >
        <!-- priority 优先级:数字越高优先级越高 -->
        <intent-filter android:priority="2">
            <action android:name="BROADCAST_ACTION2" />
        </intent-filter>
        <intent-filter android:priority="1">
            <action android:name="BROADCAST_ACTION1" />
        </intent-filter>
    </receiver>

    // 定义优先级的方式 2
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.setAction(BROADCAST_ACTION2);
    intentFilter.setPriority(2);
    registerReceiver(mBroadcastReceiver, intentFilter);

有序广播可以调用 abortBroadcast() 方法终止广播,一旦终止后面接收者就无法接受广播。

3.3 系统广播(SystemBroadcast)

Android 中内置了多个 系统广播 :只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播。每个广播都有特定的 Intent - Filter(包括具体的 action)。

例如(不完全举例):

系统操作 action
监听网络变化 android.net.conn.CONNECTIVITY_CHANGE
关闭或打开飞行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
充电时或电量发生变化 Intent.ACTION_BATTERY_CHANGED
电池电量低 Intent.ACTION_BATTERY_LOW
系统启动完成后 Intent.ACTION_BOOT_COMPLETED
插入耳机时 Intent.ACTION_HEADSET_PLUG
插入外部储存装置 Intent.ACTION_MEDIA_CHECKING
成功安装 APK Intent.ACTION_PACKAGE_ADDED
成功删除 APK Intent.ACTION_PACKAGE_REMOVED
重启设备 Intent.ACTION_REBOOT
屏幕关闭 Intent.ACTION_SCREEN_OFF
屏幕打开 Intent.ACTION_SCREEN_ON

发送广播方式如下:

当使用系统广播时,只需要在注册广播接收者时定义相关的 action 即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播。

3.4 App应用内广播(LocalBroadcast)

Android 中的广播可以跨进程甚至跨 App 直接通信,且注册是 exported 对于有 intent-filter 的情况下默认值是 true,由此将可能出现安全隐患如下:

1、其他 App 可能会针对性的发出与当前 App intent-filter 相匹配的广播,由此导致当前 App 不断接收到广播并处理;

2、其他 App 可以注册与当前 App 一致的 intent-filter 用于接收广播,获取广播具体信息。

无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:

1、对于同一 App 内部发送和接收广播,将 exported 属性人为设置成 false,使得非本 App 内部发出的此广播不被接收;

2、在广播发送和接收时,都增加上相应的 permission,用于权限验证;

3、发送广播时,指定特定广播接收器所在的包名,具体是通过 intent.setPackage(packageName) 指定在,这样此广播将只会发送到此包中的 App 内与之相匹配的有效广播接收器中。

相比于全局广播(普通广播),App应用内广播优势体现在:

1、安全性更高;

2、更加高效;

发送广播方式如下:

    //registerReceiver(mBroadcastReceiver, intentFilter);
    // 注册应用内广播接收器
    localBroadcastManager = LocalBroadcastManager.getInstance(this);
    localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

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

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

对于 LocalBroadcastManager 方式发送的应用内广播,只能通过 LocalBroadcastManager 动态注册,不能静态注册。

3.5 粘性广播(StickyBroadcast)

在 android 5.0/api 21中 deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经 deprecated。