发送请求到服务端:
继续接着上一次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时还有个崩溃: