「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
背景
在 字节跳动开发者生态 的项目中,有大量的 IPC 通信为了避免每次编写 IPC 调用的时候都得写 AIDL,改过繁琐,以及影响编译速度,对于使用者来说,是一个负担。为了让开发者使用 IPC 就像调用本地对象一样简单,故而开发了一个基于 AIDL 的 IPC 通信框架。
功能特性
- 以普通Java接口代替AIDL接口
- 像 Retrofit 一样生成远程服务接口的IPC实现
- 可以自己扩展 CallAdapter 类似 Retrofit 的方式支持 Rxjava
- 支持的Call Adapters: Call
- 支持远程服务回调机制 IPC Callback
- 支持 AIDL 的所有数据类型
- 支持 AIDL 的所有数据定向tag(默认为 in ):in,out,inout
- 支持 AIDL 的 Oneway 关键字
- 支持同进程调用的时候降级处理为本地调用
- 支持 IPC Callback 对象处理的时候如果有 连接断开 的情况(Binder 死亡的时候),会在有注解 @ConnectError 的 void {methodName}()方法上进行回调通知
- 支持 IPC Callback 对象自动注册和解除注册
BdpIPC 支持AIDL所有数据类型
- Java语言中的所有原始类型 (如:int,long,char,boolean,等等)
- String
- CharSequence
- Parcelable
- List (List中的所有元素必须是此列表中支持的数据类型)
- Map (Map中的所有元素必须是此列表中支持的数据类型)
- JSONObject 和 JSONArrayType 也加入支持
- 已经支持的类型可以不用实现 Parcelable 序列化,其他尚未写明支持的,则需要
- 如果有其他额外的要求可以自行修改源码进行扩展
使用方式
在客户端App中,通过 Builder 创建 BdpIPC 对象,并通过 create() 方法生成一个 IRemoteService 远程接口的IPC实现
.packageName(*REMOTE_SERVICE_PKG*)
.action(*REMOTE_SERVICE_ACTION*)
//.usedProvider(getPackageName() + "." + RemoteProvider.URI_SUFFIX)
// Specify the callback executor by yourself
//.addCallAdapterFactory(OriginalCallAdapterFactory.create(callbackExecutor))
// Basic MainThread callback
// 设置指定 的 Callback<T>() 所在的线程
.addCallAdapterFactory(OriginalCallAdapterFactory.*create*(new Executor() {
@Override
public void execute(Runnable command) {
BdpThreadUtil.*runOnWorkThread*(command);
}
}))
// 设置指定 请求 发送处理的所在的线程
.dispatcher(new IDispatcher() {
@Override
public void enqueue(Runnable task) {
BdpThreadUtil.*runOnWorkThread*(task);
}
})
.addInterceptor(new CacheInterceptor())
// Basic
//.addCallAdapterFactory(OriginalCallAdapterFactory.create())
.build();
一切就绪,现在 mRemoteService 对象中的所有方法都是IPC方法,直接调用即可
int pid = mRemoteService.getPid();
mRemoteService.basicTypes(1, 2L, true, 3.0f, 4.0d, "str");
进阶使用
Call Adapters
在客户端App中,你可以copy并修改远程服务接口,包装方法的返回值
@RemoteInterface
public interface IRemoteService {
Call<Integer> getPid();
Call<Void> basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
在 BdpIPC.Builder 中注册对应的Call Adapter Factory,剩下的步骤基本和 Retrofit一致。
new BdpIPC.Builder(this)
...
.addCallAdapterFactory(OriginalCallAdapterFactory.create()) // Basic
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2
.build();
使用 Call 的方式 调用 IPC
Call<Integer> call = mRemoteTask.remoteCalculate(10, 20);
// Synchronous Request
// int result = call.execute();
// Log.d("IPCDemoActivity", "remoteCalculate() result:" + result);
// Async Request
call.enqueue(new Callback<Integer>() {
@Override
public void onResponse(Call<Integer> call, Integer response) {
Toast.makeText(IPCDemoActivity.this, "remoteCalculate() onResponse: " + response, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<Integer> call, Throwable t) {
Toast.makeText(IPCDemoActivity.this, "remoteCalculate() failure: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
使用 @Callback 处理远程服务接口回调
使用 @RemoteInterface 注解修饰远程服务回调接口 IRemoteCallback
@RemoteInterface
public interface IRemoteCallback {
void onValueChange(int value);
}
在远程方法中使用 @Callback 注解修饰 callback 参数
void registerCallback(@Callback IRemoteCallback callback);
内部通过动态代理 对callback 对象进行注册,并且支持自动解注册。
指定数据定向 TAG
你可以为远程方法的参数指定 @In , @Out ,或者 @Inout 注解,它标记了数据在底层 Binder 中的流向,跟 AIDL 中的用法一致
void directionalMethod(@In KeyEvent event, @Out int[] arr, @Inout Rect rect);
注意
所有非原始类型必须指定数据定向tag:`@In`,`@Out`,或者`@Inout`,用来标记数据的流向。原始类型默认是`@In`类型,并且不能指定其他值。
使用`@Out`或者`@Inout`修饰的Parcelable参数必须实现`SuperParcelable`接口,否则你必须手动添加此方法`public void readFromParcel(Parcel in)`。
使用 @OneWay 注解
你可以在远程方法上使用 @OneWay 注解,这会修改远程方法调用的行为。当使用它时,远程方法调用不会堵塞,它只是简单的发送数据并立即返回,跟AIDL中的用法一致
@OneWay
void onewayMethod(String msg);
使用 @ConnectError 注解
你可以在远程Callback 对象的某个方法上使用 @ConnectError 注解,这会使得这个远程方法会接监听
Binder 死亡导致的连接断开的的回调
使用@RemoteInterface(target = IpcServiceInterfaceImpl.class)
可以使用这个来达到 Server 端自动注册的效果。
原理就是通过 class 的名称,进行反射生成 对象实例(在第一次调用的时候 进行反射)
案例如下:
指定执行的线程( Callback<T> - 执行后的回调 和 Disptacher-发送请求的线程)
目前无法指定 远程实现类所在的线程,默认在 binder 线程
mBdpIPC = new BdpIPC.Builder(this)
.packageName(REMOTE_SERVICE_PKG)
.action(REMOTE_SERVICE_ACTION)
//.usedProvider(getPackageName() + "." + RemoteProvider.URI_SUFFIX)
// Specify the callback executor by yourself
//.addCallAdapterFactory(OriginalCallAdapterFactory.create(callbackExecutor))
// Basic MainThread callback
// 设置指定 的 Callback<T>() 所在的线程
.addCallAdapterFactory(OriginalCallAdapterFactory.create(new Executor() {
@Override
public void execute(Runnable command) {
BdpThreadUtil.runOnWorkThread(command);
}
}))
// 设置指定 请求 发送处理的所在的线程
.dispatcher(new IDispatcher() {
@Override
public void enqueue(Runnable task) {
BdpThreadUtil.runOnWorkThread(task);
}
})
.addInterceptor(new CacheInterceptor())
// Basic
//.addCallAdapterFactory(OriginalCallAdapterFactory.create())
.build();
性能
由于使用了动态代理,总体来说,对比于原生的的调用来说性能损失还是不小有 20% 左右的性能损失。但是对比起来,降低了更多的使用成本,以及,以及使用 Parcelable 的传输数据,总体还是比其他第三方框架(使用 JSON String 传输,再用转成对象的方式)的损耗来得少,而且可扩展性,以及代码复杂程度低了很多
一千次简单的 IPC 通信 重复3次 1000 次 IPC 的情况,取平均值
线上使用
目前 BdpIPC 已经在开发者生态的项目里稳定使用了。如果有场景需求的业务,欢迎来一起探讨。
设计原理
- 动态代理 使得 普通接口的最终实现是按照 走 binder 的调用
- 参考了 Okhttp 的 Intercept 模式,使得可以快速扩展拦截,比如加入内存缓存,或者其他统一的处理
- 参考了 Retrofit 的 CallAdapter 使得可以支持 Call 的调用方式,甚至Rxjava
- 引用了幻象引用(虚引用)使得跨进程的对象回收可以被同步支持,(当然也支持手动)
- 本框架并非全部完全重新创造,吸收了多个开源项目的设计方式打造而成,感谢开源
调用的流程图
整体流程
1.初始化过程
i. 本地对象使用 Service 方式进行获取远程 binder 对象(ITransfer),并且对远程 binder 对象(ITransfer)注册Callback BInder(ICallback) 对象
ii. 远程的Service 使用 BdpIPCBinderImpl(远程对象,内部持有一个Invoker 对象) 进行对 ServiceImplObject(远程服务接口实现对象)进行注册。
iii. 注册后的 ServiceImplObject 解析成 MethodInvoker (根据类名和方法名作为Key) 储存在远程 Invoker 对象中(后续已经加入自动注册的功能,无需手动注册)
- 本地对象调用方法 localCallback(Callback callback)。注释说明:这个方法是使用者自己的,不在库中
- 本地远程代理对象调用hook后的方法(动态代理)。,BdpIPC 向本地 Invoker 对象 注册 callback 对象(解析成MethodInvoker 对象),储存在 Invoker 内部
- Hook 方法后解析成 Request 对象,包装成 RemoteCall 对象,进行 请求的处理,使用 binder 发送一个 request 请求
- 远程 Invoker 对象发现是一个带有回调接口参数的方法,在实现方法的时候,动态代理对象实现对接口对象的实现,并且使用 BdpCallbackGc 进行GC 监听
6. 远程对象的接口被调用,使用之前注册的 Callback BInder (本地注册给远程的 Callback Binder(ICallback))发送 一个 Callback 的回调请求,和之前的请求逻辑一样包装成Request 在 RemoteCall 里面使用 binder 发送请求
7. 本地进程 生成的 CallbackBinder(ICallback) 对象 会收到 Response callback(Request request) ,使用本地invoker 处理 请求,从之前注册在本地 Invoker 的容器里找到对应 key值的 MethodInvoker(之前 callback 对象解析生成),执行callback 的请求处理
8. 本地进程触发callback 回调。
9. 如果远程 BdpCallbackGc 有监听 到需要Gc 的对象, 调用之前注册的 Callback BInder (本地注册给远程的 Callback Binder(ICallback)) 发送一个 GC 处理,本地 Invoker 收到GC 后处理需要处理的 本地 callback 对应的 MethodInvoker 对象,避免本地 invoker 对象持有太多的临时 callback 对象
重要类说明
BdpCallbackGc 远程进程监听临时的 callback 对象,一旦有gc 就会触发送消息给本地进程 对 invoker 里面的 calllback 响应对象进行清楚操作
BdpIPC 进行生成本地的远程代理对象,以及 远程binder 对象的获取和本地 callback 对象的注册
invoker 本地和远程都有的实际方法执行者
在远程: 内部持有各个远程服务接口实现对象的方法响应者(远程服务接口实现对象解析 MethodInvoker)
在本地: 内部持有callback 对象的方法响应者(Callback 对象解析生成的 MethodInvoker )
RemoteCall BdpIPC生成的代理对象的真正执行者,内部构造Request 对象 使用 ICallback 进行请求
ITransfer 执行请求的Binder 对象
ICallback 远程进行拿到的 本地进程的 binder 对象(本地进程初始化的时候注册过去的),用于执行 Callback 回调的 Binder 对象