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

先简单温习下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> mAnnotatedClasses;
      private void registerClass(Class clazz) {
            String className = clazz.getName();
            mAnnotatedClasses.putIfAbsent(className, clazz);
      }

private final ConcurrentHashMap, ConcurrentHashMap> mRawMethods;
      private void registerMethod(Class clazz){
             mRawMethods.putIfAbsent(clazz, new ConcurrentHashMap());
             ConcurrentHashMap 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, HermesServiceConnection> mHermesServiceConnections = new ConcurrentHashMap();
 
    public void bind(Context context, String packageName, Class 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, 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 getInstance(Class clazz){
        ...//将IUserStorage封装为ObjectWapper
        return getProxy(SunHermesService.class, clazz);
    }

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

private  T getProxy(Class 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原理探究)