Android 四大组件之 BroadcastReceiver

来源:互联网 发布:游戏算法书籍 编辑:程序博客网 时间:2024/06/05 21:58

       广播接受器( BroadcastReceiver)是 Android 的四大组件之一,Android 中的广播机制是非常灵活的,Android 中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接受自己关心的广播内容,这些广播可能是来自系统的,也可能是来自其他程序的,Android 提供了一套完整的 API,允许应用程序间自由地接受和发送广播,今天我们来一起总结 Broadcast Receiver 的基本用法,我们先来列个大纲,让大家一目了然今天分享的内容




一、广播的机制简介


       在 Android 系统中,广播体现在方方面面,是 Android 的四大组件之一,是一个全局的监听器,用于监听和接受应用发出的广播消息,并作出相应的响应,在不同组件之间通信,系统特定情况下的通信,例如当开机完成后系统会产生一条广播,接受到这条广播就能实现开机启动服务的功能、当网络状态改变时系统会产生一条广播,接收到这条广播就能及时地做出提示以及保存需要保存的数据等操作、当电池电量发生改变时,系统也会发出一条广播等等这些应用场景都有使用

广播主要的两种类型:标准广播 和 有序广播

       标准广播(Normal Broadcasts):是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎同时在统一时刻接受到这条广播消息,因此他们之间没有任何先后顺序而言,这种广播的效率比较高,但同时也意味着它是无法被截断的,标准广播的工作流程如下图:



接下来我们通过代码来验证一下,这里我们新建一个 BroadcastReceiver 项目来演示:


1)首先我们创建 MyReceiver 类并继承 BroadcastReceiver


public class MyReceiver extends BroadcastReceiver {    public MyReceiver() {    }    @Override    public void onReceive(Context context, Intent intent) {        throw new UnsupportedOperationException("Not yet implemented");    }}

        我们可以看到 MyReceiver 继承自 BroadcastReceiver,说明这是一个广播,广播里基本什么也没有,只有一个空的构造方法,和一个 onReceive() 方法,这个 onReceiver() 方法就是负责在接收到广播以后根据 Intent 携带来的数据来处理相关的逻辑的,创建完 MyReceiver 之后我们还没有能够使广播进入工作状态,我们还需要注册一个指定的广播地址,注册广播有两种方式:在代码中注册和在 AndroidManifest.xml 中注册,其中前者被称为动态注册,后者被称为静态注册

静态注册

静态注册就是在 AndroidManifest.xml 中配置的,如下:

        <receiver            android:name=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>

       配置了以上信息后,只要是 android.intent.action.MY_BROADCAST 这个地址的广播,MyReceiver 就都能接收得到,还需要注意的就是这种注册的方式是常驻型的,比较浪费内存

动态注册

MyReceiver receiver = new MyReceiver();            IntentFilter filter = new IntentFilter();  filter.addAction("android.intent.action.MY_BROADCAST");            registerReceiver(receiver, filter);
       动态注册需要在代码中动态的进行相关配置,上面就是我们的注册示范代码,registerReceiver 是 android.content.ContextWrapper 类中的方法,其实 Activity 和 Service 都继承了 ContextWrapper,所以可以直接调用在实际应用中,还有需要注意的就是,在 Activity 和 Service 中注册一个 BroadCastReceiver,当 Activity 和 Service 销毁的时候要记得在 onDestroy() 方法中解除

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

好了 说完了注册的两种常用注册类型,接下来我们继续来验证我们的标准广播的接受情况:

刚才我们已经创建好了自己的 MyReceiver 广播接收器,并且进行了注册,现在我们来发送一条广播,看会发生什么:

    public void sendMessage(View view) {        Intent intent = new Intent("android.intent.action.MY_BROADCAST");        intent.putExtra("msg", "hello receiver.");        sendBroadcast(intent);    }

       这里我们发送了一条广播,注意 sendBroadcast 也是 ContextWrapper 类中的方法,它可以将一个指定地址和参数信息的 Intent 对象以广播的形式发送出去,这里我们点击发送按钮,,看日志的打印情况:


      这里我们看到我们成功的接收到了广播,这里我们只是演示了一个接受者来接受广播,那么如果是多个会怎么样那,接下来我们来新建 3 个广播接受者,看看会是什么情况:

这里我们创建 First、Second、Third 三个接收器来接收广播如下:

public class First extends BroadcastReceiver {    private static final String TAG = "First";    public First() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = intent.getStringExtra("msg");        Log.i(TAG, "onReceive: " + msg);    }}

public class Second extends BroadcastReceiver {    private static final String TAG = "Second";    public Second() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = intent.getStringExtra("msg");        Log.i(TAG, "onReceive: " + msg);    }}

public class Third extends BroadcastReceiver {    private static final String TAG = "Third";    public Third() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = intent.getStringExtra("msg");        Log.i(TAG, "onReceive: "+msg);    }}

接下来大家别忘了注册,这里我就先采用在 AndroidManifest.xml 中进行注册了:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.qiudengjiao.broadcastreceiver">    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        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=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>        <receiver            android:name=".First"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>        <receiver            android:name=".Second"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>        <receiver            android:name=".Third"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>    </application></manifest>

这时候点击刚才发送广播的按钮,来看日志的打印情况:



       这里我们看到我们注册的接受者都接收到了发出的广播,接下来大家注意了,我们在 onReceive() 方法中添加 abortBroadcast() 来看是否能截断广播,如下,这里我们只贴出 First 中的代码,其他的也类似:

public class First extends BroadcastReceiver {    private static final String TAG = "First";    public First() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = intent.getStringExtra("msg");        Log.i(TAG, "onReceive: " + msg);        abortBroadcast();    }}

这时候我们再来点击发送按钮看日志打印情况:



       日志我们截取了部分,可以看到我们仍然接收到了广播,并没有截断我们所发出的广播,大家都接收到了发出的广播,这说明保准广播是无法拦截的,报的错误告诉我们,在无序广播中使用拦截是错误的,当然我们这里只是演示,到这里我们就把标准广播分析完了,接下来我们来分析有序广播


       有序广播(Ordered Broadcast):是一种同步执行的广播,在广播发出之后同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的接收器就无法收到广播消息了,有序广播的工作流程如下图:


我们来修改一下 3 个接收者的代码:

public class First extends BroadcastReceiver {    private static final String TAG = "First";    public First() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = intent.getStringExtra("msg");        Log.i(TAG, "onReceive: " + msg);        Bundle bundle = new Bundle();        bundle.putString("msg", msg + "...First...");        setResultExtras(bundle);    }}

public class Second extends BroadcastReceiver {    private static final String TAG = "Second";    public Second() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = getResultExtras(true).getString("msg");        Log.i(TAG, "onReceive: " + msg);        Bundle bundle = new Bundle();        bundle.putString("msg", msg + "...Second...");        setResultExtras(bundle);    }}

public class Third extends BroadcastReceiver {    private static final String TAG = "Third";    public Third() {    }    @Override    public void onReceive(Context context, Intent intent) {        String msg = getResultExtras(true).getString("msg");        Log.i(TAG, "onReceive: " + msg);        Bundle bundle = new Bundle();        bundle.putString("msg", msg + "...Third...");        setResultExtras(bundle);    }}

       这里我们为了标记顺序,在 First 和 Second 中都使用了setResultExtras 方法将一个 Bundle 对象传递到下一个接受者哪里,这样以来优先级低的可以用 getResultExtras() 方法获取到最新处理过的信息集合,这里的代码完成后我们需要在 AndoridMainfest.xml 文件中修改广播的优先级:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.qiudengjiao.broadcastreceiver">    <uses-permission android:name="scott.permission.MY_BROADCAST_PERMISSION" />    <permission        android:name="scott.permission.MY_BROADCAST_PERMISSION"        android:protectionLevel="normal" />    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        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=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter android:priority="997">                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>        <receiver            android:name=".First"            android:enabled="true"            android:exported="true">            <intent-filter android:priority="1000">                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>        <receiver            android:name=".Second"            android:enabled="true"            android:exported="true">            <intent-filter android:priority="999">                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>        <receiver            android:name=".Third"            android:enabled="true"            android:exported="true">            <intent-filter android:priority="998">                <action android:name="android.intent.action.MY_BROADCAST" />                <category android:name="android.intent.category.DEFAULT" />            </intent-filter>        </receiver>    </application></manifest>

       这里我们看到我们在广播接受者的 <intent-filter> 标签里增加了 android:priority="" 这个属性,这个属性的使用范围在 -1000--1000 之间,数值越大优先级越高,接下来我们来修改一下发送广播的代码:

    public void sendMessage(View view) {        Intent intent = new Intent("android.intent.action.MY_BROADCAST");        intent.putExtra("msg", "hello receiver.");        sendOrderedBroadcast(intent,"scott.permission.MY_BROADCAST_PERMISSION");    }

       这里需要注意的是,使用 setOrderedBroadcast() 方法发送有序广播时,需要一个权限参数,如果为 null 则表示不要求接受者声明指定的权限,如果不为 null,则表示接受者若要接受此广播,需要声明指定权限,这是从安全角度来考虑的,例如系统的短信就是有序广播的形式,一个应用可能是具有拦截短信的功能,当短信到来时可以先接收到短信广播,必要时终止广播传递,这样的软件就必须声明接收短信的权限,所以我们在 AndroidMainfest.xml 中定义一个权限:

    <permission        android:name="scott.permission.MY_BROADCAST_PERMISSION"        android:protectionLevel="normal" />

