Android Studio 学习记录-收发应用广播

目录

收发标准广播

1.发送标准广播

2.定义广播接收器

3.开关广播接收器

​编辑​编辑

 收发有序广播

1.发送广播时要注明这是个有序广播

2.定义有序广播的接收器

3.注册有序广播的多个接收器

​编辑​编辑收发静态广播 

 定时管理器 AlarmManager

1.定义定时器的广播接收器

2.开关定时器的广播接收器

3.设置定时器的播报规则

补充: 闹钟延迟的下拉框设置


        本文介绍应用广播的几种收发形式,包括如何收发标准广播、如何收发有序广播、如何收发静态广播、如何监听定时管理器发出的系统闹钟广播等。

收发标准广播

        Android的广播机制借鉴了Wifi的通讯原理,不必搭建专门的通路,就能在发送方与接收方之间建立连接。同时广播(Broadcast)也是Android的四大组件之一,它用于Android各组件之间的灵活通信,与活动的区别在于:

  1. 活动只能一对一通信,而广播可以一对多,一人发送广播,多人接收处理。
  2. 对于发送方来说,广播不需要考虑接收方有没有在工作,接收方在工作就接收广播,不在工作就丢弃广播。
  3. 对于接收方来说,因为可能会接收到格式各样的广播,所以接收方要自行过滤符合条件的广播,之后再解包处理。

        与广播有关的方法主要有已下3个

  • sendBroadcast:发送广播。
  • registerReceiver:注册广播的接收器,可在onStart或onResume方法中注册接收器。
  • unregisterReceiver:注销广播的接收器,可在onStop或onPause方法中注销接收器。

        广播的收发过程可分为3个步骤:发送标准广播、定义广播接收器、开关广播接收器,分别说明如下:

1.发送标准广播

        广播的发送操作很简单,一共只有两步:先创建意图对象,再调用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);                          //发送标准广播
        }
    }
}

2.定义广播接收器

        广播发出来后,还得有设备去接收广播,也就是需要广播接收器。接收器主要规定两个事情:一个是接收什么样的广播,另一个是接收到广播以后要做什么。由于接收器的处理逻辑大同小异,因此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);
            }
        }
    }

3.开关广播接收器

        为了避免资源浪费,还要求合理使用接收器。活动页面启动之后才注册接收器,活动页面停止之后就注销接收器。在注册接收器的时候,允许事先指定只接收某种类型的广播,即通过意图过滤器挑选动作名称一致的广播。

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个编码步骤:

1.发送广播时要注明这是个有序广播

        之前发送标准广播用到了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);                  //发送有序广播
        }
    }
}

2.定义有序广播的接收器

        接收器的定义代码基本不变,也要从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();               //中断广播,此时后面的接收器无法收到该广播
                }
            }
        }
    }

3.注册有序广播的多个接收器

        接收器的注册操作同样调用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自身的广播,不能接收系统广播,也不能接收其他的广播。

 定时管理器 AlarmManager

        Android提供了专门的时间管理器AlarmManager,它利用系统闹钟定时发送广播,能够让App实现定时功能。由于闹钟与震动器同属系统服务,且闹钟的服务名称为ALARM_SERVICE,因此依然调用getSystemService方法获取闹钟管理器的实例。

//从系统服务中获取闹钟管理器
AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);

        得到闹钟实例后,即可调用它的各种方法设置闹钟规则了,AlarmManager的常见方法说明如下:

  • set:设置一次性定时器。第一个参数为定时器类型,通常填alarmManager.RTC_WAKEUP;第二个参数为期望的执行时刻(单位为毫秒);第三个参数为待执行的延迟意图(PendingIntent类型)。
  • setAndAllowWhileIdle:设置一次性定时器,参数说明同set方法,不同之处在于:即使设备处于空闲状态,也会保证执行定时器。因为从Android 6.0开始,set方法在暗屏时不保证发送广播,必须调用setAndAllowWhileIdle方法才能保证发送广播。
  • setRepeating:设置重复定时器。第一个参数为定时器类型;第二个参数为首次执行时间(单位为毫秒);第三个参数为下次执行的时间间隔(单位为毫秒);第四个参数为待执行的延迟意图(PendingIntent类型)。然而setRepeating方法不保证按时发送广播,只能通过setAndAllowWhileIdle方法间接实现重复定时功能。
  • cancel:取消指定延迟意图的定时器。

        以上的方法说明出现了新名词——延迟意图,它是PendingIntent类型,顾名思义,延迟意图不是马上执行的意图,而是延迟若干时间才执行的意图。意图与延迟意图的差异主要有下列3点:

  1. PendingIntent代表延迟的意图,它指向的组件不会马上被激活;而Intent代表实时的意图,一旦被启动,它指向的组件就会马上被激活。
  2. PendingIntent是一类消息的组合,不但包含目标的Intent对象,还包含请求代码、请求方式等信息。
  3. PendingIntent对象在创建之时便已知晓要用于活动还是广播,例如调用getActivity方法得到的是活动跳转的延迟意图,调用getBroadcast方法得到的是广播发送的延迟意图。

        就闹钟广播的收发过程而言,需要实现3个编码步骤:定义定时器的广播接收器、开关定时器的广播接收器、设置定时器的播报规则,分别叙述如下。

1.定义定时器的广播接收器

        闹钟广播的接收器采用动态注册方式,它的实现途径与标准广播类似,都要从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);        //命令震动器震动若干秒
            }
        }
    }

2.开关定时器的广播接收器

        定时接收器的开关流程参照标准广播,可以在活动页面的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);      //注销接收器,注销之后就不再接收广播
    }

3.设置定时器的播报规则

        首先从系统服务中获取闹钟管理器,然后调用管理器的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"/>

猜你喜欢

转载自blog.csdn.net/demon_dog/article/details/129063709