文章目录

  • 目标
  • 一、广播机制的概述
  • 目标
  • 二、广播接收者
  • 目标
  • 2.1 什么是广播接收者
  • 2.2 创建广播接收者
  • 动态注册
  • 静态注册
  • 三、自定义广播与广播的类型
  • 目标
  • 3.1 自定义广播
  • 3.2 实战演练—饭堂小广播
  • 3.3 广播的类型
  • 无序广播
  • 有序广播
  • 有序广播的优先级
  • 广播接收者优先级
  • 3.4 实战演练—数鸭子
  • 指定广播


目标

  • 熟悉广播机制的概述,能够归纳广播机制的实现流程
  • 掌握广播接收者的创建方式,能够独立创建广播接收者
  • 掌握自定义广播的方式,能够通过自定义广播实现饭堂小广播案例
  • 熟悉广播的类型,能够归纳有序广播无序广播的工作流程

在Android系统中,广播是一种运用在组件之间传递消息的机制,例如电池电量低时会发送一条提示广播。如果要接收并过滤广播中的消息,则需要使用BroadcastReceiver(广播接收者),广播接收者是Android四大组件之一,通过广播接收者可以监听系统中的广播消息,实现在不同组件之间的通信,本章将针对广播及广播接收者进行详细讲解。

一、广播机制的概述

目标

  • 熟悉广播机制的概述,能够归纳广播机制的实现流程

android studio 实战演练饭堂小广播 饭堂小广播下连_android

通常情况下在学校的每个教室都会装有一个喇叭,这些喇叭是接入到学校广播室的。如果有重要通知,会发送一条广播来告知全校师生。为了便于发送和接收系统级别的消息通知,Android系统也引入了一套类似广播的消息机制。

Android中的广播(Broadcast)机制用于进程/线程间通信,该机制使用了观察者模式,观察者模式是一种软件设计模式,该模式是基于消息的发布/订阅事件模型,该模型中的消息发布者是广播机制中的广播发送者消息订阅者是广播机制中的广播接收者

android studio 实战演练饭堂小广播 饭堂小广播下连_android_02

广播作为Android组件间的通信方式,可以使用的场景有以下几种:

android studio 实战演练饭堂小广播 饭堂小广播下连_java_03

二、广播接收者

目标

  • 熟悉广播接收者的概念,能够归纳多个广播接收者接收广播的过程
  • 掌握广播接收者的创建方式,能够独立创建广播接收者

2.1 什么是广播接收者

Android系统中内置了很多广播,例如手机开机完成、电池电量不足时都会发送一条广播。
为了监听来自系统或者应用程序的广播事件,Android系统提供了BroadcastReceiver(广播接收者)组件。

android studio 实战演练饭堂小广播 饭堂小广播下连_优先级_04

2.2 创建广播接收者

广播接收者的创建方式有两种,具体如下:

  • 一种是通过在应用程序的包中创建一个类继承BroadcastReceiver并重写onReceive()方法来实现的。
  • 一种是通过选中应用程序中的,右击选择【New】→【Other】→【Broadcast Receiver】选项来创建的。
  • 注意:创建完广播接收者之后还需要对广播接收者进行注册才可以接收广播。

android studio 实战演练饭堂小广播 饭堂小广播下连_java_05

创建完成的广播接收者MyReceiver 的代码如下:

public class MyReceiver extends BroadcastReceiver {
       public MyReceiver() {
       }
     // 该方法用于接收发送的广播消息,实现广播接收者的相关操作
     //程序默认抛出一个未支持操作异常:UnsupportedOperationException
     //后续程序实现onReceive()方法时,删除该异常即可
       @Override
        public void onReceive (Context context, Intent intent) {
             throw new UnsupportedOperationException("Not yet implemented");
        }
 }

广播接收者的注册方式有两种,分别是动态注册静态注册

  • 动态注册:在Activity中通过代码注册广播接收者
  • 静态注册:在清单文件中配置广播接收者