然后声明使用此权限:

    <uses-permission android:name="scott.permission.MY_BROADCAST_PERMISSION" />


然后我们点击按钮发送一条广播,看控制台的打印情况:


       我们看到是按照优先升级的顺序打印的,从优先级搞得到优先级低的,既然是顺序传递,这里我们就能来截断,这次我们在 First 的广播中的 onReceive() 方法中加入如下代码:

        abortBroadcast();

点击发送广播按钮,我们来看看打印情况:


这里看到我们成功拦截了继续向下传递的广播,只有第一个接受到了我们的广播

上面这些基本的概念掌握以后,就下来我们就来看一下广播的其他用法吧,我们从系统广播开始


二、系统广播


       Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息,比如向我们文章开头说的,手机开机完成后会发出一条广播,电池的电量发生变化时会发出一条广播,时间和时区发生变化时也会发出一条广播,等等,接下来我们就来看一下它的具体用法结合我们上面学到的广播基本知识


1)动态注册监听网络变化


现在我们通过一个完整的动态注册广播的例子来巩固我们刚才学到的广播的基本使用,这时我们来修改 MainActivity 中的代码如下:

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private IntentFilter intentFilter;    private NetWorkChangeReceiver netWorkChangeReceiver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //创建 IntentFilter 实例        intentFilter = new IntentFilter();        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");        //创建 NetWorkChangeReceiver 实例        netWorkChangeReceiver = new NetWorkChangeReceiver();        //调用registerReceiver()方法进行注册        registerReceiver(netWorkChangeReceiver, intentFilter);    }    /**     * 内部类 NetWorkChangeReceiver     */    class NetWorkChangeReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            //此处写逻辑            Toast.makeText(context, "网路发生变化", Toast.LENGTH_LONG).show();        }    }    /**     * 切记要在此进行取消注册     */    @Override    protected void onDestroy() {        super.onDestroy();        //取消注册        unregisterReceiver(netWorkChangeReceiver);    }}

       可以看到我们在 MainActivity 中定义了一个内部类 NetworkChangeReceiver,并继承自 BroadcastReceiver,重写了 onReceive() 方法,这样当网络状态发生变化时,我们提示了一个 Toast 文本信息,然后我们在 onCreate() 方法中我们创建了 IntentFilter 实例,并且添加了一个值为 android.net.conn.CONNECTIVITY_CHANGE 的 action,添加这个主要是因为,当网络发生变化时,系统发出的正是一条为 android.net.conn.CONNECTIVITY_CHANGE 的广播,也就是说我们的广播接收器想要监听什么,就得在这里添加相应的 action,接下来我们创建 NetworkChangeReceiver 的实例,然后调用 registerReceiver() 方法进行注册,将 NetworkChangeReceiver 和 IntentFilter 的实例都传进去,这样 NetworkChangeReceiver 就会收到 android.net.conn.CONNECTIVITY_CHANGE 的广播,也就实现了监听网络的功能,最后一定要记得在 onDestroy() 中通过 unregisterReceiver() 方法进行取消注册

这时候我们把程序运行起来,并按下 Home 键,注意不能按 back 键,那样程序会被杀死,接着我们来关闭打开网络设置,会看到,不停地弹出网络设置变化,到这里我们一个整体的动态网络注册监听网络变化的系统广播过程就完了


2)静态注册实现开机启动

       上面实现了完整的动态注册过程演示,动态注册的广播接收器可以自由的控制注册与注销,在灵活性方面有很大的优势,但是也有它的缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在 onCreate() 方法中的,那么动态注册就能实现在程序未启动的情况下接收到广播,接下来我们来进行一个完整的静态注册完整的演示:

