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 机制的简单理解和使用