Android:安卓学习笔记之广播机制的简单理解和使用

Posted JMW1407

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:安卓学习笔记之广播机制的简单理解和使用相关的知识,希望对你有一定的参考价值。

广播机制

广播简介

Broadcast是安卓四大组件之一。安卓为了方便进行系统级别的消息通知,引入了一套广播消息机制。打个比方,记得原来在上课的时候,每个班级的教室里都会装有一个喇叭,这些喇叭都是接入到学校的广播室的,一旦有什么重要通知,如上课铃下课铃,学校就会播放一条广播来告知全校的师生。

为什么会说android中的广播机制更加灵活呢?

  • 这是因为Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广播可能来自于系统,也可能是来自于其他应用程序的。

Android 引入广播机制的用意
引入广播机制可以方便几大组件的信息和数据交互。

  • b.程序间互通消息(例如在自己的应用程序内监听系统来电)
  • c.效率上(参考 UDP 的广播协议在局域网的方便性)
  • d.设计模式上(反转控制的一种应用,类似监听者模式)

问:了解广播吗?

答:

  • 广播,是一个全局的监听器,属于Android四大组件之一。
  • 广播用到了观察者模式,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展。

基于消息的发布 / 订阅事件模型,其中有三个角色:

1)消息订阅者(广播接收者)  
(2)消息发布者(广播发布者)
(3)消息中心(AMS,即Activity Manager Service)

暂且简称为订阅者、发布者和AMS吧

步骤:

  • 订阅者通过Binder机制在AMS里注册
  • 发布者通过Binder机制异步给AMS发送广播
  • AMS根据发布者的要求找到对应的订阅者
  • AMS发送广播到对应的订阅者的消息循环队列中
  • 订阅者通过消息循环拿到广播,并回调onReceive


广播分为两大类

1.标准广播(Normal broadcasts)

是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接受到这条广播消息,因此它们之间没有任何先后顺序可言,这种广播的效率会比较高,但同时也意味着它是无法被拦截的。

2.有序广播(Ordered broadcasts)

是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接受器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。

注册方式

静态注册广播

在android studio中我们可以直接创建一个配置好的BroadReceiver

android studio自动给我们在AndroidManifest.xml添加相关配置

我们如果自己通过继承BroadcastReceiver类来实现一个receiver,我们就必须手动在AndroidManifest.xml添加相关配置

MainActivity.java

package cn.liuhao.test_broadreceiver;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity 

    private Button send_btn;

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

        send_btn=this.findViewById(R.id.button);
        send_btn.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                // 广播也是一个intent
                Intent intent=new Intent();
                // 指定广播的action,这个关系到receiver接受
                intent.setAction("broad_path");
                // 在android8.0版本以上,不支持隐式的广播接收者,想象一下,一个无序广播,假设在设备开机的时候,有一百个的程序通过广播的形式被唤醒,那设备性能就会大大降低
                // 在android8.0版本以上,必须指定广播接收者
                // 通过设置intent的Component属性,接受一个ComponentName对象
                // ComponentName对象,参数1:指定广播接收者所在项目的包路径(这个包路径是项目的包路径,而不是广播接收者的类所在的包名)
                intent.setComponent(new ComponentName("cn.liuhao.test_broadreceiver","cn.liuhao.test_broadreceiver.receiver.MyReceiver"));
                // 发送广播
                MainActivity.this.sendBroadcast(intent);
            
        );
    



动态注册广播

package cn.liuhao.test_broadreceiver;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity 

    private Button send_btn;

    // 声明receiver
    private CustomBroadReceiver receiver;

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


        // 创建receiver实例
        receiver = new CustomBroadReceiver();
        // 创建IntentFilter
        IntentFilter filter = new IntentFilter();
        filter.addAction("broad_path"); // 这个对应这发送广播时set的Action属性
        // 通过registerReceiver方法动态注册
        registerReceiver(receiver, filter);

        send_btn = this.findViewById(R.id.button);
        send_btn.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 


                // 定义广播
                Intent intent = new Intent();
                // 指定广播的action,这个关系到receiver接受
                intent.setAction("broad_path");
                // 发送广播
                MainActivity.this.sendBroadcast(intent);
            
        );
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        // 所有动态注册的广播最后都应该调用unreregisterReceiver(BroadcastReceive类的实现类)方法取消注册。
        unregisterReceiver(receiver);
    

    /**
     * 我们通过自定类继承BroadcastReceiver来实现一个自定义广播接收者
     * <p>
     * 因为是我们手动定义的,所以不会自动在manifest.xml中静态注册
     * <p>
     * 我们通过动态注册的方式来注册
     */
    class CustomBroadReceiver extends BroadcastReceiver 

        @Override
        public void onReceive(Context context, Intent intent) 

            throw new UnsupportedOperationException("Not yet implemented");
        

    

两种注册各有什么优缺点

静态注册

  • 常驻,当应用程序关闭后如果有信息广播来,程序也会被系统调用,自己运行。
  • 无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器就是打开着的。

动态注册

  • 不常驻,广播会跟随程序的生命周期。
  • 在 Android 的广播机制中,动态注册优先级高于静态注册优先级,因此在必要情况下,是需要动态注册广播接收者的。
  • 当用来注册的 Activity 关掉后,广播也就失效了。

发送无序广播