我们新创建一个广播 BootCompeteReceiver 在创建界面的过程中,Exported 属性表示是否允许这个广播接收器接受本程序以外的广播,Enabled 表示是否启用这个广播接收器,完成创建如下:

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

       代码非常简单,我们只是在 onReceive() 方法中提示一段信息,由于 Android Studio 的智能性 在 AndroidMainfest.xml 中的注册也被自动的创建好了:

        <receiver            android:name=".BootCompleteReceiver"            android:enabled="true"            android:exported="true">        </receiver>

        enabled 和 exported 则是我们刚才提到的属性,不过到这里,广播接收器还是不能接收开机广播的,我们还需要对 AndroidMainfest.xml 文件进行修改,如下:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.qiudengjiao.broadcastreceiver">    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>        <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        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">            <intent-filter>                <action android:name="android.intent.action.BOOT_COMPLETED"/>                            </intent-filter>        </receiver>    </application></manifest>

       由于 Android 系统启动后会发出一条值为 android.intent.action.BOOT_COMPLETED 的广播,因此我们在 <intent-filter> 标签里添加了相应的 action,另外,监听系统开机广播也是需要声明系统权限,可以看到我们也进行了相应权限的声明,接下来我们从新运行程序,启动完成后就能接收到开机广播了,到这里我们就把整个静态注册走了一遍,最后在真正的项目使用过程中,我们需要特别注意,不要在 onReceive() 方法中进行耗时的操作,因为广播接收器中是不允许开启线程的,当 onReceive() 方法运行了较长时间而没有结束时,程序就会报错,因此广播接收器扮演的更多的是一种打开程序其他组件的角色,比如创建一条通知栏,启动一个服务等


三、自定义广播


上面我们介绍了接收来自系统的广播,接下来我们来看看如何在应用程序中发送自定义广播,这里我们需改 MyReceiver 中的代码如下:

public class MyReceiver extends BroadcastReceiver {    private static final String TAG = "MyReceiver";    public MyReceiver() {    }    @Override    public void onReceive(Context context, Intent intent) {        Toast.makeText(context, "收到广播", Toast.LENGTH_LONG).show();    }}

MyReceiver 接收到自定义广播时会弹出传过来的数据,然后在 AndroidMainfest.xml 文件中对这个广播接收器进行修改:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.qiudengjiao.broadcastreceiver">    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        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=".MyReceiver"            android:enabled="true"            android:exported="true">            <intent-filter>                <action android:name="com.example.broadcasttest.MY_BROADCAST" />            </intent-filter>        </receiver>    </application></manifest>

       可以看到我们让广播接收器接受一条 com.example.broadcasttest.MY_BROADCAST 的广播,因此待会儿发送广播的时候我们就需要发送对应的广播如下:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void sendMessage(View view){        Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");        sendBroadcast(intent);    }}

可以看到我们在点击按钮时加入了发送自定义广播的逻辑:

接下来我们点击按钮来看发送情况:




这里我们就成功实现了一个自定义的标准广播


四、本地广播


       前面我们发送和接收的广播全部属于系统全局广播,即发出的广播可以被其他任何应用程序接收到,并且我们也可以接收其他应用程序的广播,这样就很容易引起安全问题,例如我们发送一些携带关键数据的广播,有可能被其他应用程序截获,或者其他应用程序不停地向我们的应用程序的广播接收器里发送各种垃圾广播,为了能够简单的解决广播的安全问题,Android 引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全问题就解决了

       本地广播并不复杂,主要是使用了一个 LocalBroadcastManager 来对广播进行管理,并提供了发送广播和注册广播接收器的方法,下面我们通过实例来进行讲解

修改 MainActivity 中的代码如下:

public class MainActivity extends AppCompatActivity {    private IntentFilter intentFilter;    private LocalReceiver localReceiver;    private LocalBroadcastManager localBroadcastManager;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //获取LocalBroadcastManager实例        localBroadcastManager = LocalBroadcastManager.getInstance(this);        //创建IntentFilter实例        intentFilter = new IntentFilter();        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");        localReceiver = new LocalReceiver();        //注册本地广播监听器        localBroadcastManager.registerReceiver(localReceiver, intentFilter);    }    public void sendMessage(View view) {        Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");        //发送本地广播        localBroadcastManager.sendBroadcast(intent);    }    class LocalReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            Toast.makeText(context, "收到本地广播", Toast.LENGTH_LONG).show();        }    }    @Override    protected void onDestroy() {        super.onDestroy();        //取消注册        localBroadcastManager.unregisterReceiver(localReceiver);    }}

       看到上面的代码我们应该很熟悉了,和我们之前学到的动态注册是基本一样的,只不过这里通过 LocalBroadcastManager 的 getInstance() 方法得到了它的一个实例,然后在注册广播接收器的时候调用的是 LocalBroadcastManager 的 registerReceiver() 方法,同样在发送广播的时候调用的是 LocalBroadcastManager 的 sendBroadcast() 方法,仅此而已,我们来点击发送按钮看看我们是否接收到了这条广播:




我们看到成功的接收到了发出的广播,接下来我们来看使用本地广播的几点优势:

1.可以明确的知道发送的广播不会离开我们的程序,因此不必担心数据泄露问题

2.其他的程序无法将广播发送到我们的程序内部,因此不必要担心会有安全漏洞的隐患

3.发送本地广播比发送全局广播更加的高效


到这里几天今天我们分享的内容就基本完了,这里我们基本就把常用的广播都介绍了,如有错误请指出

参考:郭神第一行代码