动态注册

//动态注册的广播接收者是否被注销依赖于注册广播的组件,当组件销毁时,广播接收者也随之被注销。
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); 
	//实例化广播接收者
    receiver = new MyBroadcastReceiver(); 
    //实例化过滤器并设置要过滤的广播
    String action = "android.provider.Telephony.SMS_RECEIVED";
    IntentFilter intentFilter = new IntentFilter();
    //设置要过滤的action的属性
    intentFilter.addAction(action);
    //注册广播
    //receiver			广播接收者
    //intentFilter		实例化的过滤器
    registerReceiver(receiver,intentFilter); 
 }
 protected void onDestroy() {
    super.onDestroy();
    //当Activity销毁时,取消注册
    unregisterReceiver(receiver);
 }

静态注册

  • 无论应用程序是否处于运行状态,广播接收者都会对程序进行监听
<?xml version="1.0" encoding="utf-8"?>
<manifest ……….  >
      <application ……… > 
          <!--
			name		要注册的广播接收者
			enabled		为ture,表示广播接收者可由系统实例化
			exported	为ture,表示可以接当前程序之外的广播
			-->
           <receiver
                  android:name=".MyReceiver"
                  android:enabled="true"
                  android:exported="true" >
           </receiver> 
     </application>
</manifest>

注意:在Android 8.0之后,使用静态注册的广播接收者将无法接收到广播。

三、自定义广播与广播的类型

目标

  • 掌握自定义广播的方式,能够通过自定义广播实现饭堂小广播案例
  • 熟悉广播的类型,能够归纳有序广播与无序广播的工作流程

3.1 自定义广播

系统提供的广播不能满足实际需求时,可以自定义广播,同时需要编写对应的广播接收者。

android studio 实战演练饭堂小广播 饭堂小广播下连_android_06

当自定义广播发送消息时,会储存到公共消息区中,而公共消息区中如果存在对应的广播接收者,就会及时的接收这条信息。

3.2 实战演练—饭堂小广播

本节将通过一个饭堂小广播发送吃饭信号的案例来演示自定义广播的发送和接收。本案例的界面效果如下图所示。

android studio 实战演练饭堂小广播 饭堂小广播下连_广播接收者_07

android studio 实战演练饭堂小广播 饭堂小广播下连_广播接收者_08

放置界面控件 res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">
    <RelativeLayout
        android:id="@+id/ll_horn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp">
        <ImageView
            android:id="@+id/iv_horn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/horn" />
        <TextView
            android:id="@+id/tv_right_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/iv_horn"
            android:background="@drawable/content_right_bg"
            android:gravity="center"
            android:text="点击喇叭"
            android:textColor="@android:color/white" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_right_content"
            android:layout_marginTop="20dp"
            android:layout_toRightOf="@id/iv_horn"
            android:src="@drawable/foods" />
    </RelativeLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/ll_horn"
        android:layout_marginTop="100dp" >
        <ImageView
            android:id="@+id/iv_rabbit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:src="@drawable/rabbit" />
        <TextView
            android:id="@+id/tv_left_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/iv_rabbit"
            android:background="@drawable/content_left_bg"
            android:gravity="center"
            android:text="开饭啦!"
            android:textColor="@android:color/white"
            android:visibility="gone" />
    </RelativeLayout>
</RelativeLayout>

实现界面功能 canteenradio\MainActivity.java

package cn.itcast.canteenradio;

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.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private ImageView iv_horn;
    private TextView tv_left_content, tv_right_content;
    private MyBroadcastReceiver receiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        // 获取界面控件
        iv_horn = findViewById(R.id.iv_horn);
        tv_left_content = findViewById(R.id.tv_left_content);
        tv_right_content = findViewById(R.id.tv_right_content);
        receiver = new MyBroadcastReceiver();        // 实例化广播接收者
        String action = "Open_Rice";                 //定义过滤的action属性的值为“Open_Rice”
        IntentFilter intentFilter = new IntentFilter(action);   //实例化intentFilter过滤器
        registerReceiver(receiver, intentFilter);   //注册广播接收者
