一. Hermes說明
Hermes是之前餓了麼開源的一套專門用于跨程序通信的架構,基于aidl進行封裝,但是在使用的時候完全不用考慮aidl,很強大。位址是:https://github.com/Xiaofei-it/Hermes。
本文是實作了Hermes核心原理。
二. 使用流程
- 假設A程序為主程序,B程序為其他程序。
- 在A程序和B進行中必須要有一個完全相同的接口。該接口主要提供給B程序使用。
- A程序中要有一個單例類實作該接口。
- 在B進行中該接口類上面要加上注解 @ClassId("實作類的全路徑")
- A程序中接口的實作類必須是單例的,而且擷取單例方法名必須是:getInstance()
三. 使用流程及原理
1. register
在A程序中執行:
SunHermes.getDefault().register(UserManager.class);
這一步就是将class消息進行儲存,接下來或調用到TypeCenter中執行:
public void register(Class<?> clazz){
registerClass(clazz);
registerMethod(clazz);
}
其中registerClass儲存到mAnnotatedClasses中
private final ConcurrentHashMap<String, Class<?>> mAnnotatedClasses;
private void registerClass(Class<?> clazz){
String className = clazz.getName();
mAnnotatedClasses.putIfAbsent(className, clazz);
}
registerMethod是将class中的所有方法都儲存到mRawMethods中
private final ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, Method>> mRawMethods;
private void registerMethod(Class<?> clazz){
mRawMethods.putIfAbsent(clazz, new ConcurrentHashMap<String, Method>());
ConcurrentHashMap<String, Method> map = mRawMethods.get(clazz);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String key = TypeUtils.getMethodId(method);
map.put(key, method);
}
}
2. connect
在B程序中執行:
SunHermes.getDefault().connect(this, SunHermesService.class);
這一步後會去調用ServiceConnectionManager中的bind方法,進行binder連接配接:
ConcurrentHashMap<Class<? extends SunHermesService>, HermesServiceConnection> mHermesServiceConnections = new ConcurrentHashMap();
public void bind(Context context, String packageName, Class<? extends SunHermesService> service){
HermesServiceConnection connection = new HermesServiceConnection(service);
mHermesServiceConnections.put(service, connection);
Intent intent;
if (TextUtils.isEmpty(packageName)){
intent = new Intent(context, service);
}else{
intent = new Intent();
intent.setClassName(packageName,service.getName());
}
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
在連接配接成功後,connection中會将binder資訊儲存起來。
ConcurrentHashMap<Class<? extends SunHermesService>, SunService> mHermesServices = new ConcurrentHashMap<>();
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
SunService sunService = SunService.Stub.asInterface(service);
mHermesServices.put(mClass, sunService);
}
這一步後就在連個程序中建立了連接配接。
3. getInstance
在B程序中執行:
IUserManager userManager = SunHermes.getDefault().getInstance(IUserManager.class);
這一步就是将IUserManager接口進行代理處理,并傳回代理。
public <T> T getInstance(Class<T> clazz){
return getProxy(SunHermesService.class, clazz);
}
private <T> T getProxy(Class<? extends SunHermesService> service, Class clazz){
ClassLoader classLoader = service.getClassLoader();
return (T) Proxy.newProxyInstance(classLoader, new Class<?>[]{clazz},
new SunHermesInvocationHandler(service, clazz));
}
其中的SunHermesInvocationHandler會将每一步請求都封裝成Request對象通過aidl傳遞到A程序處理,
再将處理結果進行傳回。
4. 調用方法
在B程序中調用userManager進行相關操作。在SunHermesInvocationHandler都會進行封裝。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Response responce= SunHermes.getDefault().sendObjectRequest(hermesService, clazz, method, args);
if (!TextUtils.isEmpty(responce.getData())){
ResponseBean responceBean = gson.fromJson(responce.getData(), ResponseBean.class);
if (responceBean.getData() != null){
String data = gson.toJson(responceBean.getData());
Class<?> returnType = method.getReturnType();
return gson.fromJson(data, returnType);
}
}
return null;
}
其中sendObjectRequest将接口中設定的 注解類名,請求的方法名,請求的參數等都封裝在requestBean中, 再把requestBean封裝在Request中,進行發送給A程序處理。
public <T> Response sendObjectRequest(Class<? extends SunHermesService> hermesServiceClass, Class<T> clazz, Method method, Object[] parameters) {
RequestBean requestBean = new RequestBean();
//設定class名
ClassId classId = clazz.getAnnotation(ClassId.class);
if (classId == null){
requestBean.setClassName(clazz.getName());
requestBean.setResultClassName(clazz.getName());
}else{
requestBean.setClassName(classId.value());
requestBean.setResultClassName(classId.value());
}
//設定方法名
if (method != null){
requestBean.setMethodName(TypeUtils.getMethodId(method));
}
//設定參數資訊,将參數都json化
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.setRequestParameters(requestParameters);
}
//封裝為Request對象
Request request = new Request(gson.toJson(requestBean), TYPE_GET);
//aidl傳遞
return serviceConnectionManager.request(hermesServiceClass, request);
}
serviceConnectionManager中的mHermesServices在連接配接成功後就将binder儲存進去了,這時取出進行處理。
public Response request(Class<? extends SunHermesService> sunHermesServiceClass, Request request){
SunService sunService = mHermesServices.get(sunHermesServiceClass);
if (sunService != null){
try {
return sunService.send(request);
} catch (RemoteException e) {
e.printStackTrace();
}
}
return null;
}
到這裡邏輯就回到了A程序中,A程序在SunHermesService中執行請求。
private SunService.Stub mBinder = new SunService.Stub() {
@Override
public Response send(Request request) {
ResponseMake responseMake = null;
switch (request.getType()){
case SunHermes.TYPE_GET:
//擷取單例
responseMake = new InstanceResponseMake();
break;
}
if (responseMake != null){
return responseMake.makeResponse(request);
}
return null;
}
};
其中makeResponse就是處理Request,也就是真正的執行代碼的地方。
public Response makeResponse(Request request){
//1. 取出request中的requestBean消息并轉換為requestBean。
RequestBean requestBean = gson.fromJson(request.getData(), RequestBean.class);
//2. 通過requestBean中設定的目标單例類的名字去加載類。
resultClass = typeCenter.getClassType(requestBean.getResultClassName());
//3. 通過requestBean中的設定的方法名擷取到要執行的具體方法。
setMethod(requestBean);
//4. 組裝參數,将參數進行還原組裝。
RequestParameter[] requestParameters = requestBean.getRequestParameters();
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];
}
//5. 執行方法,并得到執行結果
Object resultObj = invokeMethod();
//6. 将執行結果封裝為Response傳回給進行B
ResponseBean responseBean = new ResponseBean(resultObj);
return new Response(gson.toJson(responseBean));
}
在第 2 步中,由于之前已經将類名和class都儲存在mAnnotatedClasses中了,是以這時會先去那裡面去找,如果找不到在通過反射區加載class資訊。
public Class<?> getClassType(String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
//先去mAnnotatedClasses找類的消息,找不到在通過 Class.forName(name)的方法找。
Class<?> clazz = mAnnotatedClasses.get(name);
if (clazz == null) {
try {
clazz = Class.forName(name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return clazz;
}
在第 3 步中,setMethod中的代碼,其中通過反射去加載getInstance方法并得到單例。
這裡其實可以不把方法名寫成getInstance也行,但是前提是在A程序中要在一開始就将類put到objectCenter。
@Override
protected void setMethod(RequestBean requestBean) {
try {
instance = objectCenter.get(requestBean.getClassName());
if (instance == null){
Method getInstanceMethod = resultClass.getMethod("getInstance", new Class[]{});
if (getInstanceMethod != null){
instance = getInstanceMethod.invoke(null);
objectCenter.put(instance);
}
}
mMethod = typeCenter.getMethod(resultClass, requestBean);
} catch (Exception e) {
e.printStackTrace();
}
}
在調用typeCenter.getMethod擷取方法時,由于之前也是将類中的方法都儲存在了 mRawMethods 中了,這裡也是先去那裡面找。
public Method getMethod(Class<?> clazz, RequestBean requestBean) {
String name = requestBean.getMethodName();
if (name != null){
//先去 mRawMethods 中找方法,找不到在去通過反射加載。
mRawMethods.putIfAbsent(clazz, new ConcurrentHashMap<String, Method>());
ConcurrentHashMap<String, Method> methods = mRawMethods.get(clazz);
Method method = methods.get(name);
if (method != null){
return method;
}
//由于之前儲存的方法名是方法名(參數1..2..),是以這裡找到"("的位置,之前的就是方法名。
int pos = name.indexOf("(");
//還原參數資訊。
Class[] paramters = null;
RequestParameter[] requestParameters = requestBean.getRequestParameters();
if (requestParameters != null && requestParameters.length > 0){
paramters = new Class[requestParameters.length];
for (int i = 0; i < requestParameters.length; i++) {
paramters[i] = getClassType(requestParameters[i].getParameterClassName());
}
}
method = TypeUtils.getMethod(clazz, name.substring(0, pos), paramters);
methods.put(name, method);
return method;
}
return null;
}
在第 5 步中就是通過反射執行代碼:
mMethod.invoke(instance, mParameters);
最後,項目位址:https://github.com/736870598/SunHermes