Android四大组件之BroadcastReceiver

来源:互联网 发布:安德罗妮淘宝 编辑:程序博客网 时间:2024/06/04 20:06

1. 概述

BroadcastReceiver,中文翻译为“广播接收器“,要了解广播接收器的定义,先介绍一下Broadcast(广播)的含义:

  • Broadcast:广播
    它是实现不同(应用)程序或者是同一个应用程序内部进行消息传递的一种机制,它的功能定义与Intent非常相似,因为它就是通过Intent对象来装载信息的,底层用Binder实现,可以看作是IPC方式的一种。

  • BroadcastReceiver:广播接收器
    它可以对发送的广播进行过滤接受并响应的一类组件,可以用来接收(Android操作)系统的广播或应用的广播。它就像一个系统层面的全局监听器,专门负责监听各个程序发出的广播,它可以在所注册的应用未开启的情况下,依然会继续运行。

参考场景:

电台:发送广播 –> 收音机:可以切换到不同频率接收广播。

2. 应用场景

系统广播:

  1. 手机开机后会发送一条广播,在应用中可以接收该广播来启动应用。
  2. 网络状态变化时,系统会发送一条广播,应用接收该广播后,对当前的数据做保存。
  3. 当电池电池过低时,系统会发送一条广播,可以接收该广播告诉用户当前电量。
  4. 接收手机短信时,系统会以广播形式发送,应用可以截取该短信。

应用广播

  1. 在一个活动和一个启动服务之间,可以间接的通过广播来实现信息的交互,解决启动服务不能向活动返回信息的问题。
  2. 如定义中所描述的,可以利用广播全局性的特点实现信息传递,具体根据实际的情况来使用。

3. 使用方式

  • 发送广播

    1. 创建一个(隐式)Intent对象,通过设置该对象属性来装载信息(属性:action、category、data、extra),如:
      Intent intent = new Intent(“com.example.broadcastreceiverdemo.MY_BROADCAST”);

    2. 通过调用相应的方法将Intent对象以广播形式发送出去,如:
      Context.sendBroadcast(Intent intent);
      Context.sendOrderBroadcast(Intent intent);

  • 接收广播

    1. 创建一个广播接收器:与自定义其它组件(如Activity、Service)类似,你需要继承BroadcastReceiver类,并复写onReceive()方法。

    2. 注册广播接收器:分为静态注册和动态注册,

      静态注册:在AndroidManifest.xml中appplication 标签下添加 receiver 子标签,通过android:name来指定接收器类名,并在该标签下添加 intent-filter 标签来设置intent过滤条件;

      动态注册:在代码中通过Context.registerReceiver(BroadcastReceiver receiver,IntentFilter filter)方法来注册,通过Context.unregisterReceiver(BroadcastReceiver receiver)来注销。

      动态注册优点:根据应用的生命周期来决定接收器是否运行,可以降低系统不必要的消耗,如在Activity.onResume()的实现里注册接收者的话,你应该在Activity.onPause()里反注册它,在paused状态时你不会接收到intent。

    3. 接收广播原理:当通过sendBroadcast(Intent intent)发送Intent对象后,系统中所有已注册的BroadcastReceiver都会检查注册时的IntentFilter是否与发送的Intent相匹配,若匹配,则会调用BroadcastReceiver的onReceive()方法。

4. 代码示例

1.新建一个项目为broadcastreceiverdemo,然后创建一个广播接收器MyBroadcastReceiver,代码如下:

package com.example.broadcastreceiverdemo;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.widget.Toast;public class MyBroadcastReceiver extends BroadcastReceiver {    @Override   public void onReceive(Context context, Intent intent) {        Toast.makeText(context,"Received a broadcast in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();    }}

2.(静态)注册广播接收器,AndroidManifest.xml代码如下:

 <application android:allowBackup="true" android:icon="@mipmap/ic_launcher"   ...        <receiver android:name=".MyBroadcastReceiver">            <intent-filter>                <action android:name="com.example.broadcastreceiverdemo.MY_BROADCAST"/>            </intent-filter>        </receiver>    </application>

3.发送广播。
activity_main.xml中添加一个按钮用来发送广播,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <Button        android:id="@+id/send_broadcast"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Send Broadcast" /></LinearLayout>

在MainActivity中为按钮注册点击事件,发送一个广播,代码示例如下:

package com.example.broadcastreceiverdemo;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Button;public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button mBtnSendBc = (Button) findViewById(R.id.send_broadcast);        mBtnSendBc.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //创建Intent对象并通过构造函数或者setAction()指定action的值                Intent intent = new Intent("com.example.broadcastreceiverdemo.MY_BROADCAST");                /*Intent intent = new Intent();                intent.setAction("com.example.broadcastreceiverdemo.MY_BROADCAST");*/                sendBroadcast(intent);            }        });    }}

运行程序,点击Send Broadcast按钮,结果如下:
这里写图片描述

不同应用间的广播传递

上面的代码示例是在一个应用中通过广播机制来实现消息的传递,下面测试一下不同应用下广播的传递,首先我们再创建一个新的项目为”broadcastreveiverdemo2“,并在该项目中创建一个广播接收器为”MyBroadcastReceiver2“,代码如下:

public class MyBroadcastReceiver2 extends BroadcastReceiver {    @Override   public void onReceive(Context context, Intent intent) {        Toast.makeText(context,"Received a broadcast in MyBroadcastReceiver2",Toast.LENGTH_SHORT).show();    }}

注意:Toast通知中信息是”Received a broadcast in MyBroadcastReceiver2“。
在AndroidManifest.xml中注册该广播接收器,代码如下:

  <application android:allowBackup="true" android:icon="@mipmap/ic_launcher"   ...        <receiver android:name=".MyBroadcastReceiver2">            <intent-filter>                <action android:name="com.example.broadcastreceiverdemo.MY_BROADCAST"/>            </intent-filter>        </receiver>    </application>

intent-filter中action值同样为”com.example.broadcastreceiverdemo.MY_BROADCAST“,然后运行该程序,程序安装成功以后,在手机或模拟器中关闭该应用进程。
注意:直接退出应用是不会关闭应用进程的(系统为了下次更快进入程序),可以在退出应用的前提下,再去Screen Overview来关闭最新打开的应用进程。
然后,再次进入第一次安装的应用”broadcastreveiverdemo“中,点击”Send Broadcast“按钮,界面中会先后弹出”Received a broadcast in MyBroadcastReceiver“和”Received a broadcast in MyBroadcastReceiver2“,截图如下:
这里写图片描述这里写图片描述

因为Toast通知属于系统功能,所以应用”broadcastreveiverdemo2“显示的通知在应用”broadcastreveiverdemo“界面中也可以看到,这也说明应用”broadcastreveiverdemo2“也收到了广播,验证了(静态注册的)广播接收器的运行并不依赖当前应用的进程是否在运行,只要手机处于开机状态,那么该广播接收器就会一直接收并过滤广播信息。
那么如果实际应用中并不需要这样,比如同一个应用不同组件之间,如Activity或Service只有在存活期间才需要接收广播并响应,怎么处理,这就需要动态注册,动态注册的方式在上面”3.使用方式“第二条有介绍,这里把项目”broadcastreveiverdemo2“中接收器”MyBroadcastReceiver2“改为动态注册,首先删除AndroidManifest.xml中注册代码,然后在MainActivity中添加动态注册的代码,示例如下:

public class MainActivity extends Activity {    private MyBroadcastReceiver2 mReceiver;    private IntentFilter mFilter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //创建广播接收器对象        mReceiver = new MyBroadcastReceiver2();        //创建IntentFilter对象,并通过构造函数指定action为com.exampe.broadcastreceiverdemo.MY_BROADCAST        mFilter = new IntentFilter("com.example.broadcastreceiverdemo.MY_BROADCAST");        //通过Context.registerReceiver(BroadcastReceiver receiver,IntentFilter filter)来注册接收器        registerReceiver(mReceiver, mFilter);    }    @Override    protected void onDestroy() {        super.onDestroy();        //通过Context.unregisterReceiver(BroadcastReceiver receiver)来注销广播接收器        unregisterReceiver(mReceiver);    }}

重新运行程序,打开应用”broadcastreveiverdemo2“,按Home键回到桌面,然后打开应用”broadcastreveiverdemo“,点击Send Broadcast按钮,界面中会先后弹出”Received a broadcast in MyBroadcastReceiver“和”Received a broadcast in MyBroadcastReceiver2“;而如果再回到应用”broadcastreveiverdemo2“,并通过Back键退出应用,然后再打开应用”broadcastreveiverdemo“,点击Send Broadcast按钮,界面中只会弹出”Received a broadcast in MyBroadcastReceiver“,因为应用”broadcastreveiverdemo2“关闭时,在onDestroy()中注销了接收器,所以它无法运行并接收广播。

系统广播
开机自动运行的service,代码示例:

public class LaunchReceiver extends BroadcastReceiver{    @Override    public void onReceive(Context context, Intent intent) {        Intent mIntent = new Intent(context,LaunchService.class);        context.startService(mIntent);    }}

在Androidmanifest.xml中注册代码如下:

        <receiver android:name=".MyBroadcastReceiver2">            <intent-filter>                <action android:name="android.intent.action.BOOT_COMPLETED"/>            </intent-filter>        </receiver>

手机开机后,将发出一个广播,它所传递Intent对象的action值为”android.intent.action.BOOT_COMPLETED“。
至于LaunchService可以是用户任意开发的Service,既可以是监听用户来电的Service,也可以是监听用户短信,拦截黑名单的电话等Service,此处不再给出Service的代码。

5、广播类型

  • Normal Broadcast(普通广播)

    1. 广播传递是异步的,可以在同一时刻(逻辑上)被所有接收器接收,消息的传递比较高效。
    2. 接收器不能将处理结果传递下一个接收器,并且无法使用abortBroadcast()来终止广播传递。
      截取第一行代码
      备注:通过Context.sendBroadcast(Intent intent)来发送的广播就是普通广播。
  • Ordered Broadcast(有序广播)

    1. 系统根据广播接收器声明的优先级高低(静态注册:在AndroidManifest.xml中通过标签intent-filter的android:priority属性或动态注册:通过IntentFilter的对象的setPriority()来设置,值为-1000到1000),从高到低依次传递。
    2. 优先接收到广播的接收器可以通过abortBroadcast()来终止广播的传递,这样后面的广播接收器将无法接收到该广播;另外可以通过setResultExtras(Bundle bundle)来将处理结果存入广播,下一个广播接收器可以通过Bundle getResultExtras(true)来获取上一个接收器存入的数据。
      截取第一行代码
      备注:通过Context. sendOrderedBroadcast (Intent intent, String receiverPermission)来发送的广播就是有序广播。

下面就通过代码来逐一验证有序广播的特性,首先是按优先级高低,从高到低发送,利用上面的代码,首先修改项目”broadcastreceiverdemo“中AndroidManifest.xml文件下MyBroadcastReceiver接收器的intent-fliter标签,通过android:priority设置广播接收器的优先级为100,代码示例如下:

      <receiver android:name=".MyBroadcastReceiver" >            <intent-filter android:priority="100">                <action android:name="com.example.broadcastreceiverdemo.MY_BROADCAST"/>            </intent-filter>        </receiver>

然后把MainActivity中发送的广播的语法该为”sendOrderBroadcast()“,代码示例如下:

                Intent intent = new Intent("com.example.broadcastreceiverdemo.MY_BROADCAST");                /*                 * 第二参数为权限描述,为一个字符串类型,即指应用如果要接收该广播,                 * 必须在AndroidManifest.xml中通过use-permission标签来声明,这里为null.                 */                sendOrderedBroadcast(intent,null);

最后进入项目”broadcastreceiverdemo2“,在ManiActivity文件中通过IntentFilter.setPriority()来设置接收器的优先级为200,代码示例如下:

        mReceiver = new MyBroadcastReceiver2();        mFilter = new IntentFilter("com.example.broadcastreceiverdemo.MY_BROADCAST");        mFilter.setPriority(200);        registerReceiver(mReceiver, mFilter);

然后按照上面的操作步骤来检查点击”Send Broadcast“按钮后,是否先弹出”Received a broadcast in MyBroadcastReceiver2“,然后在弹出”Received a broadcast in MyBroadcastReceiver“,这部分截图就不贴上来了,因为不直观,也可以通过打印日志的方式来查看。

接着要测试有序广播是否可以终止广播的传递,只需要在项目”broadcastreceiverdemo2“中MyBroadcastReceiver2.class文件下onReceive()方法中添加一行abortBroadcast(),代码示例如下:

public class MyBroadcastReceiver2 extends BroadcastReceiver {    @Override   public void onReceive(Context context, Intent intent) {        Toast.makeText(context,"Received a broadcast in MyBroadcastReceiver2",Toast.LENGTH_SHORT).show();        abortBroadcast();    }}

继续按照上面的操作,检查页面是否只弹出”Received a broadcast in MyBroadcastReceiver2“信息。

最近通过setResultExtras(Bundle)和getResultExtras()来验证先接收到广播的接收器是否可以添加数据并传递下一个接收器,修改项目”broadcastreveiverdemo2“中MyBroadcastReceiver2.class代码如下:

public class MyBroadcastReceiver2 extends BroadcastReceiver {    @Override   public void onReceive(Context context, Intent intent) {        Toast.makeText(context,"Received a broadcast in MyBroadcastReceiver2",Toast.LENGTH_SHORT).show();//        abortBroadcast();        //创建一个Bundle对象        Bundle bundle = new Bundle();        //通过putString方法通过键值对方式存储一个字符串值        bundle.putString("msg","This is a message from MyBroadcastReceiver2");        //传递给下一个广播接收器        setResultExtras(bundle);    }}

修改项目”broadcastreveiverdemo“中MyBroadcastReceiver.class代码如下:

public class MyBroadcastReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        //获取上一个广播接收器传递的Bundle对象        Bundle bundle = getResultExtras(true);        String msg = bundle.getString("msg");        //通过Toast通知显示出来        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();    }}

重复上面的操作,检查Toast是否弹出”This is a message from MyBroadcastReceiver2“,测试截图如下:
这里写图片描述

6、安全性。

通过Context.sendBroadcast()发送的广播是全局性的、是跨应用的,那么你发送的广播有可能被别人接取,如果广播中包含一些机密信息,会引起安全问题;另外别的应用也有可能向你发送信息,那么有些方式限制着种行为呢:

广播接收器:
1. 可以通过在manifest中指定组件的属性android:exported=”false“来防止其它应用发送广播给它。
2. 为了在接收的时候执行权限,你需要在你注册接收者的时候提供一个非NULL的权限 – 或者在调用registerReceiver(BroadcastReceiver,IntentFilter,String,android.os.Handler)时或者在AndroidManifest.xml静态标签。只有获得权限允许的广播(通过在标签请求)可以发送Intent给接收者。

发送广播:你可以通过以下描述的权限控制谁可以接收到这些广播。
1. 从Android 4.0.4开始,你可以使用Intent.setPackage安全地限制广播到单一的应用。
2. 你要提供一个非NULL的权限变量给sendBroadcast(Intent,String)或者sendOrderedBroadcast(Intent,String,BroadcastReceiver,android.os.Handler,int,String,Bundle)。只有获得权限允许的接收者(通过在AndroidManifest.xml的标签请求)可以接收到广播。

可以在Security和Permission官方文档查看更多关于权限和安全的信息。

如果广播的发送和接收只在本应用中发生,Android提供了一个本地广播(LocalBroadcastManager)来进行管理,它可以很好的解决上面的安全问题,它的使用方式很简单,首先可以通过静态方法getInstance(Context)获取它的实例,然后用该实例发送广播、注册广播接收器、注销广播接收器来代替Context方法,代码如下:

package com.example.broadcastreceiverdemo;import android.app.Activity;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.support.v4.content.LocalBroadcastManager;import android.view.View;import android.widget.Button;public class MainActivity extends Activity {    public static final String MY_BROADCAST = "com.example.broadcastreceiverdemo.MY_BROADCAST";    private LocalBroadcastManager mManager;    private MyBroadcastReceiver mReceiver;    private IntentFilter mFilter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mManager = LocalBroadcastManager.getInstance(this);        mReceiver = new MyBroadcastReceiver();        mFilter = new IntentFilter(MY_BROADCAST);        mManager.registerReceiver(mReceiver, mFilter);        Button mBtnSendBc = (Button) findViewById(R.id.send_broadcast);        mBtnSendBc.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent(MY_BROADCAST);                mManager.sendBroadcast(intent);            }        });    }    @Override    protected void onDestroy() {        super.onDestroy();        mManager.unregisterReceiver(mReceiver);    }}

测试结果:项目”broadcastreveiverdemo2“不会接收到广播。LocalBroadManger需要android.support.v4包,如果无法自动导入,可以参考这个文章:[Android Studio 权威教程]AS添加第三方库的6种方式(Jar,module,so等)

本地广播是无法通过静态注册的,因为Androidmanifest.xml是一个全局性文件,而静态注册的目的就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时,程序肯定已经启动了,因此也不需要静态注册的功能。

总结本地广播的优点:
1. 不会向其它应用发送广播,因此不需要担心机密数据泄露。
2. 其他程序无法向本应用发送广播,因此不需要担心会有安全漏洞的隐患。
3. 因为不需要跨应用发送,所以运行效率更加高效。

7、生命周期

1、接收器的生命周期
一个广播接收者只有在onReceive(Context,Intent)方法调用时是有效的。一旦你的代码从这个函数中返回时,系统就认为这个对象结束了并且不再有效。而onReceive()中不能执行耗时操作,如果操作时间超过10s就出现ANR错误。你可以在里面添加一个无限循环并通过打印日志方式来测试,所以一般广播接收器的生命周期只有10s。

2、进程的生命周期
你可能想到了,既然onReceive()中不能执行耗时操作,可以开启一个工作线程来执行工作,这也是不允许的,因为接收器对象可能在10s内就销毁,虽然工作线程会继续运行,但是该进程很可能因为没有组件在运行变为空进程,比如一个静态注册的广播接收器,接收一个广播后开启一个工作线程,但接收器对象将很快被销毁,此时该进程就变为空进程,在内存紧张的情况,空进程的优先级非常低,它会优先被杀掉,那么里面所有的子线程也会被终止,导致任务执行未结束。而解决办法,就是通过startService()开启一个服务,这样就提高了进程的优先级,不会被轻易杀掉。

另外,你不能从BroadcastReceiver里展示一个对话框或者绑定Service。对于前者,你应该使用NotificationManager API来代替。对于后者,你可以使用Context.startService()来发送一个命令给Service。其实对话框可以通过把类型设置为”TYPE_SYSTEM_ALERT“在广播接收器中显示。

参考内容:
Google:Android APIs-BroadcastReceiver
BroadcastReceiver中文翻译
慕客网:第5章 四大组件之BroadcastReceiver
郭霖:第一行代码-第5章 全局大喇叭,详解广播机制
李刚:疯狂Android讲义:10.7 接收广播信息

0 0
原创粉丝点击