第十五章 Android的广播机制和BroadCast Receiver

1.引言

在《第四章 Android 四大应用组件》中,简单介绍了下四大组件的成员,属性,生命周期等。这里主要是介绍下Android中的广播机制。在四大组件中,Activity的使用频率是最高的。其他三个组件的使用频率相对叫低,但是既然叫四大组件,那就说明了他们的不可或缺性。
  记得小时候上学的时候的大喇叭吗?有时候听力考试也会用到,学校的广播室通过发送广播到各个教室的小喇叭上,每次一开,那简直是要命啊 。类似于喇叭的工作机制,在现在的计算机领域会有很广泛的引用。为了便于系统级别的消息通知,Android也引入了广播消息机制。当然相应的比较那个大喇叭例子,Android的广播机制更加灵活。

2.简介

为什么说Android的广播机制灵活呢?我们知道在我们的手机上有很多个应用,有时候会接收到不同的应用的消息,但是,既然是大喇叭,发过来的消息,不是都可以接受么?这里简单讲一下它的灵活之处,Android中的每个应用程序都可以对自己感兴趣的广播注册,这样该程序就只能接收到自己关心的广播内容。这些广播的内容可以是来自于系统,也可以是来自其他应用程序。
  我们所使用的手机,既可以说是一个广播接收器,也可以说是一个广播发送器。Android 的广播分为两个方面:广播发送者和广播接收者。BroadCast Receiver指的是广播接受者(广播接收器)。在一些系统的广播的使用场景:电量低弹窗,开机,锁屏等。常见的广播使用场景有下面几种:
1.同一app内部的同一组件内的消息通信(单个或多个线程之间)
2.同一app内部的不同组件之间的消息通信(单个进程);
3.同一app具有多个进程的不同组件之间的消息通信;
4.不同app之间的组件之间消息通信;
5.Android系统在特定情况下与App之间的消息通信。

之所以叫做广播,就是因为它只管"说"至于"听不听",那就不管了。另外广播的另一个特点是,它可以被不止一个程序接收,也可以不被任何程序接收。广播机制最大的特点就是发送方并不关心接收方是否接到数据,也不关心接收方是如何处理数据的。
Android中广播的是操作系统中产生的各种各样的事件。例如,收到一条短信就会产生一个收到短信息的事件。而Android操作系统一旦内部产生了这些事件,就会向所有的广播接收器对象来广播这些事件。

3.BroadCast Receiver广播接收器

3.1 广播的类型

1.标准广播
标准关闭时一种完全一步执行的广播,假设有很多个广播接收器,当广播发出去的时候,所有的接收器,同时接收到广播消息。接收器之间没有任何先后顺序。这种广播的频率比较高,这也意味着它无法被截断。标准广播的工作流程如下图所示:

标准广播

2.有序广播
这是一种同步执行的广播,广播接收器有先后之分,同一时刻只有一个接收器能够接收广播,而且,当优先级靠前的接收器没有接收到广播的时候,优先级靠后的接收器就无法接收到广播了。而且优先级高的广播还能截断广播。有序广播的工作流程如下图所示:

有序广播

3.2 广播接收器的注册

1.静态注册
  静态注册方式是在AndroidManifest.xml的application里面定义receiver并设置要接收的action。如果在清单配置文件中配置了广播接收器,那么程序在安装后会自动注册广播接收器。
静态注册方式的特点:不管该应用程序是否处于活动状态,都会进行监听。程序即便未启用,也可以接收广播。
实现静态注册的步骤:
1.创建一个类BootBroadCastReceiver继承BroadcastReceiver类,通过Android Studio创建,可以通过File->New->Other->Broadcast Receiver 这样的快捷方式创建广播接收器,AndroidManifest.xml文件会自动注册。有一个<receiver> 标签对。在onReceiver()中的方法很简单,Toast一个就好了

public class BootBroadCastReceiver extends BroadcastReceiver {

    private static final String TAG = "BootBroadCastReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "启动Broadcast", Toast.LENGTH_SHORT).show();
    }
}

2.因为我们是需要接收广播信息,所以需要权限RECEIVE_BOOT_COMPLETED。由于Android在启动的时候会发出一条值为android.intent.action.BOOT_COMPLETED的广播,所以 在<receiver>标签对里面添加相应的activity.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.demo.broadcastreceiverdemo">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <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=".BootBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

3.重启模拟器,程序就可以接到开机广播了。


静态注册