//        实现喇叭图片的点击事件
        iv_horn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                设置喇叭对应的气泡文本为:"开饭啦!"
                tv_right_content.setText("开饭啦!");
                Intent intent = new Intent();
                // 定义广播的事件类型,即广播的action属性名称
                intent.setAction("Open_Rice");
                sendBroadcast(intent);     //发送广播
            }
        });
    }

    class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
//            判断接收到的广播消息是否是“Open_Rice”
            if (intent.getAction().equals("Open_Rice")) {
                tv_left_content.setVisibility(View.VISIBLE);
                Log.i("MyBroadcastReceiver", "自定义的广播接收者,接收到了发送开饭信号的广播消息");
            }
            Log.i("MyBroadcastReceiver", intent.getAction());
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);    // 注销注册的广播接收者
    }
}

3.3 广播的类型

Android系统提供了两种广播类型,有序广播和无序广播,开发者可根据需求为程序设置不同的广播类型。

  • 无序广播
  • 无序广播是完全异步执行,发送广播时所有监听这个广播的广播接收者都会接收到此消息,但接收的顺序不确定。
  • 有序广播
  • 按照接收者的优先级接收,只有一个广播接收者能接收消息,在此广播接收者中逻辑执行完毕后,才会继续传递。

无序广播

无序广播的效率比较高,但无法被拦截,当发送一条广播消息时,所有的广播接收者都会接收到此消息。无序广播的工作流程如下:

android studio 实战演练饭堂小广播 饭堂小广播下连_优先级_09

有序广播

相比无序广播,有序广播的广播效率较低,但此类型是有先后顺序的,并可被拦截。有序广播的工作流程如下:

android studio 实战演练饭堂小广播 饭堂小广播下连_android_10

有序广播的优先级

//动态注册MyReceiver广播
MyReceiver  one = new MyReceiver ();
IntentFilter filter = new IntentFilter();
//数值越大,优先级越高。如果两个广播接收者的优先级相同,则先注册的广播接收者优先级高。
filter.setPriority(1000); 
filter.addAction("Intercept_Stitch");
registerReceiver(one,filter);

广播接收者优先级

在动态注册广播接收者时,可以使用IntentFilter对象的setPriority()方法设置优先级别,例如:intentFilter.setPriority(1000)。这里需要说明的是,属性值越大,优先级越高。

如果两个广播接收者的优先级相同,则先注册的广播接收者优先级高。也就是说,如果两个程序监听了同一个广播事件,同时设置了相同的优先级,则先安装的程序优先接收。

3.4 实战演练—数鸭子

本节将通过一个有序数鸭子的案例来演示如何发送有序广播、根据广播接收者的优先级顺序接收广播、拦截广播,本案例的界面效果如下图所示。

android studio 实战演练饭堂小广播 饭堂小广播下连_apache_11

android studio 实战演练饭堂小广播 饭堂小广播下连_android_12

鸭子图片上的红色圆形数字背景 res\drawable\badge_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<!--    shape="rectangle"     定义一个矩形
        <gradient>            定义矩形中的渐变色
        startColor            渐变色开始的颜色    endColor   渐变色结束的颜色
        type="linear"         颜色以线性渐变的形式进行显示
        <corners>             定义矩形的四个边角
        radius                设置矩形的圆角半径
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:endColor="#fe451d"
        android:startColor="#fe957f"
        android:type="linear" />
    <corners android:radius="180dp" />
</shape>

