发送请求到服务端:
继续接着上一次https://www.cnblogs.com/webor2006/p/12196171.html的代码继续来编写,上一次在SecondAcitity中完成了远程服务的连接了:
接下来则应该是发送消息给主进程,然后在MainActivity中进行消息的接收处理了,我们知道在主进程中我们注册了一个UserManager单例对象:
那如果我们在SecondActivity改变这个单例对象的值:
很明显在MainActivity的主进程中再获取的话是收不到在子进程更改的数据变化的:
因为这俩是不在一个进程了,那接下来就以这个为例子,如果能实现跨进程的情况下也能达到相互数据及时感知,那其实也就实现了Hermes框架的效果了,当然要实现肯定得借助于AIDL了,不过这里还得借助于动态代理来达成,之前咱们在分析Hermes的原理时也看到了动态代理隐藏其中, 下面则来开始实现,稍麻烦一点,坚持:
为了能够清楚的知道咱们要实现它的具体步骤,跟着图来:
也就是客户端要通过aidl来告诉服务端需要给客户端返回服务端的UserManager单例对象,好,下面实现一下,先修改一下布局:
具体来实现一下,由于目前在子进程无法拿到父进程的UserManager对象实例,所以这里需要定义一个接口来将这个类中的行为抽象出来:
而这个接口对应的是哪个类这里用注解标注一下,以便到时反射时进行动态代理时好知道最终要代理的对象的类名:
然后让UserManager实现这个接口:
刚才不是说最终要用到动态代理技术么,最终对象的生成则就是采用动态代理来实现的,所以我们看到Hermes框架也是这么搞的,瞅一下:
而它也是实现了一个接口:
接口中则都是抽象的行为:
此时咱们获取对像的代码就可以这样写的:
接下来则集中精力来实现这个方法,实现了跨进程发消息的问题就基本就解决了:
而通过aidl发送的方法是Request,它里面只有一个String属性:
所以,咱们应该对所有的参数信息进行对象封装一下,最终再转换成一个Json串并生成咱们的Request对象,所以接下来新建一个JavaBean:
好,接下来具体实现一下:
public class MyHermes { private static final MyHermes ourInstance = new MyHermes(); private static final Gson GSON = new Gson(); private Context context; private MyTypeCenter typeCenter; private ServiceConnectionManager serviceConnectionManager; public static MyHermes getDefault() { return ourInstance; } private MyHermes() { typeCenter = MyTypeCenter.getInstance(); serviceConnectionManager = ServiceConnectionManager.getInstance(); } public void init(Context context) { context = context.getApplicationContext(); } public void register(Class<?> clazz) { typeCenter.register(clazz); } public void connectApp(Context context, Class<? extends HermesService> service) { connectApp(context, null, service); } public void connectApp(Context context, String packageName, Class<? extends HermesService> service) { init(context); serviceConnectionManager.bind(context.getApplicationContext(), packageName, service); } //获取另一个进程的对象 public <T> T getInstance(Class<T> clazz, Object... parameters) { Response responce = sendRequest(HermesService.class, clazz, null, parameters); return null; } private <T> Response sendRequest(Class<HermesService> hermesServiceClass , Class<T> clazz, Method method, Object[] parameters) { RequestBean requestBean = new RequestBean(); String className = null; if (clazz.getAnnotation(ClassId.class) == null) { //有注解 requestBean.setClassName(clazz.getName()); requestBean.setResultClassName(clazz.getName()); } else { //木有注解时返回类型的全类名 requestBean.setClassName(clazz.getAnnotation(ClassId.class).value()); requestBean.setResultClassName(clazz.getAnnotation(ClassId.class).value()); } if (method != null) { //方法名 统一传 方法名+参数名 getInstance(java.lang.String) requestBean.setMethodName(TypeUtils.getMethodId(method)); } RequestParameter[] requestParameters = null; if (parameters != null && parameters.length > 0) { requestParameters = new RequestParameter[parameters.length]; for (int i = 0; i < parameters.length; i++) { Object parameter = parameters[i]; String parameterClassName = parameter.getClass().getName(); String parameterValue = GSON.toJson(parameter); RequestParameter requestParameter = new RequestParameter(parameterClassName, parameterValue); requestParameters[i] = requestParameter; } } if (requestParameters != null) { requestBean.setRequestParameter(requestParameters); } return null; } }
好,接下来则将上面的这个封装bean利用gson转换成json串,最后再生成咱们要发送的Request对象,再通过aidl进行发送,这里在发送Request其实是有两种类型的,所以咱们定义两个常量值,并给Request增加一个类型字段:
public class Request implements Parcelable { private String data; //请求对象的类型 private int type; public String getData() { return data; } public void setData(String data) { this.data = data; } public int getType() { return type; } public void setType(int type) { this.type = type; } protected Request(Parcel in) { data = in.readString(); type = in.readInt(); } public Request(String data, int type) { this.data = data; this.type = type; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(data); dest.writeInt(type); } @Override public int describeContents() { return 0; } public static final Creator<Request> CREATOR = new Creator<Request>() { @Override public Request createFromParcel(Parcel in) { return new Request(in); } @Override public Request[] newArray(int size) { return new Request[size]; } }; }
其aidl的发送则转由ServiceConnectionManager负责了,所以接下来实现一下发送逻辑:
此时就通过AIDL将请求发送到了Service了,目前咱们的Service还木有实现:
所以接下来咱们来处理它。
服务端消息处理:
接下来则到这一步了:
这里则需要根据不同的requesttype来生成不同的response,这里需要用到策略模式了,其实Hermes框架也是类似,瞅一下:
其中对于第个Receiver进行了一些抽象:
所以咱们也校仿一下,先建立一个抽象的Response生成对象:
public abstract class ResponceMake { //UserManage 的Class protected Class<?> reslutClass; // getInstance() 参数数组 protected Object[] mParameters; Gson GSON = new Gson(); protected MyTypeCenter typeCenter = MyTypeCenter.getInstance(); public Response makeResponce(Request request) { RequestBean requestBean = GSON.fromJson(request.getData(), RequestBean.class); reslutClass = typeCenter.getClassType(requestBean.getResultClassName()); //参数还原 RequestParameter[] requestParameters = requestBean.getRequestParameter(); if (requestParameters != null && requestParameters.length > 0) { mParameters = new Object[requestParameters.length]; for (int i = 0; i < requestParameters.length; i++) { RequestParameter requestParameter = requestParameters[i]; Class<?> clazz = typeCenter.getClassType(requestParameter.getParameterClassName()); mParameters[i] = GSON.fromJson(requestParameter.getParameterValue(), clazz); } } else { mParameters = new Object[0]; } setMethod(requestBean); Object resultObject = invokeMethod(); //TODO 需要转换成Response return null; } protected abstract Object invokeMethod(); protected abstract void setMethod(RequestBean requestBean); }
然后咱们来处理具体的子类,目前先来处理单例UserManager的获取:
其中先来实现setMethod(),也就是根据方法的参数信息最终来生成一个Method对象,如下:
其中method就是调用UserManager中的getInstance()方法:
比如好理解,直接贴出代码了,接着再来实现invokeMethod():
好,此时再回到抽象ResponseMake类,再处理Response结果转换的逻辑:
此时就又需要借助于一个封装的bean类进行转换,如下:
其中Respnse需要再增加一个构造,直接对里面的data赋值:
最后咱们在Service中来使用一下:
好,再回到主流程来,目前Response已经从服务端拿到之后,则需要根据Response通过动态代理来调用对象中的具体方法了,也就是流程来到了这:
对应代码为:
这里则就是需要产生一个代理对象了,具体代码如下:
接下来就是来实现这个InvocationHandler的代码了,先来从调用角度来看一下,当我们获得了UserManager的代理对象之后,则会如此调用了:
而每个方法的调用很显然都得有一个AIDL的过程,也就是要通知到服务端来进行调用,那既然每个方法都需要有这么一个过程,那用动态代理的拦截机制不正好么,所以接睛来咱们在拦截处进行方法的远程调用:
上面发送代码基本跟之前获取getInstance的差不多,可以对其进行封装一下,这里就不追求优质代码了, 重点是理解背后的原理。
好,此时已经将setStudent()的方法请求发送到了服务端了,接着服务端需要对其进行处理,回到主服务的代码:
跟之前的类似使用策略模式,先建议一个子类,然后再使用之则可:
其中TypeCenter中需要增加一个getMethod()方法:
其中可以看到都是从之前我们主进程进行注册时的缓存中拿的方法,所以性能也是比较好的。
好,准备了这个Response之后下面则来使用一下:
至此!!!关于得到UserManager单例对象以及调用它里面的setStudent()方法的整个底层的aidl逻辑都写法了,说实话,真的好麻烦呀~~还是挺佩服饿了么开源的工程师,那写了这么多代码到底好不好使呢?下面来应用一下:
好,编译运行,在SecondActivity获取UserManager单例时报错了,错误信息如下:
呃,好吧,粗心了,确实不是接口:
咱们修复一下:
好,再次运行:
嗯,完美的实现了我们想要的跨进程的功能,当然目前代码不是太完善,但是木有关系,实现了核心的功能对于这些完善都是可以自己再弄的,再说如果商用也不可以是真的用手写的框架,一般都会用三方成熟的开源组件,但是从学习的角度只有这样才能让自己学到东东,最后在返回app时还有个崩溃: