基于JSON RPC的一种Android跨进程调用解决方案了解一下?
基于json rpc的一种android跨进程调用解决方案了解一下?
基于json rpc的一种android跨进程调用解决方案了解一下?
简介 使用方式 实现原理 总结 后续安排 相关链接
简介
今天上午,看票圈有朋友分享爱奇艺的跨进程通信框架——andromeda,觉的还是有点意思的。
以前项目中用到跨进程这种解决方案比较少,今天看了下andromeda,发现调用方式很简单。
恰好最近一年都是在做后端工作,想到了json rpc的方案,其实android跨进程接也是一种rpc调用方式,那么参考json rpc协议,通过aidl通道也可以很简单一种跨进程通信方式,而且使用方式也很简单。
说干就干,但是作为了高级程序员,肯定要给项目起个名字高大上的名字——bifrost(彩虹桥),参考复联电影雷神上面的彩虹桥,寓意可以传送到各地,也表达android跨进程通信可以畅通无阻。
使用方式
在android的跨进程调用需要用到aidl方式,但是呢,操作起来非常麻烦,可以传递基本类型,如果需要自定义类,那么还需要实现parcelable接口,同时也要写不少代码,操作起来繁琐。
像平常一样,先定义一个接口和实现类就行了。
public interface inumberapi { int add(int a, int b); }
public class numberapiimpl implements inumberapi { @override public int add(int a, int b) { return a + b; } }
注册下接口和实现类,因为暂时没有用到依赖注入工具,同时我也不想把功能做的很复杂,暂时手动注册吧,做注册前,先做好初始化工作。
bifrost.getinstance().init(this); bifrost.getinstance().register(iuserapi.class, new userapiimpl()); bifrost.getinstance().register(inumberapi.class, numberapiimpl.class);
bifrost暂时支持2个注册方式,kv都是class类型,还有就是k是class,v是接口实现类的一个对象。
调用方式也很简单。
iuserapi userapi = bifrost.getinstance().getremoteinstance(iuserapi.class); user user = userapi.login("admin", "123456"); timber.i("user = %s", user); inumberapi numberapi = bifrost.getinstance().getremoteinstance(inumberapi.class); int ret = numberapi.add(1, 2); toast.maketext(getapplicationcontext(), "1 + 2 = " + ret, toast.length_long).show();
实现原理
原理很简单,见下图所示。
当在原始的进程中,定义一个接口,然后获取该对象的时候,其实返回值是一个用java动态代理实现的一个值,当有使用方调用接口中的方法时候,会构造成一个rpcrequest对象,这个对象很简单,就是标识这个调用的必要信息。
public class rpcrequest { @serializedname("jsonrpc") public string jsonrpc = "1.0"; @serializedname("id") public string id = uuid.randomuuid().tostring(); @serializedname("clazz") public string clazz; @serializedname("method") public string method; @serializedname("params") public string params; @override public string tostring() { return "rpcrequest{" + "jsonrpc='" + jsonrpc + '\'' + ", id='" + id + '\'' + ", clazz='" + clazz + '\'' + ", method='" + method + '\'' + ", params='" + params + '\'' + '}'; } }
比如上面的接口方法inumberapi.add,那么生成的最终的json信息如下。
{ "clazz": "cn.mycommons.bifrost.demo.api.inumberapi", "id": "0af23e0d-03ab-4cb9-8f52-2c7f7e094023", "jsonrpc": "1.0", "method": "add", "params": "[1,2]" }
然后这个对象又会转化成req对象,这个对象是实现parcelable接口的,用于2个进程之间通信。
public class req implements parcelable { private string uuid; private string payload; public req() { uuid = uuid.randomuuid().tostring(); } public string getuuid() { return uuid; } public void setuuid(string uuid) { this.uuid = uuid; } public string getpayload() { return payload; } public void setpayload(string payload) { this.payload = payload; } public static creator getcreator() { return creator; } @override public string tostring() { return "req{" + "uuid='" + uuid + '\'' + ", payload='" + payload + '\'' + '}'; } protected req(parcel in) { uuid = in.readstring(); payload = in.readstring(); } public static final creator creator = new creator() { @override public req createfromparcel(parcel in) { return new req(in); } @override public req[] newarray(int size) { return new req[size]; } }; @override public int describecontents() { return 0; } @override public void writetoparcel(parcel dest, int flags) { dest.writestring(uuid); dest.writestring(payload); } }
上面的请求最终的信息变成了这样,这个不是json,是java的tostring方法返回的。
req{uuid='f6a8028a-3cba-4abf-912b-ee7979923fb5', payload='{"clazz":"cn.mycommons.bifrost.demo.api.inumberapi","id":"0af23e0d-03ab-4cb9-8f52-2c7f7e094023","jsonrpc":"1.0","method":"add","params":"[1,2]"}'}
当另外一个进程获取到这些数据后,那么会做对应的反序列化,再次转化成req,然后又可以得到rpcrequest。
当取到rpcrequest时候,可以根据里面的信息,获取当前调用接口的实现类,然后利用反射完成调用操作,得到结果后再次把结果转成json。
public class bifrostaidlimpl extends bifrostaidl.stub { private gson gson = new gson(); @override public resp exec(req req) throws remoteexception { timber.i("%s-->exec", this); timber.i("req = %s", req); string data = req.getpayload(); rpcrequest rpcrequest = gson.fromjson(data, rpcrequest.class); timber.i("rpcrequest = %s", rpcrequest); try { class clazz = class.forname(rpcrequest.clazz); method method = null; for (method m : clazz.getmethods()) { if (m.getname().equals(rpcrequest.method)) { method = m; break; } } if (method != null) { class[] types = method.getparametertypes(); list args = new arraylist<>(); if (!textutils.isempty(rpcrequest.params)) { jsonarray array = new jsonarray(rpcrequest.params); for (int i = 0; i < array.length(); i++) { string o = array.getstring(i); args.add(gson.fromjson(o, types[i])); } } object instance = bifrost.getinstance().getinstance(clazz); timber.i("instance = %s", instance); timber.i("method = %s", method); timber.i("types = %s", arrays.tostring(types)); timber.i("params = %s", args); object result = method.invoke(instance, args.toarray(new object[0])); timber.i("result = %s", result); return resputil.success(req.getuuid(), rpcrequest.id, result); } throw new runtimeexception("method " + rpcrequest.method + " cant not find"); } catch (exception e) { timber.e(e); // e.printstacktrace(); return resputil.fail(req.getuuid(), rpcrequest.id, e); } } }
json也会转成resp,返回到原始的进程。然后解析数据,当做函数返回值。
总结
总体来说,这个流程还是蛮清晰的,就是利用一个aidl通道,然后自己定义调用协议,我这边参考了json rpc协议。当然了也可以参考其他的,这里不再表述。
整理下优缺点吧:
优点
使用和调用简单,无上手压力
无需实现parcelable接口,代码简洁
缺点
因为涉及到json转换,所以需要依赖gson
调用过程中含有多次json序列化与反序列化,有反射操作,可能会有性能影响
接口方法中的参数和返回值必须要是基本的类型,支持josn序列化和反序列化,但原始的aidl方式基本上也是一样,所以这条可以接受
后续安排
暂时只是实现简单的demo,只是验证这个思路是否可行,后续会做些优化操作,如有朋友有兴趣,可以一起参与,本人联系方式 xiaqiulei@126.com。
支持异步操作,支持回调函数,可参考retroft调用方式,可支持rxjava操作
被调用进程支持线程池,增加并发量
单独的日志操作,不依赖timber
支持同进程和夸进程调用
支持事件的通知、发送,可参考broadcastreceiver,eventbus等。