序号和图片控件的样式 res\values\styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!--定义序号控件的样式-->
    <style name="badge_style">
        <item name="android:layout_width">20dp</item>
        <item name="android:layout_height">20dp</item>
        <item name="android:layout_marginLeft">10dp</item>
        <item name="android:paddingLeft">2dp</item>
        <item name="android:paddingRight">2dp</item>
        <item name="android:textColor">@android:color/white</item>
        <item name="android:gravity">center</item>
        <item name="android:background">@drawable/badge_bg</item>
        <item name="android:textStyle">bold</item>
        <item name="android:textSize">12sp</item>
        <item name="android:visibility">gone</item>
    </style>

    <!--定义图片控件的样式-->
    <style name="duck_style">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:src">@drawable/duck</item>
    </style>

</resources>

放置界面控件 res\layout\activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/count_ducks_bg">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:layout_marginLeft="80dp">
        <ImageView
            android:id="@+id/iv_horn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:src="@drawable/horn" />
        <TextView
            android:id="@+id/tv_left_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/iv_horn"
            android:background="@drawable/content_left_bg"
            android:gravity="center"
            android:text="有序报数"
            android:textColor="@android:color/white"
            android:visibility="gone" />
    </RelativeLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:gravity="center_horizontal">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tv_one"
                style="@style/badge_style"/>
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/duck" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tv_two"
                style="@style/badge_style"/>
            <ImageView
                style="@style/duck_style" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tv_three"
                style="@style/badge_style"/>
            <ImageView
                style="@style/duck_style" />
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

实现数鸭子界面的功能 countducks\MainActivity.java

package cn.itcast.countducks;

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.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private MyBroadcastReceiverOne one;
    private MyBroadcastReceiverTwo two;
    private MyBroadcastReceiverThree three;
    private ImageView iv_horn;
    private TextView tv_left_content, tv_one, tv_two, tv_three;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        registerReceiver();
        init();
    }

    private void init() {
        iv_horn = findViewById(R.id.iv_horn);
        tv_left_content = findViewById(R.id.tv_left_content);
        tv_one = findViewById(R.id.tv_one);
        tv_two = findViewById(R.id.tv_two);
        tv_three = findViewById(R.id.tv_three);
        iv_horn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tv_left_content.setVisibility(View.VISIBLE);
                iv_horn.setClickable(false);  //设置喇叭图片为不可点击的状态
                Intent intent = new Intent();
                intent.setAction("Count_Ducks");      // 定义广播的事件类型
                sendOrderedBroadcast(intent, null);  // 发送有序广播
//                MyBroadcastReceiverThree receiver = new MyBroadcastReceiverThree();
//                sendOrderedBroadcast(intent,null,receiver, null, 0, null, null); // 发送有序广播
            }
        });
    }

    private void registerReceiver() {
        // 动态注册MyBroadcastReceiverTwo广播
        two = new MyBroadcastReceiverTwo();
        IntentFilter filter2 = new IntentFilter();
        filter2.setPriority(1000);
        filter2.addAction("Count_Ducks");
        registerReceiver(two, filter2);
        // 动态注册MyBroadcastReceiverOne广播
        one = new MyBroadcastReceiverOne();
        IntentFilter filter1 = new IntentFilter();
        filter1.setPriority(1000);
        filter1.addAction("Count_Ducks");
        registerReceiver(one, filter1);
        // 动态注册MyBroadcastReceiverThree广播
        three = new MyBroadcastReceiverThree();
        IntentFilter filter3 = new IntentFilter();
        filter3.setPriority(600);
        filter3.addAction("Count_Ducks");
        registerReceiver(three, filter3);
    }

    private int num = 0; // 存放接收到广播消息时鸭子图片上方需要显示的序号
    class MyBroadcastReceiverOne extends BroadcastReceiver {
        @Override
//        用于接收程序发送的有序广播
        public void onReceive(Context context, Intent intent) {
            tv_one.setVisibility(View.VISIBLE);     //将第一只鸭子图片上方的序号控件tv_one设置为显示状态
            num = num + 1;
            tv_one.setText(num + "");       //将变量num显示到控件tv_one上
            Log.i("BroadcastReceiverOne", "广播接收者One,接收到了广播消息");
            delay();        //将广播消息延迟500毫秒后传递到下一个广播接收者中
        }
    }

    class MyBroadcastReceiverTwo extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            tv_two.setVisibility(View.VISIBLE);
            num = num + 1;
            tv_two.setText(num + "");
            Log.i("BroadcastReceiverTwo", "广播接收者Two,接收到了广播消息");
