赞
踩
目录
本文介绍应用广播的几种收发形式,包括如何收发标准广播、如何收发有序广播、如何收发静态广播、如何监听定时管理器发出的系统闹钟广播等。
Android的广播机制借鉴了Wifi的通讯原理,不必搭建专门的通路,就能在发送方与接收方之间建立连接。同时广播(Broadcast)也是Android的四大组件之一,它用于Android各组件之间的灵活通信,与活动的区别在于:
与广播有关的方法主要有已下3个
广播的收发过程可分为3个步骤:发送标准广播、定义广播接收器、开关广播接收器,分别说明如下:
广播的发送操作很简单,一共只有两步:先创建意图对象,再调用sendBroadcast方法发送广播即可。
- public class BroadcastType1 extends AppCompatActivity implements View.OnClickListener{
- //这是广播的动作名称,发送广播和接收广播都以它作为接头暗号
- private final static String STANDARD_ACTION = "com.example.helloandroid.Broadcast";
- private TextView tv_ReceiveBroadcast1;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_broadcast_type1);
- findViewById(R.id.btn_SendBroadcast1).setOnClickListener(this);
- }
- @Override
- public void onClick(View v){
- if (v.getId()==R.id.btn_SendBroadcast1){
- Intent intent = new Intent(STANDARD_ACTION); //创建指定动作的意图
- sendBroadcast(intent); //发送标准广播
- }
- }
- }

广播发出来后,还得有设备去接收广播,也就是需要广播接收器。接收器主要规定两个事情:一个是接收什么样的广播,另一个是接收到广播以后要做什么。由于接收器的处理逻辑大同小异,因此Android提供了抽象之后的接收器基类BroadcastReceiver,开发者自定义的接收器都从BroadcastReceiver派生而来。新定义的接收器需要重写onReceive方法,方法内部先判断当前广播是否符合代收的广播名称,校验通过后再开展后面的业务逻辑。
- private String mDesc ="这里查看标准广播的收听信息";
- //定义一个标准广播的接收器
- private class StandardReceiver extends BroadcastReceiver {
- //一旦收到广播马上触发onReceive方法
- @Override
- public void onReceive(Context context,Intent intent){
- //广播意图飞控且接头暗号正确
- if (intent != null && intent.getAction().equals(STANDARD_ACTION)){
- mDesc = String.format("%s\n%s收到一个标准广播",mDesc,DateUtil.getNowTime());
- tv_ReceiveBroadcast1.setText(mDesc);
- }
- }
- }
为了避免资源浪费,还要求合理使用接收器。活动页面启动之后才注册接收器,活动页面停止之后就注销接收器。在注册接收器的时候,允许事先指定只接收某种类型的广播,即通过意图过滤器挑选动作名称一致的广播。
- private StandardReceiver standardReceiver; //声明一个标准广播的接收器实例
- @Override
- protected void onStart(){
- super.onStart();
- standardReceiver = new StandardReceiver(); //创建一个标准广播的接收器
- //创建一个意图过滤器,只处理STANDARD_ACTION的广播
- IntentFilter filter = new IntentFilter(STANDARD_ACTION);
- //注册接收器之后才能正常接收广播
- registerReceiver(standardReceiver,filter);
- }
- @Override
- protected void onStop(){
- super.onStop();
- unregisterReceiver(standardReceiver); //注销接收器,注销之后就不再接收广播
- }
由于广播没有指定唯一的接收者,因此可能存在多个接收器,每个接收器都拥有自己的处理逻辑。这种机制固然灵活,却不够严谨,因为不同接收器之间也许有矛盾。
因此广播的有序接收功能要求实现下列的处理逻辑:
(1)一个广播存在多个接收器,这些接收器需要排队接收广播,这意味着该广播是条有序广播。
(2)先收到广播的接收器A,既可以让其他接收器继续收听广播,也可以终端广播不让其他接收器接听。
至于如何实现有序广播的收发,则需要完成以下3个编码步骤:
之前发送标准广播用到了sendBroadcast方法,可是该方法发出来的广播是无序的。只有调用sendOrderedBroadcast方法才能发送有序广播。
- public class BroadcastType2 extends AppCompatActivity implements View.OnClickListener{
- private final static String ORDER_ACTION = "com.example.helloandroid.Broadcast2";
- private TextView tv_ReceiveBroadcast2;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_broadcast_type2);
- findViewById(R.id.btn_SendBroadcast2).setOnClickListener(this);
- }
- @Override
- public void onClick(View v){
- if(v.getId()==R.id.btn_SendBroadcast2){
- Intent intent = new Intent(ORDER_ACTION); //创建一个指定动作意图
- sendOrderedBroadcast(intent,null); //发送有序广播
- }
- }
- }

