代理模式是什么
如上图所示,代理代表着另一终端中的某个真实服务对象,Client 调用代理(Client helper)的方法,然后将请求通过网络与真正的服务对象进行沟通。
例如 Windows 的快捷方式就是一种代理,用户点击快捷方式,认为自己在跟实际应用交流,实际上是快捷方式去调用了真正的应用程序。
代理模式在 Java RMI 的应用
RMI 是什么 ?
RMI(Remote Method Invocation)是 J2SE 的一部分,使用它能开发出基于 Java 的分布式应用。一个 RMI 对象可以像调用本地 Java 对象的方法一样调用远程对象的方法,使分布在不同的 JVM 中的对象的外表和行为都像本地对象一样。
RMI 使用 jrmp(Java Remote Messaging Protocol)进行通信,采用 tcp/ip 协议,且只能基于 Java 语言。
RMI 如何实现代理模式?
RMI 提供了客户辅助对象(Stub)和服务辅助对象(Skeleton),为这两者创建了相同的方法。RMI 远程调用的过程图如下所示:
RMI 方法调用过程
- 客户对象调用 Stub 的方法
- Stub 打包信息,通过网络发送给 Skeleton
- Skeleton 解包信息,找出被调用的方法和对象并调用
- 服务对象调用方法,将结果返回 Skeleton
- Skeleton 打包信息,通过网络发送给 Stub
- Stub 解包信息,将结果返回给客户对象
RMI 搭建 RMI 服务
制作远程接口
远程接口会被 Stub,Skeleton 和真实服务对象实现,这样客户对象就能知道服务对象都有哪些方法。
在不同终端都需要创建远程接口类。
远程接口要继承自 Remote 接口,用于标识所包含的方法能被远程调用,如下所示:
public interface MyRemote extends Remote {
public String sayHello() throws RemoteException;
}
创建远程接口实现类
服务对象就是远程接口实现类,是真正执行方法提供服务的类。需要实现远程接口,如下所示:
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
return "Server says Hello";
}
生成 Stub 和 Skeleton
rmic 是 JDK 内的一个工具,用来服务对象生成 Stub 和 Skeleton,是在服务端代码的基础上生成的,所以需要 MyRemoteImpl.class 文件。
%rmic MyRemoteImpl
这样就 rmic 就会生成两个服务对象,MyRemoteImpl_Stub.class 和 MyRemoteImpl_Skel.class。
启动 RMI 注册服务(RMI Registry)
RMI Registry 类似于电话簿或计算机网络中的 DNS 功能,服务对象在此注册,客户对象则在此寻找需要的 Stub。
启动RMI Registry:
%rmiregistry
注册并启动服务
服务对象必须在 RMI Registry 注册后,才能被客户对象找到。
注册服务:
try {
MyRemote service = new MyRemoteImpl();
Naming.rebind("RemoteHello", service);
} catch (Exception ex) {...}
启动服务:
%java MyRemoteImpl
客户对象获取 Stub 并调用方法
同样的,客户对象从 RMI Registry 中取得 Stub,就可以调用方法了。
try {
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
String s = service.sayHello();
} catch (Exception ex) {
ex.printStackTrace();
}
RMI 搭建的注意事项
学习了解 RMI 实现远程代理的方式,有很大的借鉴意义,因为在 Android IPC 通信中,就能够看到许多 RMI 的影子。
同时要注意, 调用方法的『参数』和『返回值』都必须是可序列化的类型,因为这些变量都会被打包通过网络传输。
代理模式在 Android 的应用
Android 源码中有许多关于代理模式的实现,这里主要了解 Android 中的 Binder 机制和 AIDL。这两者都是 Android 跨进程通信机制中十分重要的概念。
Binder 跨进程通信机制
Binder 概述
在计算机中,为使两个不同进程中的对象能够互相访问,需要使用到跨进程通信技术。传统的跨进程通信机制有:Socket,内存共享,消息队列等。
由于传统的跨进程通信机制,开销大安全性低,Android 自己设计了一个新的通信机制:Binder。
Binder 基于 Client-Server 通信模式,为发送方添加 UID/PID 身份,在确保传输性能的同时又提高了安全性。
Binder 的四大模块
Binder 涉及到的四个主要模块分别是 Binder Client、Binder Server、ServerManager 和 Binder Driver。
- Binder Client 相当于客户端
- Binder Server 相当于服务器
- ServerManager 相当于 DNS 服务器
- BinderDriver 相当于路由器
其中 Binder Driver 实现于内核空间中,其余三者实现于用户空间中。
Binder Driver
Binder Driver 主要负责 Binder 通信的建立,以及其在进程间的传递和 Binder 引用计数管理/数据包的传输等。Binder Client 和 Binder Server 之间的跨进程通信统一通过 Binder Driver 处理转发。
Binder Client
Binder Client 只需要知道自己要使用的 Binder 的名字及其在 ServerManager 中的引用即可获取该 Binder 的引用,得到引用后就可以像普通方法调用一样调用 Binder 实体的方法。
Binder Server
Binder Server 在生成一个 Binder 实体时会为其绑定一个名称并传递给 Binder Driver,Binder Driver 会在内核空间中创建相应的 Binder 实体节点和节点引用,并将引用传递给 ServerManager。
ServerManager 会将该 Binder 的名字和引用插入一张数据表中,这样 Binder Client 就能够获取该 Binder实体 的引用,并调用上面的方法。
ServerManager
ServerManager 相当于 DNS服务器,负责映射 Binder 名称及其引用。其本质同样是一个标准的 Binder Server。
ServerManager 和在 Binder Client 端的 ServiceManagerNative、ServiceManagerProxy 都实现了 IServiceManager 接口,该接口定义了 ServerManager 对外公布的方法。这样 Client 就能通过这些方法获得 Binder 引用。
Binder 的总结
从 Binder 的四个模块可以看出其与 Java RMI 有着许多相似之处,但其实 Android 的 Binder 机制是一个庞大的体系模块,其实现要复杂多很多。
当我们想使用 Binder 进行进程间通信时,Android 已将 Binder Driver 和 ServerManager 封装得很完美了,我们只需实现自己的 Binder Client 和 Binder Server 即可。
Android 提供了 AIDL 这种简便的方式来快速实现 Binder。
AIDL
Android 接口定义语言,使用它能够快速实现 Binder 来进行进程间通信。
AIDL 的使用流程
使用 AIDL 来进行进程间通信的流程,分为服务端和客户端两个方面
服务端:服务端要创建 Service 来监听客户端的请求,创建一个 AIDL 文件声明公开的方法。在这里 AIDL 文件相当于『远程接口』,Service 相当于『服务对象』。
客户端:客户端需要绑定服务端的 Service,并将服务端返回的 Binder 引用转成 AIDL 接口所属的类型。
AIDL 的具体实现
创建 AIDL 接口
// IHelloManager.aidl
package com.melon.aidl;
interface IHelloManager {
String sayHello();
}
需要注意,AIDL 的包结构在服务端和客户端要保持一致,因为客户端需要反序列化服务端中和 AIDL 接口相关的所有类。
项目 Build 之后系统会根据 IHelloManager.aidl 生成一个 Binder 类。
Binder 类内部包含一个 Stub 类和 Proxy 类。
值得注意的是,在这里生成的 Stub 相当于 『远程接口实现类的抽象类』,在 Service 中将实现 Stub 中的抽象方法。
而 Proxy 则是提供给客户对象的代理类。
实现远程服务端 Service
先创建一个 Service,创建一个 Binder 对象实现 Stub 中的内部方法并在 onBind() 中返回它。
private Binder mBinder = new IHelloManager.Stub() {
@override
public String sayHello() throws RemoteException {
return "server says hello";
}
可以看到,服务端 Service 内的 mBinder 才是真正提供服务,执行方法的对象。
实现客户端
客户端只要绑定连接 Service,将服务端返回的 Binder 对象转化为 AIDL 接口,就能够调用服务端的方法了。关键代码如下:
IHelloManager HelloManager = IBookManager.Stub.asInterface(service);
String hello = HelloManager.sayHello();
可以看到,连接成功后,asInterface() 会返回 Proxy 代理对象。之后再将 Proxy 转化成 IHelloManager 接口。
这样我们在客户端就能够远程调用在不同进程里的方法了。
全文总结
本文对代理模式,RMI,AIDL 做了简单的介绍,主要是对书本和网络的资料进行整合,并尽量用简洁的方式表达出来。在撰写文章时,对 AIDL 有了更为深入的了解。
对书本内容的认识可能会有出错的地方,希望各位读者能够指出文章中的错误,第一次写技术总结,也希望各位对文章陈述的结构给出指导意见。