2.动态注册
动态注册也叫做代码注册,不需要在AndroidManifest.xml中注册。而是在activity里面调用上下文对象的registerReceiver() 方法来注册。和静态的内容差不多。一个形参是receiver对象,另一个是IntentFilter对象。而IntentFilter构造方法的参数是要接收的action。
动态注册方式特点:在代码中进行注册后,当应用程序关闭后,就不再进行监听。
实现动态注册的步骤,监听网络状态:
1.创建一个类MyReceiver继承BroadCast Receiver类,重写父类的onReceiver()方法,这样当网络发生变化的时候,onReceiver的方法就会得到执行。

  class MyReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "网络连接状态发生改变", Toast.LENGTH_SHORT).show();
        }
    }

2.在onCreate()方法中创建一个MyReceiver实例,并创建一个IntentFilter的过滤器,指定action,因为当网络状态发生改变的时候,系统会发送android . net . conn . CONNECTIVITY _CHANGE这样的广播。如果想调用别的关闭可以使用相应的action。最后在调用registerReceiver()方法,将myReceiver和intentFilter的实例传进去,这样就完成了注册。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyReceiver   myReceiver = new MyReceiver();
       //创建过滤器,并制定action,使之用于接收同action的广播
        IntentFilter  intentFilter=new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
        //注册广播接收器
        registerReceiver(myReceiver,intentFilter);

    }
       //创建过滤器的第二种写法
          IntentFilter  intentFilter=new IntentFilter();
          intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");

3.最后在onDestory()的方法中调用 unregisterReceiver(),销毁广播,释放内存

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

4.因为Android 系统为了保护用户设备的隐私,所以对于一些相对于用户来说的敏感权限需要在配置AndroidManifest.xml文件中添加权限声明。

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

3.3发送自定义广播

  因为我们知道我们所适应的实际既是广播接收器,也是广播发送器。如果只能接,不能发,那就和收音机没啥区别了。前面已经介绍了,广播主要分为两种类型:标准广播和有序广播。这里介绍一下自定义的广播,相信你看完之后会很兴奋的,你可以定制你的Style了。

3.3.1 发送标准广播

在思考发送广播之前,我们需要有一个接收器,这样发出去的东西,才能知道它到底有没有被接收到。否则只管发了,发了有没有结果不知道,这就做了无用功了。注册的方式,就选择静态注册,因为我们是自定义的广播,所以,系统启动的时候,不会直接发送广播。
具体步骤:
1.创建一个类MyBroadCastReceiver继承 BroadcastReceiver。这里就提示性的弹窗。

public class MyBroadCastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "快给我发过来啊,广播", Toast.LENGTH_SHORT).show();
    }
}

2.在Androidmanifest.xml文件中,修改receiver标签,添加需要发送的广播。

   <receiver
            android:name=".myStyle.MyBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.demo.broadcastreceiverdemo.MY_BROADCAST" />
            </intent-filter>

        </receiver>

这里需要说明一下,我们在action标签里面添加了一个com.demo.broadcastreceiverdemo. MY_BROADCAST的广播。因此待会发送广播的时候,我们就需要发送这样的广播。然后在receiver里面接收同样的广播标签。特别说明一下,格式是全包名+自定义。要不然会报错。你可以试试。
3.修改activity_main2.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"
    android:gravity="center"
  >

    <Button
        android:id="@+id/button"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:text="发送广播"/>

</LinearLayout>

4.发送广播

public class Main2Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Button button= (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent  intent=new Intent("com.demo.broadcastreceiverdemo.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
    }
}

我们把按钮的点击事件加入发送自定义广播。首先构建Intent对象,然后把要发送的广播传入,然后调用Context的sendBroadCast()的方法,这样所有监听com.demo.broadcastreceiverdemo. MY_BROADCAST的接收器就会接收到这条广播信息。运行效果如图所示:

自定义标准广播

这是一个简单的小例子,我们通过在MainActivity中通过触发点击事件,来发送广播,并且自定义接收器,接收广播。

3.3.2 发送有序广播

在开篇的广播介绍中,我们知道有序广播的原理,应用场景。我们知道广播是可以跨进程进行通信的。因此在不同进程之间进行通信是可以实现的。为了测试有序广播我们再新建一个project,然后在从第一个project开始发送广播,第二个project实现接受。
实现步骤:
1.新建一个project ,然后新建一个类SecondBroadCastReceiver继承BroadCastReceiver.


public class SecondBroadCastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "第二个进程接收广播", Toast.LENGTH_SHORT).show();
    }

