美文网首页
Android进程通信框架Hermes原理探究

Android进程通信框架Hermes原理探究

作者: R7_Perfect | 来源:发表于2019-12-18 11:50 被阅读0次

    先简单温习下AIDL通信机制:


    162758e43a62d3cc.png
    服务端

    创建Service等待客户端发来连接请求。
    创建AIDL文件,将暴露给客户端使用的接口在这个文件中声明。
    在Service中实现这个接口。

    客户端

    绑定服务端的Service
    绑定成功后讲返回的binder对象转化为AIDL接口所属的类型。
    使用AIDL中的方法

    Hermes的使用流程:

    • 假设A进程为主进程,B进程为其他进程。
    • 定义一个接口,改接口需要annotation标注classid和methodid。该接口主要提供给B进程使用。
    • 有一个单例类实现该接口。
    • A进程中register这个单例类
    • B进程使用Hermes.newInstance(接口class)获得接口对象,并且可以调用其中method

    register

        public void register(Class<?> clazz) {
            TypeUtils.validateClass(clazz);
            registerClass(clazz);
            registerMethod(clazz);
        }
    
    private final ConcurrentHashMap<String, Class<?>> mAnnotatedClasses;
          private void registerClass(Class<?> clazz) {
                String className = clazz.getName();
                mAnnotatedClasses.putIfAbsent(className, clazz);
          }
    
    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);
                 }
             }
    
        public static String getMethodId(Method method) {
            MethodId methodId = method.getAnnotation(MethodId.class);
            if (methodId != null) {
                return methodId.value();
            } else {
                StringBuilder result = new StringBuilder(method.getName());
             result.append('(').append(getMethodParameters(method.getParameterTypes())).append(')');
                return result.toString();
            }
        }
    
    

    其中registerClass保存到mAnnotatedClasses中
    registerMethod是将class中的所有方法都保存到mRawMethods中

    connect

    调用的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);
            }
    

    在连接成功后,HermesServiceConnection会将binder信息保存起来。

    ConcurrentHashMap<Class<? extends SunHermesService>, SunService> mHermesServices = new ConcurrentHashMap<>();
     
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
               IHermesService hermesService = IHermesService.Stub.asInterface(service);
               mHermesServices.put(mClass, hermesService);
            }
    

    getInstance

    在B进程中执行:

    mUserStorage = Hermes.getInstance(IUserStorage.class);
    public <T> T getInstance(Class<T> clazz){
            ...//将IUserStorage封装为ObjectWapper
            return getProxy(SunHermesService.class, clazz);
        }
    

    可以看到使用了动态代理技术:

    private <T> T getProxy(Class<? extends SunHermesService> service, Class clazz){
            Class<?> clazz = object.getObjectClass();
            T proxy =  (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz},
                        new HermesInvocationHandler(service, object));
        }
    

    其中的HermesInvocationHandler会将每一步请求都封装成Request对象通过aidl传递到A进程处理,再将处理结果进行返回。

    public class HermesInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] objects) {
            Reply reply = mSender.send(method, objects);
                if (reply == null) {
                    return null;
                }
                if (reply.success()) {
                    return reply.getResult();
                } else {
                    return null;
                }
        }
    }
    

    mSender将ObjectWapper拆成MethodWapper和ParameterWrapper,并放在mail对象中
    send方法最终调用的是HermesService里aidl中的send()方法

           @Override
            public Reply send(Mail mail) {
             Receiver receiver = ReceiverDesignator.getReceiver(mail.getObject());
                    int pid = mail.getPid();
                    IHermesServiceCallback callback = mCallbacks.get(pid);
                    if (callback != null) {
                        receiver.setHermesServiceCallback(callback);
                    }
                    return receiver.action(mail.getTimeStamp(), mail.getMethod(), mail.getParameters());
            }
    

    receiver将mail对象解析出来并执行:

    public final Reply action(long methodInvocationTimeStamp, MethodWrapper methodWrapper, ParameterWrapper[] parameterWrappers) throws HermesException{
            setMethod(methodWrapper, parameterWrappers);
            setParameters(methodInvocationTimeStamp, parameterWrappers);
            Object result = invokeMethod();
            if (result == null) {
                return null;
            } else {
                return new Reply(new ParameterWrapper(result));
            }
        }
    

    总结:

    每次启动进程的时候都会重新创建Application,所以同一个程序包下的Application会被创建N次,也就是说在同一个应用包下开的进程会重用所有的资源(清单文件这么配置android:process=":f"就开了进程),所以发消息时如果发现当前的进程不是主进程,则会先绑定一个主进程的Service。这样B想用和A通信是通过Service桥梁来实现。
    那么Service怎么将参数回调到A指定的方法中,这里还是通过A注册的时候将当前对象和方法的参数对象的class产生一对一的绑定,也就是参数的class是key,当前A对象为value注册进集合中,并把有注解的method保存到集合。在B发消息的时候是带着参数对象的,将对象序列话,得到class,重新组装一个实现了序列话接口的对象,发送到Service端,Service通过class得到注册的A对象和方法,利用反射调用A对象的方法,将参数反序列话传过去,最终实现两个进程Activity的之间的通信。


    20181107170150936.png

    相关文章

      网友评论

          本文标题:Android进程通信框架Hermes原理探究

          本文链接:https://www.haomeiwen.com/subject/tjaynctx.html