接收器的定义代码基本不变,也要从BroadcastReceiver继承而来,唯一的区别是有序广播的接收器允许中断广播。倘若在接收器内部调用abortBroadcast方法,就会中断有序广播,使得后面的接收器不能再接收该广播。
- private OrderAReceiver orderAReceiver; //声明有序广播接收器A的实例
- //定义一个有序广播的接收器A
- private class OrderAReceiver extends BroadcastReceiver {
- //一旦接收到有序广播A,马上触发接收器的onReceive方法
- @Override
- public void onReceive(Context context,Intent intent){
- if (intent != null && intent.getAction().equals(ORDER_ACTION)){
- String desc = String.format("%s%s 接收器A收到一个有序广播\n",
- tv_ReceiveBroadcast2.getText().toString(), DateUtil.getNowTime());
- tv_ReceiveBroadcast2.setText(desc);
- if (ck_abort.isChecked()){
- abortBroadcast(); //中断广播,此时后面的接收器无法收到该广播
- }
- }
- }
- }
-
- private OrderBReceiver orderBReceiver; //声明有序广播接收器B的实例
- //定义一个有序广播的接收器B
- private class OrderBReceiver extends BroadcastReceiver {
- //一旦接收到有序广播B,马上触发接收器的onReceive方法
- @Override
- public void onReceive(Context context,Intent intent){
- if (intent != null && intent.getAction().equals(ORDER_ACTION)){
- String desc = String.format("%s%s 接收器A收到一个有序广播\n",
- tv_ReceiveBroadcast2.getText().toString(), DateUtil.getNowTime());
- tv_ReceiveBroadcast2.setText(desc);
- if (ck_abort.isChecked()){
- abortBroadcast(); //中断广播,此时后面的接收器无法收到该广播
- }
- }
- }
- }

接收器的注册操作同样调用registerReceiver方法,为了给接收器排队,还需调用意图过滤器的setPriority方法设置优先级,优先级越大的接收器,越先收到有序广播。如果不设置优先级,或者两个接收器的优先级相等,那么越早注册的接收器,会越先收到有序广播。
- @Override
- protected void onStart() {
- super.onStart();
- // 多个接收器处理有序广播的顺序规则为:
- // 1、优先级越大的接收器,越早收到有序广播;
- // 2、优先级相同的时候,越早注册的接收器越早收到有序广播
- orderAReceiver = new OrderAReceiver(); // 创建一个有序广播的接收器A
- // 创建一个意图过滤器A,只处理ORDER_ACTION的广播
- IntentFilter filterA = new IntentFilter(ORDER_ACTION);
- filterA.setPriority(8); // 设置过滤器A的优先级,数值越大优先级越高
- registerReceiver(orderAReceiver, filterA); // 注册接收器A,注册之后才能正常接收广播
- orderBReceiver = new OrderBReceiver(); // 创建一个有序广播的接收器B
- // 创建一个意图过滤器A,只处理ORDER_ACTION的广播
- IntentFilter filterB = new IntentFilter(ORDER_ACTION);
- filterB.setPriority(10); // 设置过滤器B的优先级,数值越大优先级越高
- registerReceiver(orderBReceiver, filterB); // 注册接收器B,注册之后才能正常接收广播
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- unregisterReceiver(orderAReceiver); // 注销接收器A,注销之后就不再接收广播
- unregisterReceiver(orderBReceiver); // 注销接收器B,注销之后就不再接收广播
- }

