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<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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容