[Android] 代理模式

代理模式是什么

远程代理流程图

如上图所示,代理代表着另一终端中的某个真实服务对象,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 远程调用的过程图如下所示:


Java RMI 示意图

RMI 方法调用过程

  1. 客户对象调用 Stub 的方法
  2. Stub 打包信息,通过网络发送给 Skeleton
  3. Skeleton 解包信息,找出被调用的方法和对象并调用
  4. 服务对象调用方法,将结果返回 Skeleton
  5. Skeleton 打包信息,通过网络发送给 Stub
  6. 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 涉及到的四个主要模块分别是 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 来进行进程间通信的流程,分为服务端和客户端两个方面

  1. 服务端:服务端要创建 Service 来监听客户端的请求,创建一个 AIDL 文件声明公开的方法。在这里 AIDL 文件相当于『远程接口』,Service 相当于『服务对象』。

  2. 客户端:客户端需要绑定服务端的 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 有了更为深入的了解。

对书本内容的认识可能会有出错的地方,希望各位读者能够指出文章中的错误,第一次写技术总结,也希望各位对文章陈述的结构给出指导意见。

参考资料

  1. RMI(Remote Method Invocation)原理浅析

  2. Java RMI详解

  3. 设计模式之代理模式(Proxy Pattern)远程代理解析

  4. 《 Android 开发艺术探索 》

  5. 《 Android 源码设计模式解析与实战 》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,814评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,124评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,814评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,924评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,815评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,562评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,944评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,582评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,859评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,881评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,700评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,493评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,943评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,115评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,413评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,978评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,182评论 2 339

推荐阅读更多精彩内容