广播发送和接收的设计模式
1.1 观察者模式定义
广播的发送和接收是采用的观察者模式的设计模式,观察者模式(Observer Pattern)定义了对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新,观察者模式又叫做发布-订阅(Publish/Subscribe)模式。观察者模式主要用来解耦,将被观察者和观察者解耦,让他们之间没有没有依赖或者依赖关系很小。
2.2 观察者模式核心
观察者模式的核心就是用一个list把观察者保存起来,并提供add和remove观察者,在被观察者变化的时候就遍历并调用list里观察者的方法。核心就是一个list遍历
2.3 观察者模式总结
观察者模式就是将观察者和被观察者彻底隔离,实现解耦,只依赖于我们定义的抽象。具有观察者和被观察者解耦以及增强灵活性的优点,但是同时由于Java代码的顺序执行,要考虑被观察者的执行效率,多个观察者要及时响应就要考虑异步的问题了。
动/静态注册和优缺点对比
2.1静态注册
2.1.1 定义
静态注册是在AndroidManifest.xml 中,通过标签来声明。
2.2.2 优点
不受其他组件生命周期影响,即使应用程序被关闭,也能接收广播。
2.2.3 缺点
因为即使应用程序被关闭,也能接收广播,监听app启动。静态注册的特点是接收设备全局的相应广播,尽管静态注册看起来比较方便,但实际上是十分耗费资源的。不管需不需要,静态注册的广播被触发时总是会使你的应用被启动。而应用的启动,无疑是会消耗手机资源的。如果一个应用使用静态注册监听手机电量的,那么应用将无法真正停止,十分耗费手机的资源。应用的开发是为用户服务,创造更好的体验,应该尽量减少资源的消耗,因此,静态注册的Broadcast需要谨慎使用。
2.2 动态注册
2.2.1 定义
动态注册是在代码中注册的,在需要开启的时候通过代码实现开启
2.2.3 优点
灵活,不耗电,易控,省内存,动态注册使得广播的接收更加的灵活,因为大部分广播接收器是没有一直开启的必要的,动态注册可以使用户在需要监听某一广播的时候开启接收器,实现在特点的时候监听广播,这也能够实现应用真正的停止,减少了系统资源的消耗。
2.2.3 缺点
动态注册的广播接收器,一定要通过unregisterReceiver方法注销广播接收器,因为广播机制其实是观察者模式,会把所有接收到的广播保存在内存中,如果不取消注册,可能会导致内存泄漏。
广播接收和发送的系统内部机制
3.1 Android广播机制作用概述
经过更加深入的学习,发现广播机制不仅是用于应用之间的通信,还有很多可以使用的场景:
1)同一app内部的同一组件内的消息通信(单个或多个线程之间);
2)同一app内部的不同组件之间的消息通信(单个进程);
3)同一app具有多个进程的不同组件之间的消息通信;
4)不同app之间的组件之间消息通信;
5)Android系统在特定情况下与App之间的消息通信。
3.2 广播机制具体实现流程
1)广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;
2)广播发送者通过binder机制向AMS发送广播;
3)AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;
4)消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。
简单了解IPC和Binder机制
4.1 IPC概念
IPC是Inter-Process Communication的缩写,其含义是进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。Android中IPC的基础概念主要包含三方面的内容:Serializable接口、Parcelable接口以及Binder,Serializable和Parcelable接口可以完成对象的序列化过程(将对象的状态信息转换为可以存储或传输的形式的过程),当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable来完成对象的持久化。
4.1.1 Serializable接口
Serializable是Java提供的一个用于为对象标准的序列化和反序列化操作的接口,使用Seralizable来实现序列化十分简单,想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,如下:
public class User implements Serializable {
private static final long serialVersionUID = 3110845084273175296L;
之后采用ObjectOutputStream和ObjectInputStream即可轻松实现。
4.1.2 Parcelable接口
Parcelable也是一个接口,作用与Serializable 接口类似,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。
4.2 Binder机制
4.2.1 Binder的概念
直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种基于C/S结构的、面向对象的IPC机制。包含:Client、Server、Binder驱动和ServiceManager四大组成部分。
从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据。
4.2.2 Android为什么采用Binder机制
Android系统是基于Linux内核实现的,Linux已经提供了多种IPC机制,比如:管道、消息队列、共享内存和套接字(Socket)等等,但是还是实现了一套IPC机制,原因有以下两点:
[if !supportLists]1)[endif]安全性考虑
传统的IPC机制没有安全措施,接收方无法获得对方可靠的进程ID或用户ID,完全靠上层的协议来保护,比如Socket通信的IP地址是客户端填入的,很可能被恶意程序篡改。Android系统为每个已安装的App都分配了用户ID(UID),UID是鉴别进程身份的重要标识,通过UID可以进行一系列的权限校验。另一方面 ,传统IPC的接入点是开放的,任何程序都可以根据协议进行访问,无法阻止恶意程序的访问,Android需要一种基于C/S架构的IPC机制,Server端需要能够对Client的请求进行身份校验,来保证数据的安全性。
[if !supportLists]2)[endif]性能考虑
管道、消息队列、Socket实现一次进程通信都需要2次内存拷贝,效率太低
需要进行2次数据拷贝,第1次是从发送方用户空间拷贝到内核缓存区,第2次是从内核缓存区拷贝到接收方用户空间。发送方进程、内核缓存区进程,接收方进程的虚拟内存映射到三块不同的物理内存中,所以实际上做了做了两次拷贝。
Binder只需要一次内存拷贝,从性能角度来看,优于其它方式。Binder在内核空间和接收方用户空间的数据缓存区之间额外做了一层内存映射,使得从发送方用户空间拷贝到内核空间缓存区的数据,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。
4.2.3 Binder通信模型
具体的通信过程是这样的:
[if !supportLists]1)[endif]Server向ServiceManger注册:Server通过Binder驱动向ServiceManager注册,声明可以对外提供服务。ServiceManager中会保留一份映射表:名字为XXX的Server对应的Binder引用是XXXX。
[if !supportLists]2)[endif]Client向ServiceManger请求Binder的引用:Client想要请求Server的数据时,需要先通过Binder驱动向ServiceManager请求Server的Binder引用:我要向名字为XXX的Server通信,请告诉我Server的Binder引用。
[if !supportLists]3)[endif]向具体的Server发送请求:Client通过ServiceManager拿到Binder引用之后,就可以通过Binder驱动和Server进行通信了。
[if !supportLists]4)[endif]Server返回结果:Server响应请求后,需要再次通过Binder驱动将结果返回给Client。
自定义广播发送和接收
5.1 动态注册自定义广播并发送
1)新建一个广播:Intent intent = new Intent(MY_ACTION);其中MY_ACTION是一个自定义的常量,用来表示广播的名字,这就是一条自定义的广播,可以通过Intent.putExtra()方法来将要携带的数据放入广播中,最后调用sendBroadcast(intent)方法将广播发送出去。
Demo在附件中的MyBroadcastDemo3
5.2 接收自定义广播
[if !supportLists]1)[endif]首先创建自己的广播,定义一个类,继承于BroadcastReceiver,并重写其onReceive方法。
[if !supportLists]2)[endif]因为BroadcastDemo3中向外发送广播的时候通过Intent.putExtra()方法将要携带的数据放入了广播中,所以在广播接收器的onReceive方法中可以通过Intent.getStringExtra()方法将广播携带的数据拿到,再通过TextView.setText()方法将得到的字符串类型的数据显示在TextView控件中。
[if !supportLists]3)[endif]因为广播接收器的onReceive方法是运行在主线程中的,所以不能在里面执行太过耗时的操作,虽然此处的操作并不耗时,但是还是为它开辟了一个子线程,将TextView.setText()方法放入了子线程去执行。
Tips:因为控件所在布局是绑定在Activity中的,所以MyReceiver类中无法直接获取到TextView实例化的对象,这里可以通过给MyReceiver类添加一个含参构造函数,参数是TextView类型的对象,之后在Activity中实例化MyReceiver的时候把TextView对象传进去就可以实现在onReceiver方法中对activty中绑定的TextView控件进行操作。
Demo在附件中的ReceiverDemo中。