使用步骤:

  • 1.获取Intent示例,创建实例时在构造器中加入你想要发出的广播消息。
  • 2.调用intent.setPackage(广播接收器的包名)方法为广播指明方向。

(android8.0以后且广播接收器是静态注册的才需要这步操作)

  • 3.调用sendBroadcast(Intent)方法发送标准广播。
 Intent intent=new Intent("com.example.temp02.MY_BROADCAST");//步骤一
 intent.setPackage("com.example.temp02");//步骤二
 sendBroadcast(intent);//步骤三

特征

完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播intent 的传播。

发送有序广播

使用步骤:

  • 1.获取Intent示例,创建实例时在构造器中加入你想要发出的广播消息。
  • 2.调用intent.setPackage(广播接收器的包名)方法为广播指明方向。

(android8.0以后且广播接收器是静态注册的才需要这步操作)

  • 3.调用sendOrderedBroadcast(Intent,null);方法发送有序广播。
 Intent intent=new Intent("com.example.temp02.MY_BROADCAST");//步骤一
 intent.setPackage("com.example.temp02");//步骤二
 sendOrderedBroadcast(intent,null);//步骤三

特征

按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C,优先级是 A > B > C。那这个消息先传给A,再传给 B,最后传给 C。每个接收者有权终止广播,比如 B 终止广播,C 就无法接收到。此外 A接收到广播后可以对结果对象进行操作,当广播传给 B 时, B 可以从结果对象中取得 A 存入的数据。

常见问题

1、怎样设置有序广播的接收顺序呢?

答:只需在广播接收器的< intent-filter >标签里加入android:priority属性给广播接收器设置优先级即可,优先级较高的广播接收器会先接收到广播(最大值为100)。

2、前面的广播接收器怎样截断正在传递的广播呢?

答:只需在广播接收器的onReceive()方法中加入abortBroadcast()方法即可截断该广播,使其优先级之后的广播接收器不能再接收到该广播。

3、BrocastReceiver 里可不可以执行耗时操作

BroadCastReceiver 的生命周期很短暂,当接收到广播的时候创建,当onReceive()方法结束后销毁

因为BroadCastReceiver的声明周期很短暂,所以不要在广播接收器中去创建子线程做耗时的操作,因为广播接受者被销毁后,这个子进程就会成为空进程,很容易被杀死

广播引起 ANR 的时间限制是10s,BroadCastReceiver是运行在主线程的,所以不能直接在BroadCastReceiver中去做耗时的操作,否则就会出现ANR异常

4、BroadcastReceiver,LocalBroadcastManager 区别

应用场景

  • 1、BroadcastReceiver 用于应用之间的传递消息。
  • 2、LocalBroadcastManager 用于应用内部传递消息,比 BroadcastReceiver 更加高效。

安全

  • BroadcastReceiver 本质上它是跨应用的,所以在使用它时必须要考虑到不要被别的应用滥用。
  • LocalBroadcastManager 不需要考虑安全问题,因为它只在应用内部有效。

原理

  • BroadcastReceiver 是以 Binder 通讯方式为底层实现。
  • LocalBroadcastManager 的核心实现是 Handler,只是利用到了 IntentFilter 的 match功能,至于 BroadcastReceiver 换成其他接口也无所谓,顺便利用了现成的类和概念而已,安全性更好,效率更高。

5、如何通过广播拦截和 abort 一条短信

1、首先添加接收短信的权限

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

2、在清单文件中注册广播接收器,设置该广播接收器优先级,尽量设高一点。

3、创建一个 BroadcastReceiver 来实现广播的处理,并设置拦截器 abortBroadcast()。

6、广播传输的数据是否有限制,是多少,为什么要限制

1、你用 Intent 传递数据,实际上走的是跨进程通信(IPC),跨进程通信需要把数据从内核 copy 到进程中,每一个进程有一个接收内核数据的缓冲区,默认是 1 M。如果一次传递的数据超过限制,就会出现异常。

2、不同厂商表现不一样有可能是厂商修改了此限制的大小,也可能同样的对象在不同的机器上大小不一样。

3、传递大数据,不应该用 Intent;考虑使用 ContentProvider 或者直接匿名共享内存。简单情况下可以考虑分段传输。

7、动态广播最好在Activity 的 onResume()注册、onPause()注销

原因:

  • 1、对于动态广播,有注册就必然得有注销,否则会导致内存泄露
  • 2、重复注册、重复注销也不允许

Activity生命周期的方法是成对出现的:

onCreate() & onDestory()
onStart() & onStop()
onResume() & onPause()

1、在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。

2、不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足(优先级更高的应用需要内存,请看上图红框)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。

3、假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。

8、对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

1、对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext

2、对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context

3、对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context

4、对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context

参考

1、android的广播机制
2、Android面试题(3)–Broadcast
3、Android面试题之Broadcast Receiver篇
4、BroadcastReceiver史上最全面解析

以上是关于Android:安卓学习笔记之广播机制的简单理解和使用的主要内容,如果未能解决你的问题,请参考以下文章

Android :安卓学习笔记之 事件分发机制 的简单理解和使用

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android:安卓学习笔记之Binder 机制的简单理解和使用

Android :安卓学习笔记之事件内存泄露 的简单理解

Android:安卓学习笔记之共享元素的简单理解和使用

Android:安卓学习笔记之共享元素的简单理解和使用