最近看了下饿了么开源框架Hermes,它是Android进程间IPC通信框架,可以避免AIDL的编写,使用接口代理的方式获取跨进程的数据,感觉挺有意思的,于是学习了下,自己总结了一套简易版的实现,当然还有许多要完善的,但是基本架构思想还是值得学习的。
整个项目三个部分client,server,ipc_core,分别是客户端,服务端和IPC通信核心代码,其中client,server都依赖于ipc_core。
服务端注册UserManager
@ClassId("com.narkang.server.manager.UserManager")
public class UserManager {
private static UserManager ourInstance = null;
public static synchronized UserManager getInstance() {
if(ourInstance == null){
ourInstance = new UserManager();
}
return ourInstance;
}
public UserManager() {
}
private String data = "呵呵哒";
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
Chry.getDefault().register(UserManager.class);
服务端Service注册
<service android:name="com.narkang.chry.service.ChryService" >
<intent-filter>
//服务端包名
<action android:name="com.narkang.server" />
</intent-filter>
</service>
客户端使用
连接服务端
Chry.getDefault().connectApp(this, "com.narkang.server", ChryService.class)
定义一个接口IUserManager
@ClassId("com.narkang.server.manager.UserManager")
public interface IUserManager {
String getData();
void setData(String data);
}
IUserManager userManager = Chry.getDefault().getInstance(IUserManager.class);
在这里UserManager是一个单例,并且需要提供一个getInstance()方法,这个名字不能变,因为client去获取server的UserManager对象是通过这个方法名反射完成的,同时这个UserManager需要通过Chry注册,接着服务端需要注册一个server。而客户端想要使用需要首先连接服务端,接着定义一个接口IUserManager,接口里面的方法为需要操作UserManager里面的方法,需要一致,接着定义一个ClassId去保证IUserManager和UserManager的name一致。最后客户端就可以通过Chry.getDefault().getInstance(IUserManager.class)去获取服务端的UserManager代理对象。
1 .服务端注册
Chry.getDefault().register(UserManager.class)
这里Chry是一个单例对象,getDefault()就是获取其实例,来看register方法
public void register(Class<?> clazz) {
mTypeCenter.register(clazz);
}
可以看到注册转移到了TypeCenter中了
public void register(Class<?> clazz) {
registerClass(clazz); //class类型注册
registerMethod(clazz); //注册方法
}
主要注册了class类和class类中的method
private void registerClass(Class<?> clazz) {
String name = clazz.getName();
mClass.putIfAbsent(name, clazz);
}
private void registerMethod(Class<?> clazz) {
Method[] methods = clazz.getMethods();
for (Method method: methods) {
mRawMethods.putIfAbsent(clazz, new ConcurrentHashMap<String, Method>());
ConcurrentHashMap<String, Method> map = mRawMethods.get(clazz);
String methodId = TypeUtils.getMethodId(method); //会根据method和parameter生成一个methodId
map.put(methodId, method);
}
}
注册的值是保存在了一个map中了
2 .AIDL封装
通常来说,不同的javabean之间想要进行跨进程通信,都要编写相应的AIDL文件。为了只编写一次AIDL文件就可以通用不同的javabean,这里就对javabean进行了进一步封装,在进行IPC调用之前,会将需要执行的方法、参数和返回值封装到一个Request中,然后转化为json,再将json和一个Type封装到Request中,然后进行传递,这样就只用编写一次AIDL,实现复用的目的。具体看代码实现
// Request.aidl
package com.narkang.chry;
parcelable Request;
//Request.java
public class Request implements Parcelable {
private String data; //1
// 请求对象的类型
private int type;
...
}
// Response.aidl
package com.narkang.chry;
parcelable Response;
public class Response implements Parcelable {
//执行的结果也会转化为json存入data返回
private String data; //2
}
...
//MyEventBusService.aidl
package com.narkang.chry;
import com.narkang.chry.Request;
import com.narkang.chry.Response;
interface MyEventBusService {
Response send(in Request request); //3
}
注释1处的data是通过RequestBean转化为json存入的
public class RequestBean {
//请求单例的全类名
private String className;
//类型
private String resultClassName;
private String requestObject;
//返回方法名字
private String methodName;
// 参数
private RequestParameter[] requestParameter;
...
}
注释2处的data是通过ResponseBean转化为json存入的
public class ResponseBean {
private Object data;
...
}
注释3处,客户端要和服务端通信调用send方法就行了。
3 .客户端连接服务端
Chry.getDefault().connectApp(this, "com.narkang.server", ChryService.class);
如果服务端和客户端不在同一个进程是需要知道服务端的包名,否则包名不用传,可以这样使用
Chry.getDefault().connect(this, ChryService.class);
connect内部也还是会调用到connectApp,看下这个方法的实现
public void connectApp(Context context, String pkn, Class<ChryService> clazz) {
init(context); //1
mSCM.bind(context.getApplicationContext(), pkn, clazz); //2
}
看注释2,mSCM是一个ServiceConnectManager对象,主要是来绑定service和发送请求进行IPC调用。看下bind方法
public void bind(Context context, String pkn, Class<ChryService> clazz) {
ChryServiceConnection connection = new ChryServiceConnection(clazz);
mChryServiceConnections.put(clazz, connection);
Intent intent;
//根据是否在同一个进程选择对应的Intent创建方式
if (TextUtils.isEmpty(pkn)) {
intent = new Intent(context, clazz);
} else {
intent = new Intent();
intent.setClassName(pkn, clazz.getName());
}
//绑定服务
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
从这里可以看到是用来和服务端service进行绑定,绑定成功会添加到一个map中
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyEventBusService myEventBusService = MyEventBusService.Stub.asInterface(service);
mChryServices.put(mClass, myEventBusService);
}
4 .客户端调用服务端的getInstance单例方法
IUserManager userManager = Chry.getDefault().getInstance(IUserManager.class);
看getInstance方法
public <T> T getInstance(Class<T> clazz, Object... parameters) {
Response response = sendRequest(ChryService.class, clazz, null, parameters); //1
return getProxy(ChryService.class, clazz); //2
}
先看注释1处
private <T> Response sendRequest(Class<ChryService> hermesServiceClass
, Class<T> clazz, Method method, Object[] parameters) {
RequestBean requestBean = new RequestBean();
//根据是否有添加注解来存储对应的className和resultClassName
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) {
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);
}
Request request = new Request(GSON.toJson(requestBean), TYPE_GET);
return mSCM.request(hermesServiceClass, request); //1
}
这个方法主要做的就是拼接class和method的签名,然后转化为json存放的Request对象中,接着调用注释1处的request,内部会调用AIDL里面的send方法进行IPC通信。此时会有一个TYPE_GET标志,看服务端的执行过程。
private MyEventBusService.Stub mBinder=new MyEventBusService.Stub() {
@Override
public Response send(Request request){
// 对请求参数进行处理 生成Response结果返回
ResponseMake responseMake = null;
switch (request.getType()) { //根据不同的类型,产生不同的策略
case Chry.TYPE_GET://获取单例
responseMake = new InstanceResponseMake(); //1
break;
case Chry.TYPE_NEW:
responseMake = new ObjectResponseMake();
break;
}
return responseMake.makeResponse(request); //2
}
};
刚刚传入的是TYPE_GET标志,所以会执行注释1处的代码,这里其实用到了一个策略模式,InstanceResponseMake是ResponseMake的子类,而注释2处的makeResponse是在父类创建的,先看这个方法的实现
public Response makeResponse(Request request) {
RequestBean requestBean = GSON.fromJson(request.getData(), RequestBean.class);//1
resultClass = typeCenter.getClassType(requestBean.getResultClassName()); //2
RequestParameter[] requestParameters = requestBean.getRequestParameter();//3
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); //4
Object resultObject = invokeMethod(); //5
ResponseBean responseBean = new ResponseBean(resultObject); //6
String data = GSON.toJson(responseBean); //7
Response response = new Response(data); //8
return response;
}
在注释1处解析客户端传过来的RequestBean,注释2处获取resultClass,注释3处获取RequestParameter,后面会通过GSON.fromJson解析处参数,注释4处在子类InstanceResponseMake中实现,注释5处会执行客户端需要执行的方法,注释6,7,8会将返回结果封装到ResponseBean,然后通过GSON.toJson转成string,然后封装到response返回给客户端。重点看注释4和5,先看注释4处在InstanceResponseMake中的实现
@Override
protected void setMethod(RequestBean requestBean) {
RequestParameter[] requestParameters = requestBean.getRequestParameter();
Class<?>[] parameterTypes = null;
//下面这段获取参数class类型
if (requestParameters != null && requestParameters.length > 0) {
parameterTypes = new Class<?>[requestParameters.length];
for (int i = 0; i < requestParameters.length; ++i) {
parameterTypes[i] = typeCenter.getClassType(requestParameters[i].getParameterClassName());
}
}
String methodName = requestBean.getMethodName(); //可能出现重载
Method method = TypeUtils.getMethodForGettingInstance(resultClass, methodName, parameterTypes); //1
mMethod = method;
}
主要看注释1处,传入解析到的resultClass,方法名methodName,参数parameterTypes到TypeUtils.getMethodForGettingInstance去获取Method对象,来看下其实现
public static Method getMethodForGettingInstance(Class<?> clazz, String methodName, Class<?>[] parameterTypes) {
Method[] methods = clazz.getMethods();
Method result = null;
if (parameterTypes == null) {
parameterTypes = new Class[0];
}
for (Method method : methods) {
String tmpName = method.getName();
if (tmpName.equals("getInstance")) {
if (classAssignable(method.getParameterTypes(), parameterTypes)) {
result = method;
break;
}
}
}
if (result != null) {
if (result.getReturnType() != clazz) {
return null;
}
return result;
}
return null;
}
这里会获取clazz的所有method,然后去遍历所有method,然后去找是否有getInstance方法,有的话接着就去比较里面的parameterTypes,比较上了,就取这个method,接着就去比较result.getReturnType()是否和clazz相等,然后返回result,接着会保存在一个全局mMethod。接着看上面注释5的代码
protected Object invokeMethod() {
Object object = null;
try {
object = mMethod.invoke(null, mParameters); //1
// 保存对象
OBJECT_CENTER.putObject(object.getClass().getName(), object); //2
}catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
注释1通过反射执行这个单例对象,然后在注释2处,将这个对象保存在全局OBJECT_CENTER中了。
5. 客户端获取代理对象
private <T> T getProxy(Class<? extends ChryService> service, Class clazz) {
ClassLoader classLoader = service.getClassLoader();
T proxy = (T) Proxy.newProxyInstance(classLoader, new Class<?>[]{clazz}, new ChryInvocationHandler(service, clazz));
return proxy;
}
也就是说客户端获取的都是代理对象,来看下其处理过程ChryInvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Response response = Chry.getDefault().sendObjectRequest(hermeService, clazz, method, args); //1
...
}
这里注释1处会调用服务端的方法,再来看服务器处理方法
private MyEventBusService.Stub mBinder=new MyEventBusService.Stub() {
@Override
public Response send(Request request){
// 对请求参数进行处理 生成Response结果返回
ResponseMake responseMake = null;
switch (request.getType()) { //根据不同的类型,产生不同的策略
case Chry.TYPE_GET://获取单例
responseMake = new InstanceResponseMake();
break;
case Chry.TYPE_NEW:
responseMake = new ObjectResponseMake(); //2
break;
}
return responseMake.makeResponse(request);
}
};
直接看ObjectResponseMake对象里面的处理过程
@Override
protected void setMethod(RequestBean requestBean) {
mObject = OBJECT_CENTER.getObject(resultClass.getName());
Method method = typeCenter.getMethod(mObject.getClass(), requestBean);
mMethod = method;
}
这里先会去获取对应的Method对象,然后保存在全局mMethod中,接着就会去执行这个method
@Override
protected Object invokeMethod() {
try {
return mMethod.invoke(mObject,mParameters);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
执行完之后就和之前一样转化为json,保存在Response中,接着再来看这个代理对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Response response = Chry.getDefault().sendObjectRequest(hermeService, clazz, method, args); //1
if (!TextUtils.isEmpty(response.getData())) {
ResponseBean responseBean = GSON.fromJson(response.getData(), ResponseBean.class);//2
if (responseBean.getData() != null) {
Object getUserResult = responseBean.getData();
String data = GSON.toJson(getUserResult);
//
Class stringgetUser = method.getReturnType();
Object o = GSON.fromJson(data, stringgetUser); //3
return o;
}
}
return null;
}
执行成功会在注释2处解析responseBean,接着会在注释3处解析method的返回类型,返回给调用者使用。
源码分析到了这里总结下先会去执行服务器注册对象的getInstance方法,然后将返回对象保存在全局map中,接着客户端在调用接口方法时候会去代理对象中执行服务器的method,客户端就可以获取到执行结果。这样一来对于用户来说就是无感知的,就像调用同一进程方法,获取到服务端方法执行结果。