美文网首页Android TV
Android TV开发-桌面 跨进程通信(IPC) 详解

Android TV开发-桌面 跨进程通信(IPC) 详解

作者: 冰雪情缘long | 来源:发表于2019-07-30 09:59 被阅读1次

    @[TOC]

    1. 写在前面

    Android 进程间通信 的 几种方式:

    • 四大组件间传递Bundle
    • 使用文件共享方式,多进程读写一个相同的文件,获取文件内容进行交互;
    • 使用Messenger,一种轻量级的跨进程通讯方案,底层使用AIDL实现(实现比较简单,博主开始本文前也想了一下是否要说一下这个东西,最后还是觉得没有这个必要,Google一下就能解决的问题,就不啰嗦了);
    • 使用AIDL(Android Interface Definition Language),Android接口定义语言,用于定义跨进程通讯的接口;
    • 使用ContentProvider,常用于多进程共享数据,比如系统的相册,音乐等,我们也可以通过ContentProvider访问到;
    • Socket(Domain Socket),Linux 桌面的DBus就是使用此方式实现的.
    • ... ...

    各个优缺点对比:


    在这里插入图片描述

    多个app通过统一的接口操作,不建议起线程的耗时任务,支持 RPC(远程过程调用,进程A调用进程B提供的 函数/方法)

    比如 登陆接口,支付,定位服务 等等

    这里唯一比较好的应该属于 AIDL 的方式了,不仅有回调,还可以传参数,返回参数。

    但是 通常要写很多代码,操作繁杂,麻烦;不同业务的跨进程调用,不易复用。

    我们在下面的文章将讲解用另一种简洁的方式实现。

    2. 跨进程通信的实现

    我编写 了一个 XBus(跨进程通信)的库 ,欢迎下载体验

    在这里插入图片描述
    耗费了几天时间,完成了这个 跨进程通信 的代码,后续不断完善吧,下面我讲解下我写这个代码的思路。

    涉及的知识点

    流程图

    在这里插入图片描述
    大概思路如下

    第1步:进程A 绑定 进程B 的AIDL 服务 并 返回 一个 XBusAidl.Stub 的AIDL;进程B注册好需要被调用的函数.

    // 绑定服务
    Intent intent = new Intent(context, XBusService.class);
    context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    
    // 绑定回调(成功,失败的处理)
    ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mXBusAidl = XBusAidl.Stub.asInterface(service);
        }
    };
        
    // AIDL 接口,主要用于传输相应的调用参数以及返回.  关于 Response,Request 可以查看相应的代码.  
    interface XBusAidl {
        Response run(in Request request);
    }
    
    
    // 进程B 注册需要被调用的相关函数
    XBus.getInstance().register(ITestData.class, TestData.class);
    
    // 之所以要注册,是因为接口的调用与实现的类不能一一对应起来,所以需要提前注册.
    public void register(Class<?> face, Class<?> impl) {
        mRegisterClassMap.put(face, impl);
    }
    

    第2步:函数调用,触发 动态代理,将 类名,函数,参数,调用 绑定服务返回的 XBusAidl.Stub 的AIDL 的 run函数传过去

    // 准备被调用的接口
    // @ClassId("TestData")
    public interface ITestData {
        public String testtesttest(int i, String s);
    }
    
    
    // 动过动态代理,然后调用,触发 invoke,然后将 类名,函数,参数传过去,接受返回值,搞定.
    ITestData testData = XBus.getInstance().getCreateCall(ITestData.class);
    String result = testData.testtesttest(10, "测试函数");
    
    
    public <T> T getCreateCall(Class<T> tClass) {
        Class<?>[] interfaces = new Class[]{tClass};
        XBusHandler handler = new XBusHandler(tClass, mXBusAidl);
        Object proxy = Proxy.newProxyInstance(tClass.getClassLoader(), interfaces, handler);
        return (T) proxy;
    }
    
    class XBusHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            String clazzName = clazz.getName();
            // 1. 创建 Request.
            Request request = new Request();
            
            // 这里使用了 JSON 与 Bundle的传参数与接收返回值 的方式,还需要继续测试,才知道那种方式更优.
            // 主要是思路,无论是 JSON 还是 Bundle,斗不过是将数据传输过去而已,本文章使用Bundle讲解.
            // 2. bundle 传递 args参数. 方法,类名
            Bundle bundle = new Bundle();
            bundle.putSerializable(Request.ARGS_KEY, args);
            bundle.putString(Request.METHOD_KEY, methodName);
            bundle.putString(Request.CLAZZ_KEY, clazzName);
            request.setBundle(bundle);
    
            // 3. AIDL 远程调用函数run (函数执行过程)
            Response response = xBusAidl.run(request);
            
            // 4. 处理 返回值.
            Bundle bundle1 = response.getBResult();
            return bundle1.getSerializable(Response.RESULT_KEY);
        }
    }
    

    第3步:处理 函数执行,返回值.

    // 绑定服务返回的 XBusAidl.Stub 的AIDL 的 run 函数 执行.
    public Response run(Request request) throws RemoteException {
        return runBundle(request);
    }
    
    private Response runBundle(Request request) {
        Bundle bundle = request.getBundle();
        Object[] bargs = (Object[]) bundle.getSerializable(Request.ARGS_KEY);
        String clazzName = bundle.getString(Request.CLAZZ_KEY);
        String method = bundle.getString(Request.METHOD_KEY);
        // 1. 创建Response.
        Response response = new Response();
        // 2. 函数调用
        Class<?> iclazz = Reflect.on(clazzName).get();
        Object createObject = XBus.getInstance().getCreateObject(iclazz); // 获取已经注册的相关函数
        Object result = Reflect.on(createObject).call(method, bargs).get();
        // 3. 处理返回值
        bundle.putSerializable(Response.RESULT_KEY, (Serializable) result);
        response.setBResult(bundle);
        // 4. 返回response,response 带有 code, message,主要是标志函数是否执行成功等信息.
        return response;
    }
    
    // 这里是进程B已经注册的函数
    public <T> T getCreateObject(Class<?> face) {
        if (mRegisterClassMap.containsKey(face)) {
            Class<?> aClass = mRegisterClassMap.get(face); // 根据对应的接口找到对应的类
            return Reflect.on(aClass).create().get(); // 创建对应的类
        }  
        return null;
    }
    

    原理已经分析完了,大概步骤就是,注册AIDL服务,使用动态代理调用函数,处理函数执行,返回处理,没了,就是这么简单.

    在这里插入图片描述

    3. 扩展思考

    • 是否考虑过一种方式,只提供中转分发(服务一直存在),不作为提供服务。
    • AIDL 是否可以使用 Socket 来替代,是可以的喔(DBus就是例子),不过需要使用JSON或者其它方式来序列化,反序列化.
    • 注册相应的接口与类,感觉还是比较麻烦,后续准备加入注解的方式支持.
    @ClassId("TestData")
    public interface ITestData {
        public String testtesttest(int i, String s);
    }
    
    • 现在只是类的函数的调用,还需要加入对回调的支持.
    • Bitmap,View,Drawable 的传输是否有问题,测试一下. 如果使用JSON又这么办?
    // 看到下面的代码,我感觉JSON的使用,心都凉了一半.
    // 来自网上的一段代码,关于 Bitmap 转换成JSON的,感觉效果应该不是很好吧(如果图片几MB).
    /*
     * This functions converts Bitmap picture to a string which can be
     * JSONified.
     * */
    private String getStringFromBitmap(Bitmap bitmapPicture) {
       final int COMPRESSION_QUALITY = 100;
       String encodedImage;
       ByteArrayOutputStream byteArrayBitmapStream = new ByteArrayOutputStream();
       bitmapPicture.compress(Bitmap.CompressFormat.PNG, COMPRESSION_QUALITY,
       byteArrayBitmapStream);
       byte[] b = byteArrayBitmapStream.toByteArray();
       encodedImage = Base64.encodeToString(b, Base64.DEFAULT);
       return encodedImage;
     }
    
    /*
     * This Function converts the String back to Bitmap
     * */
    private Bitmap getBitmapFromString(String stringPicture) {
       byte[] decodedString = Base64.decode(stringPicture, Base64.DEFAULT);
       Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
       return decodedByte;
    }
    

    4. 参考资料

    https://github.com/LiushuiXiaoxia/Bifrost

    跨进程通信框架. https://github.com/Xiaofei-it/Hermes

    爱奇艺的跨进程通信框架 https://github.com/iqiyi/Andromeda

    https://github.com/kuangfrank/PieBridge

    相关文章

      网友评论

        本文标题:Android TV开发-桌面 跨进程通信(IPC) 详解

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