广播接收器也能在AndroidManifest.xml中注册,并且注册的节点名为receiver,一旦接收器在AndroidManifest.xml中注册,就无需在代码中注册。
在AndroidManifest.xml中注册接收器,该方式被称作静态注册;在代码中注册接收器,该方式被称作为动态注册。之所以罕见静态注册,是因为静态注册容易导致安全问题,故而Android 8.0之后废弃了大多数静态注册。话虽如此,Android倒也没有彻底禁止静态注册,只要满足特定的编码条件,那么依然能够通过静态方式注册接收器。具体注册步骤说明如下。
1.点击当前模块的默认包,依次选择右键菜单的New→Package,创建名为receiver的新包,用于存放静态注册的接收器代码。
2.右击刚创建的receiver包,依次选择右键菜单的New→Other→Broadcast Receiver,弹出如下广播组件的创建对话框。
3.在组件对话框的Class Name一栏填写接收器的类名,比如ShockReceiver,再单机对话框右下角的Finish按钮。之后Android Studio自动再receiver包内创建代码文件ShockReceiver.java,且接收器默认代码如下:
- public class ShockReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO: This method is called when the BroadcastReceiver is receiving
- // an Intent broadcast.
- throw new UnsupportedOperationException("Not yet implemented");
- }
- }
同时AndroidManifest.xml自动添加接收器的节点配置,默认的receiver配置如下:
- <receiver
- android:name=".receiver.ShockReceiver"
- android:enabled="true"
- android:exported="true"></receiver>
因为ShockReceiver未依附于任何活动,自然无法直接操作界面控制,所以只能观察程序日志,或者干脆让手机摇晃起来。实现手机震动,要调用getSystemService方法,先从系统服务VIBRATOR_SERVICE获取震动管理器Vibrator,再调用震动管理器的vibrate方法震动手机。
- public class ShockReceiver extends BroadcastReceiver {
- private static final String TAG = "ShockReceiver";
- // 静态注册时候的action、发送广播时的action、接收广播时的action,三者需要保持一致
- public static final String SHOCK_ACTION = "com.example.helloandroid.shock";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive");
- if (intent.getAction().equals(ShockReceiver.SHOCK_ACTION)){
- // 从系统服务中获取震动管理器
- Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- vb.vibrate(500); // 命令震动器吱吱个若干秒,这里的500表示500毫秒
- }
- }
- }
由于震动手机需要申请对应的权限,因此打开AndroidManifest.xml添加以下的权限申请配置
- <!-- 震动 -->
- <uses-permission android:name="android.permission.VIBRATE" />
此外,接收器代码定义了一个动作名称,其值为"com.example.helloandroid.shock",表示onReceive方法只处理过滤该动作之后的广播从而提高接收效率。除了在代码中过滤之外,还能修改AndroidManifest.xml,在receiver节点内部增加intent-filter标签加以过滤。
- <receiver
- android:name=".receiver.ShockReceiver"
- android:enabled="true"
- android:exported="true" >
- <intent-filter>
- <action android:name="com.example.helloandroid.shock"/>
- </intent-filter>
- </receiver>
由于Android 8.0之后删除了大部分静态注册,防止App退出后仍在收听广播,因此为了让应用能够继续接收静态广播,需要给静态广播指定包名,也就是调用意图对象的setComponent方法设置组件路径.
- public class BroadcastType3 extends AppCompatActivity implements View.OnClickListener {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_broadcast_type3);
- findViewById(R.id.btn_send_shock).setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- if (v.getId() == R.id.btn_send_shock) {
- // Android8.0之后删除了大部分静态注册,防止退出App后仍在接收广播,
- // 为了让应用能够继续接收静态广播,需要给静态注册的广播指定包名。
- String receiverPath = "com.example.chapter04.receiver.ShockReceiver";
- Intent intent = new Intent(ShockReceiver.SHOCK_ACTION); // 创建一个指定动作的意图
- // 发送静态广播之时,需要通过setComponent方法指定接收器的完整路径
- ComponentName componentName = new ComponentName(this, receiverPath);
- intent.setComponent(componentName); // 设置意图的组件信息
- sendBroadcast(intent); // 发送静态广播
- Toast.makeText(this, "已发送震动广播", Toast.LENGTH_SHORT).show();
- }
- }

注 意 |
---|
经过整改的静态注册只适用于接收App自身的广播,不能接收系统广播,也不能接收其他的广播。 |
Android提供了专门的时间管理器AlarmManager,它利用系统闹钟定时发送广播,能够让App实现定时功能。由于闹钟与震动器同属系统服务,且闹钟的服务名称为ALARM_SERVICE,因此依然调用getSystemService方法获取闹钟管理器的实例。
- //从系统服务中获取闹钟管理器
- AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
得到闹钟实例后,即可调用它的各种方法设置闹钟规则了,AlarmManager的常见方法说明如下:
以上的方法说明出现了新名词——延迟意图,它是PendingIntent类型,顾名思义,延迟意图不是马上执行的意图,而是延迟若干时间才执行的意图。意图与延迟意图的差异主要有下列3点:
就闹钟广播的收发过程而言,需要实现3个编码步骤:定义定时器的广播接收器、开关定时器的广播接收器、设置定时器的播报规则,分别叙述如下。
闹钟广播的接收器采用动态注册方式,它的实现途径与标准广播类似,都要从BroadcastReceiver派生的接收器,并重写onReceiver方法。
- //声明一个闹钟广播事件的标识串
- private String ALARM_ACTION = "com.example.helloandroid.alarm";
- private String mDesc = ""; //闹钟时间到达的描述
- //定义一个闹钟广播的接收器
- public class AlarmReceiver extends BroadcastReceiver {
- //一旦接收到闹钟时间到达的广播,马上触发接收器的onReceive方法
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null) {
- mDesc = String.format("%s\n%s 闹钟时间到达", mDesc,DateUtil.getNowTime());
- tv_alarm.setText(mDesc);
- //从系统服务中获取震动管理器
- Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- vb.vibrate(500); //命令震动器震动若干秒
- }
- }
- }

