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

@[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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容