//            abortBroadcast(); //拦截有序广播
//            Log.i("BroadcastReceiverTwo","我是广播接收者Two,广播消息被我拦截了");
            delay();
        }
    }

    class MyBroadcastReceiverThree extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            tv_three.setVisibility(View.VISIBLE);
            num = num + 1;
            tv_three.setText(num + "");
            Log.i("BroadcastReceiverThree", "广播接收者Three,接收到了广播消息");
            delay();
        }
    }

    /**
     * 延迟500毫秒
     */
    private void delay() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(one);
        unregisterReceiver(two);
        unregisterReceiver(three);
    }
}

若将广播接收者MyBroadcastReceiverTwo的优先级设置为1000,并将注册MyBroadcastReceiverTwo的语句放在注册MyBroadcastReceiverOne的语句前面,修改完MainActivity中注册的广播接收者代码后,运行程序,点击界面中的喇叭图片,效果如下。

android studio 实战演练饭堂小广播 饭堂小广播下连_广播接收者_13

如果想要拦截一个有序广播,则必须在优先级较高的广播接收者中拦截接收到的广播,接下来通过在优先级较高的MyBroadcastReceiverTwo中添加一个abortBroadcast()方法拦截广播,运行程序,点击界面中的喇叭图片,效果如下。

android studio 实战演练饭堂小广播 饭堂小广播下连_android_14

countducks\MainActivity.java

class MyBroadcastReceiverTwo extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        tv_two.setVisibility(View.VISIBLE);
        num = num + 1;
        tv_two.setText(num + "");
        Log.i("BroadcastReceiverTwo", "广播接收者Two,接收到了广播消息");
//            abortBroadcast(); //拦截有序广播
//            Log.i("BroadcastReceiverTwo","我是广播接收者Two,广播消息被我拦截了");
        delay();
    }
}

指定广播

当发送一条有序广播时,要保证一个广播接收者必须接收到此广播,无论此广播接收者的优先级高或低。要满足这种需求,可以在Activity中使用sendOrderedBroadcast()方法发送有序广播,并设置该方法中传递的第3个参数为指定的广播接收者对象即可。

Intent intent = new Intent();
intent.setAction("Count_Ducks"); // 定义广播的事件类型
MyBroadcastReceiverThree receiver = new  MyBroadcastReceiverThree();
sendOrderedBroadcast(intent,null,receiver, null, 0, null, null); // 发送有序广播

运行数鸭子程序,点击界面中的喇叭图片,效果如下。

android studio 实战演练饭堂小广播 饭堂小广播下连_优先级_15

countducks\MainActivity.java

private void init() {
    iv_horn = findViewById(R.id.iv_horn);
    tv_left_content = findViewById(R.id.tv_left_content);
    tv_one = findViewById(R.id.tv_one);
    tv_two = findViewById(R.id.tv_two);
    tv_three = findViewById(R.id.tv_three);
    iv_horn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            tv_left_content.setVisibility(View.VISIBLE);
            iv_horn.setClickable(false);  //设置喇叭图片为不可点击的状态
            Intent intent = new Intent();
            intent.setAction("Count_Ducks");      // 定义广播的事件类型
            sendOrderedBroadcast(intent, null);  // 发送有序广播
//                MyBroadcastReceiverThree receiver = new MyBroadcastReceiverThree();
//                sendOrderedBroadcast(intent,null,receiver, null, 0, null, null); // 发送有序广播
        }
    });
}