2.给第二个AndroidMainifest.xml文件中,修改receiver方法,设置action接受的广播与第一个project的一样com.demo.broadcastreceiverdemo.MY_BROADCAST。

    <receiver
            android:name=".SecondBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter >
                <action android:name="com.demo.broadcastreceiverdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

我们可以看到第一个和第一个project都可以接收同样的广播com.demo.broadcastreceiverdemo. MY_BROADCAST。然后回到第一个project界面,运行结果如下图所示:

跨进程通信

我们可以看到,我们接受了两次信息,分别为MyBroadCastReceiver和SecondBroadCastReceiver,所以我们的应用程序之间可以被通信。
不过到目前为止,我们的程序里发出的都是标准广播,哪有序广播呢?回到第一个project项目。

public class Main2Activity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Button button= (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent  intent=new Intent("com.demo.broadcastreceiverdemo.MY_BROADCAST");
//              sendBroadcast(intent);
                sendOrderedBroadcast(intent,null);
            }
        });
    }
}

修改intent的发送方法, sendOrderedBroadcast()。 这个方法接收两个参数,第一个参数为intent,,第二个参数是一个与权限相关的字符串。这里传个空值就可以了。然后重新运行第一个project,发现两个应用程序还是可以接收广播。

看上去好像没有什么区别,但是我们知道有序广播,是串行广播,是有优先级的,广播可以被拦截的。
1.设置优先级:

        <receiver
            android:name=".myStyle.MyBroadCastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter  android:priority="100">
                <action android:name="com.demo.broadcastreceiverdemo.MY_BROADCAST" />
            </intent-filter>
        </receiver>

在 intent-filter 添加 android:priority="100",数字可以自己设置。数字越大,优先级越高。当你将第二个project的 action 的值设置比第一个大,弹窗会第二个较第一个早。
2.拦截广播

public class MyBroadCastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "快给我发过来啊,广播", Toast.LENGTH_SHORT).show();
        abortBroadcast();//拦截广播
    }
}

在重写的onReceive()的方法里面加上 abortBroadcast()方法,这样就完成了拦截广播的操作。

4.使用本地广播

我们之前讲的广播无论是发送还是接收全部都属于系统全局广播,发出的广播可以被任何应用程序接收,而且也可以接收任何应用程序的广播,所以这就会引发安全问题。通过广播发送的信息被其他程序截获了,或者别的程序不同的发送垃圾广播给你,那还得了。
所以为了能够解决安全性的问题,Android引入了本地广播消息机制,这样发送的广播消息都只能在本应用程序里面传递,广播接收器只能接收来自本应用的消息,这样就不存在安全性问题了。代码如下:
1.在MainActivity中,定义一个内部类集成BroadcCastReceiver

class LocalBroadCastReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接受本地的广播消息", Toast.LENGTH_SHORT).show();
        }
    }

2.设置发送的广播和接收的广播。我们这里因为要用到本地广播,所以需要引入LocalBroadcastManager本地广播管理类。通过getInstance()方法获取实例,然后通过本地广播调用sendBroadcast()去发送广播,然后设置过滤器,过滤接收的广播action,最后实例化本地广播接收器,去注册本地广播。

    private LocalBroadcastManager localBroadcastManager;
    private IntentFilter intentFilter;
    private LocalBroadCastReceiver localBroadCastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        //获取实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button= (Button) findViewById(R.id.button3);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
        localBroadCastReceiver = new LocalBroadCastReceiver();
        localBroadcastManager.registerReceiver(localBroadCastReceiver,intentFilter);//注册本地广播监听器
    }

3.还记得代码注册广播,要销毁

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

完整代码如下:


public class Main3Activity extends AppCompatActivity {

    private LocalBroadcastManager localBroadcastManager;
    private IntentFilter intentFilter;
    private LocalBroadCastReceiver localBroadCastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        //获取实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        Button button= (Button) findViewById(R.id.button3);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.demo.broadcastreceiverdemo.MY_LOCAL_BROADCAST");
        localBroadCastReceiver = new LocalBroadCastReceiver();
        localBroadcastManager.registerReceiver(localBroadCastReceiver,intentFilter);//注册本地广播监听器
    }

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

    class LocalBroadCastReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接受本地的广播消息", Toast.LENGTH_SHORT).show();
        }
    }
}

实现效果如下所示:

本地广播的使用

最后奉上github地址:https://github.com/wangxin3119/BroadCastDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容