天天看點

仿Hermes實作跨程序通信的核心原理

一. Hermes說明

Hermes是之前餓了麼開源的一套專門用于跨程序通信的架構,基于aidl進行封裝,但是在使用的時候完全不用考慮aidl,很強大。位址是:https://github.com/Xiaofei-it/Hermes。

本文是實作了Hermes核心原理。

二. 使用流程

  1.  假設A程序為主程序,B程序為其他程序。
  2.  在A程序和B進行中必須要有一個完全相同的接口。該接口主要提供給B程序使用。
  3.  A程序中要有一個單例類實作該接口。
  4.  在B進行中該接口類上面要加上注解 @ClassId("實作類的全路徑")
  5.  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

繼續閱讀