定时接收器的开关流程参照标准广播,可以在活动页面的onStart方法中注册接收器,在活动页面的onStop方法中注销接收器。
- private AlarmReceiver alarmReceiver; //声明一个闹钟的广播接收器
- @Override
- public void onStart() {
- super.onStart();
- alarmReceiver = new AlarmReceiver(); //创建一个闹钟的广播接收器
- //创建一个意图过滤器,只处理指定事件来源的广播
- IntentFilter filter = new IntentFilter(ALARM_ACTION);
- //注册接收器,注册之后才能正常接收广播
- registerReceiver(alarmReceiver,filter);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- unregisterReceiver(alarmReceiver); //注销接收器,注销之后就不再接收广播
- }

首先从系统服务中获取闹钟管理器,然后调用管理器的set***方法,把事先创建的延迟意图填到播报规则当中。
- // 发送闹钟广播
- private void sendAlarm() {
- Intent intent = new Intent(ALARM_ACTION); // 创建一个广播事件的意图
- // 创建一个用于广播的延迟意图
- PendingIntent pIntent = PendingIntent.getBroadcast(this, 0,
- intent, PendingIntent.FLAG_UPDATE_CURRENT);
- // 从系统服务中获取闹钟管理器
- AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
- long delayTime = System.currentTimeMillis() + mDelay*1000; // 给当前时间加上若干秒
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- // 允许在空闲时发送广播,Android6.0之后新增的方法
- alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, delayTime, pIntent);
- } else {
- // 设置一次性闹钟,延迟若干秒后,携带延迟意图发送闹钟广播(但Android6.0之后,set方法在暗屏时不保证发送广播,必须调用setAndAllowWhileIdle方法)
- alarmMgr.set(AlarmManager.RTC_WAKEUP, delayTime, pIntent);
- }
- }

ps. 因内置模拟器在模拟此功能时会出现闪退现象,顾选用外部模拟器进行模拟,我选用的是夜神模拟器
至于闹钟重复播报问题,因为setRepeating方法不再可靠,所以要修改闹钟的收听逻辑,在onReceive末尾补充调用sendAlarm方法,确保每次收到广播之后立即准备下一个广播。
- // 定义一个闹钟广播的接收器
- public class AlarmReceiver extends BroadcastReceiver {
- // 一旦接收到闹钟时间到达的广播,马上触发接收器的onReceive方法
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent != null) {
- mDesc = String.format("%s\n%s 闹钟时间到达", mDesc, DateUtil.getNowTime());
- tv_alarm.setText(mDesc);
- // 从系统服务中获取震动管理器
- Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- vb.vibrate(500); // 命令震动器吱吱个若干秒
- if (ck_repeate.isChecked()) { // 需要重复闹钟广播
- sendAlarm(); // 发送闹钟广播
- }
- }
- }
- }

Java:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_broadcast_type4);
- ck_repeate = findViewById(R.id.ck_repeate);
- tv_alarm = findViewById(R.id.tv_alarm);
- findViewById(R.id.btn_alarm).setOnClickListener(this);
- initDelaySpinner(); // 初始化闹钟延迟的下拉框
- }
-
- // 初始化闹钟延迟的下拉框
- private void initDelaySpinner() {
- ArrayAdapter<String> delayAdapter = new ArrayAdapter<String>(this,
- R.layout.item_select, delayDescArray);
- Spinner sp_delay = findViewById(R.id.sp_delay);
- sp_delay.setPrompt("请选择闹钟延迟");
- sp_delay.setAdapter(delayAdapter);
- sp_delay.setOnItemSelectedListener(new DelaySelectedListener());
- sp_delay.setSelection(0);
- }
-
- private int[] delayArray = {5, 10, 15, 20, 25, 30};
- private String[] delayDescArray = {"5秒", "10秒", "15秒", "20秒", "25秒", "30秒"};
- class DelaySelectedListener implements OnItemSelectedListener {
- public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
- mDelay = delayArray[arg2];
- }
-
- public void onNothingSelected(AdapterView<?> arg0) {}
- }

xml:
- <Spinner
- android:id="@+id/sp_delay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_toRightOf="@+id/tv_delay"
- android:gravity="left|center"
- android:spinnerMode="dialog"/>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。