当前位置:   article > 正文

Android程序员进阶必知架构源码:手把手讲解IPC框架,金三银四旗开得胜_ipc服务框架

ipc服务框架
  • 创建业务类实例,并且保存到注册表中
    /
    public final static int TYPE_CREATE_INSTANCE = 0;
    /
    *
  • 执行普通业务方法
    */
    public final static int TYPE_BUSINESS_METHOD = 1;
    public int getType() {
    return type;
    }
    private String serviceId;
    //客户端告诉服务端要调用哪一个业务
    private String methodName;
    //要调用哪一个方法
    private Parameter[] parameters;
    //调这个方法要传什么参数
    …省略无关代码
    }
> `Response`中的重要元素有:`result` 字符串类型,用 `json`字符串表示接口执行的结果`isSuccess` 为 `true`,接口执行成功, `false` 执行失败。
  • 1

public class Response implements Parcelable {
private String result;
//结果json串
private Boolean isSuccess;
//是否成功
}

最后,Request引用的Parameter类:type表示,参数类型(如果是String类型,那么这个值就是java.long.String)value 表示,参数值,Gson序列化之后得到的字符串。
  • 1

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**,就是在这里**大显身手**了 。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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框架层**,只写一次就够了。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

public class Channel {
String TAG = “ChannelTag”;
private static final Channel ourInstance = new Channel();
/**

  • 考虑到多重连接的情况,把获取到的binder对象保存到map中,每一个服务一个binder
    /
    private ConcurrentHashMap<Class<? extends IpcService>, IIpcService> binders = new ConcurrentHashMap<>();
    public static Channel getInstance() {
    return ourInstance;
    }
    private Channel() {
    }
    /
    *
  • 考虑app内外的调用,因为外部的调用需要传入包名
    */
    public void bind(Context context, String packageName, Class<? extends IpcService> service) {
    Intent intent;
    if (!TextUtils.isEmpty(packageName)) {
    intent = new Intent();
    Log.d(TAG, “bind:” + packageName + “-” + service.getName());
    intent.setClassName(packageName, service.getName());
    } else {
    intent = new Intent(context, service);
    }
    Log.d(TAG, “bind:” + service);
    context.bindService(intent, new IpcConnection(service), Context.BIND_AUTO_CREATE);
    }
    private class IpcConnection implements ServiceConnection {
    private final Class<? extends IpcService> mService;
    public IpcConnection(Class<? extends IpcService> service) {
    this.mService = service;
    }
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    IIpcService binder = IIpcService.Stub.asInterface(service);
    binders.put(mService, binder);
    //给不同的客户端进程预留不同的binder对象
    Log.d(TAG, “onServiceConnected:” + mService + “;bindersSize=” + binders.size());
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
    binders.remove(mService);
    Log.d(TAG, “onServiceDisconnected:” + mService + “;bindersSize=” + binders.size());
    }
    }
    public Response send(int type, Class<? extends IpcService> service, String serviceId, String methodName, Object[] params) {
    Response response;
    Request request = new Request(type, serviceId, methodName, makeParams(params));
    Log.d(TAG, “;bindersSize=” + binders.size());
    IIpcService iIpcService = binders.get(service);
    try {
    response = iIpcService.send(request);
    Log.d(TAG, "1 " + response.isSuccess() + “-” + response.getResult());
    }
    catch (RemoteException e) {
    e.printStackTrace();
    response = new Response(null, false);
    Log.d(TAG, “2”);
    }
    catch (NullPointerException e) {
    response = new Response(“没有找到binder”, false);
    Log.d(TAG, “3”);
    }
    return response;
    }
    …省略不关键代码
    }

>上面的代码是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的定义:**让客户端像使用本地方法一样调用远程过程**。

像 使用本地方法一样?我们平时是怎么使用本地方法的呢?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

A a = new A();
a.xxx();


类似上面这样。但是我们的客户端和服务端是两个隔离的进程,内存并不能共享,也就是说服务端存在的类对象,不能直接被客户端使用,那怎么办?**泛型+动态代理**!我们需要构建一个处在**客户端进程内**的业务**代理**类对象,它可以执行和**服务端**的业务类一样的方法,但是它确实不是服务端进程的那个对象,如何实现这种效果?
  • 1
  • 2

public class Ipc {
…省略无关代码
/**

  • @param service
  • @param classType
  • @param getInstanceMethodName
  • @param params
  • @param 泛型,
  • @return
    */
    public static T getInstanceWithName(Class<? extends IpcService> service,
    Class classType, String getInstanceMethodName, Object… params) {
    //这里之前不是创建了一个binder么,用binder去调用远程方法,在服务端创建业务类对象并保存起来
    if (!classType.isInterface()) {
    throw new RuntimeException(“getInstanceWithName方法 此处必须传接口的class”);
    }
    ServiceId serviceId = classType.getAnnotation(ServiceId.class);
    if (serviceId == null) {
    throw new RuntimeException(“接口没有使用指定ServiceId注解”);
    }
    Response response = Channel.getInstance().send(Request.TYPE_CREATE_INSTANCE, service, serviceId.value(), getInstanceMethodName, params);
    if (response.isSuccess()) {
    //如果服务端的业务类对象创建成功,那么我们就构建一个代理对象,实现RPC
    return (T) Proxy.newProxyInstance(
    classType.getClassLoader(), new Class[]{
    classType
    }
    ,
    new IpcInvocationHandler(service, serviceId.value()));
    }
    return null;
    }
    }

上面的 `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` :

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

/**

  • RPC调用 执行远程过程的回调
    */
    public class IpcInvocationHandler implements InvocationHandler {
    private Class<? extends IpcService> service;
    private String serviceId;
    private static Gson gson = new Gson();
    IpcInvocationHandler(Class<? extends IpcService> service, String serviceId) {
    this.service = service;
    this.serviceId = serviceId;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //当,调用代理接口的方法时,就会执行到这里,执行真正的过程
    //而你真正的过程是远程通信
    Log.d(“IpcInvocationHandler”, “类:” + serviceId + " 方法名" + method.getName());
    for (int i = 0; i < args.length; i++) {
    Log.d(“IpcInvocationHandler”, “参数:” + args.getClass().getName() + “/” + args[i].toString());
    }
    Response response = Channel.getInstance().send(Request.TYPE_BUSINESS_METHOD, service, serviceId, method.getName(), args);
    if (response.isSuccess()) {
    //如果此时执行的方法有返回值
    Class<?> returnType = method.getReturnType();
    if (returnType != void.class && returnType != Void.class) {
    //既然有返回值,那就必须将序列化的返回值 反序列化成对象
    String resStr = response.getResult();
    return gson.fromJson(resStr, returnType);
    }
    }
    return null;
    }
    }
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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/122788
推荐阅读
相关标签
  

闽ICP备14008679号