赞
踩
> `Response`中的重要元素有:`result` 字符串类型,用 `json`字符串表示接口执行的结果`isSuccess` 为 `true`,接口执行成功, `false` 执行失败。
public class Response implements Parcelable {
private String result;
//结果json串
private Boolean isSuccess;
//是否成功
}
最后,Request引用的Parameter类:type表示,参数类型(如果是String类型,那么这个值就是java.long.String)value 表示,参数值,Gson序列化之后得到的字符串。
public class Parameter implements Parcelable {
private String value;
//参数值序列化之后的json
private String type;
//参数类型 obj.getClass
}
为什么设计这么一个Parameter?为什么不直接使用Object?因为,Request 中需要客户端给的参数列表,可是如果直接使用客户端给的Object[] ,你并不能保证数组中的所有参数都实现了 `Parcelable`,一旦有没有实现的,通信就会失败( `binder` `AIDL`通信,所有参与通信的对象,都必须实现 `Parcelable`,这是基础),所以,直接用 `gson`将Object[] 转化成Parameter[],再传给Request,是不错的选择,当需要反射执行的时候,再把Parameter[] 反序列化成为 Object[] 即可。 OK,通信协议的3个类讲解完了,那么下一步应该是把这个协议使用起来。 ##### **3)binder连接封装** 参照 `Demo`源码,这一个步骤中的两个核心类:`IpcService` , `Channel` > ##### 先说 `IpcService.java` > > 它就是一个 `extendsandroid.app.Service` 的一个普通 `Service`,它在服务端启动,然后与客户端发生通信。它必须在服务端 `app`的 `manifest`文件中注册。同时,当客户端与它连接成功时,它必须返回一个 `Binder`对象,所以我们要做两件事: > > **1、服务端的 `manifest`中对它进行注册** > > ![](https://upload-images.jianshu.io/upload_images/15590149-71adecfe3b6d7c89?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > > ps: 这里肯定有人注意到了,上面 `service`注册时,其实使用了多个 `IpcService`的内部静态子类,设计多个内部子类的意义是,考虑到服务端存在**多**个业务接口的存在,让每一个业务接口的实现类都由一个专门的 `IpcService`服务区负责通信。举个例子:上图中存在两个 `IpcService`的子类,我让 `IpcService0` 负责用户业务 `UserBusiness`,让 `IpcService1` 负责 `DownloadBusiness`, 当客户端需要使用 `UserBusiness`时,就连接到 `IpcService0`,当需要使用 `DownloadBusiness`时,就连接到 `IpcService1`. 但是这个并不是硬性规定,而只是良好的编程习惯,一个业务接口 `A`,对应一个`IpcService子类A`,客户端要访问业务接口 `A`,就直接和`IpcService子类A`通信即可。同理,一个业务接口 `B`,对应一个`IpcService子类B`,客户端要访问`业务接口B`,就直接和`IpcService子类B`通信即可。(我是这么理解的,如有异议,欢迎留言) **2、重写** **`onBind`方法,返回一个Binder对象:**我们要明确返回的这个Binder对象的作用是什么。它是给**客户端**去使用的,**客户端**用它来调用远程方法用的,所以,我们前面两个**大步骤**准备的**注册机Registry**,和**通信协议request,response**,就是在这里**大显身手**了 。
public IBinder onBind(Intent intent) {
return new IIpcService.Stub() {
//返回一个binder对象,让客户端可以binder对象来调用服务端的方法
@Override
public Response send(Request request) throws RemoteException {
//当客户端调用了send之后
//IPC框架层应该要 反射执行服务端业务类的指定方法,并且视情况返回不同的回应
//客户端会告诉框架,我要执行哪个类的哪个方法,我传什么参数
String serviceId = request.getServiceId();
String methodName = request.getMethodName();
Object[] paramObjs = restoreParams(request.getParameters());
//所有准备就绪,可以开始反射调用了?
//先获取Method
Method method = Registry.getInstance().findMethod(serviceId, methodName, paramObjs);
switch (request.getType()) {
case Request.TYPE_CREATE_INSTANCE:
try {
Object instance = method.invoke(null, paramObjs);
Registry.getInstance().putObject(serviceId, instance);
return new Response(“业务类对象生成成功”, true);
}
catch (Exception e) {
e.printStackTrace();
return new Response(“业务类对象生成失败”, false);
}
case Request.TYPE_BUSINESS_METHOD:
Object o = Registry.getInstance().getObject(serviceId);
if (o != null) {
try {
Log.d(TAG, “1:methodName:” + method.getName());
for (int i = 0; i < paramObjs.length; i++) {
Log.d(TAG, "1:paramObjs " + paramObjs[i]);
}
Object res = method.invoke(o, paramObjs);
Log.d(TAG, “2”);
return new Response(gson.toJson(res), true);
}
catch (Exception e) {
return new Response(“业务方法执行失败” + e.getMessage(), false);
}
}
Log.d(TAG, “3”);
break;
}
return null;
}
}
;
}
> 这里有一些细节需要总结一下:
> 1、从 `request`中拿到的 参数列表是 `Parameter[]`类型的,而我们反射执行某个方法,要的是 `Object[]`,那怎么办?反序列化咯,先前是用 `gson`去序列化的,这里同样使用 `gson`去反序列化, 我定义了一个名为:`restoreParams`的方法去反序列化成 `Object[]`.
>
> 2、之前在 `request`中,定义了一个 `type`,用来区分静态的 `getInstance`方法,和 普通的业务 `method`,这里要根据 `request`中的 `type`值,区分对待。`getInstance`方法,会得到一个业务实现类的 `Object`,我们利用 `Registry`的 `putObject`把它保存起来。而,普通 `method`,再从 `Registry`中将刚才业务实现类的 `Object`取出来,反射执行 `method`
>
> 3、静态 `getInstance`的执行结果,不需要告知客户端,所以没有返回 `Response`对象,而 普通 `Method`,则有可能存在返回值,所以必须将返回值 `gson`序列化之后,封装到 `Response`中, `return`出去。
**再来讲 `Channel`类:**
> 之前抱怨过,不喜欢重复写 `bindService,ServiceConnection,unbindService`。但是其实还是要写的,写在**IPC框架层**,只写一次就够了。
public class Channel {
String TAG = “ChannelTag”;
private static final Channel ourInstance = new Channel();
/**
>上面的代码是Channel类代码,两个关键:
>
>1、 `bindService+ServiceConnection` 供客户端调用,绑定服务,并且将连接成功之后的binder保存起来
>
>![](https://upload-images.jianshu.io/upload_images/15590149-d5ecb041c5d69a31?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>
> 2、 提供一个 `send`方法,传入 `request`,且 返回 `response`,使用 `serviceId`对应的 `binder` 完成通信。
##### **4)动态代理实现 RPC**
终于到了最后一步,前面3个步骤,为进程间通信做好了所有的准备工作,只差最后一步了------ 客户端调用服务。重申一下RPC的定义:**让客户端像使用本地方法一样调用远程过程**。
像 使用本地方法一样?我们平时是怎么使用本地方法的呢?
A a = new A();
a.xxx();
类似上面这样。但是我们的客户端和服务端是两个隔离的进程,内存并不能共享,也就是说服务端存在的类对象,不能直接被客户端使用,那怎么办?**泛型+动态代理**!我们需要构建一个处在**客户端进程内**的业务**代理**类对象,它可以执行和**服务端**的业务类一样的方法,但是它确实不是服务端进程的那个对象,如何实现这种效果?
public class Ipc {
…省略无关代码
/**
上面的 `getInstanceWithName`,会返回一个动态代理的 业务类对象(处在客户端进程), 它的行为 和 真正的业务类(服务端进程)一模一样。这个方法有 `4`个参数 `@paramservice` 要访问哪一个远程service,因为不同的service会返回不同的Binder `@paramclassType` 要访问哪一个业务类,注意,这里的业务类完全是客户端自己定义的,包名不必和服务端一样,但是一定要有一个和服务端对应类一样的注解。注解相同,框架就会认为你在访问相同的业务。`@paramgetInstanceMethodName` 我们的业务类都是设计成单例的,但是并不是所有获取单例对象的方法都叫做getInstance,我们框架要允许其他的方法名 `@paramparams` 参数列表,类型为 `Object[]`。
**重中之重,实现RPC的最后一个步骤,如图**:
> ![](https://upload-images.jianshu.io/upload_images/15590149-bf48a306f092734f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>
> 如果服务端的**单例对象**创建成功,那么说明 服务端的**注册表**中已经存在了一个**业务实现类的对象**,进而,我可以通过**binder通信**来 使用这个对象 执行我要的**业务方法**,并且拿到方法**返回值**,最后 把返回值反序列化成为 `Object` ,作为动态代理业务类的方法的**执行结果**。
关键代码 `IpcInvocationHandler` :
/**
ok,收工之前总结一下,最后 `RPC`的实现,借助了 `Proxy`动态代理+ `Binder`通信。用动态代理产生一个本进程中的对象,然后在重写 `invoke`时,使用 `binder`通信执行服务端过程拿到返回值。这个设计确实精妙。 ## **五、 写在最后的话** > 1. 本案例提供的两个**Demo**,都只是作为演示效果作用的,代码不够精致,请各位不要在意这些细节。 > > 2. 此框架并非本人原创,课题内容来自享学课堂Lance老师,本文只做学习交流之用,转载请务必注明出处,谢谢合作。 # 总结 我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。 **[送大家一份资料,戳这里免费领取](https://codechina.csdn.net/m0_60958482/java-p7)** # Mybatis源码解析 ![](https://img-blog.csdnimg.cn/img_convert/23726cffd8066f8097967410b5058011.png) 架并非本人原创,课题内容来自享学课堂Lance老师,本文只做学习交流之用,转载请务必注明出处,谢谢合作。 # 总结 我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。 **[送大家一份资料,戳这里免费领取](https://codechina.csdn.net/m0_60958482/java-p7)** # Mybatis源码解析 [外链图片转存中...(img-R3LgrjvL-1630121526006)] ![](https://img-blog.csdnimg.cn/img_convert/d053abd1ceba91fed1fb9c1c9f1d50ab.png)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。