InterStellar
InterStellar 是一个基于接口的组件间通信方案,包括同进程的本地接口调用和跨进程接口调用。 在 InterStellar 的世界里,不需要任何 aidl 接口及 Service,IPC 通信就和本地通信一样简单、方便。
注:之所以分成本地服务和远程服务这两种,是由于本地服务的接口可以传递各种类型的参数和返回值,而远程接口则受 binder 通信数据类型的限制,参数和返回值只能是基本类型或者实现了 Parcelable 接口的自定义类型。
特色
无需开发者进行 bindService()操作,也不用定义 Service,不需要定义任何 aidl 接口即可实现 IPC 通信
同步获取远程服务。抛弃了 bindService()这种异步获取的方式,改造成了同步获取
生命周期自动管理。可根据 Fragment 或 Activity 的生命周期进行提高或降低服务提供方进程的操作
支持 IPC 的 Callback,并且支持跨进程的事件总线
采用"接口+数据结构"的方式来实现组件间通信,这种方式相比协议的方式在于实现简单,维护方便
注意这里的服务不是 Android 中四大组件的 Service,而是指提供的接口与实现。为了表示区分,后面的服务均是这个含义,而 Service 则是指 Android 中的组件。
InterSteallar 和其他组件间通信方案的对比如下:
使用方便性 | 代码侵入性 | 互操作性 | 是否支持 IPC | 是否支持跨进程事件总线 | 是否支持页面跳转 | |
---|---|---|---|---|---|---|
InterSteallar | 好 | 较小 | 好 | Yes | Yes | No |
DDComponentForAndroid | 较差 | 较大 | 差 | No | No | Yes |
ARouter | 较好 | 较大 | 差 | No | No | Yes |
接入方式
首先在 buildscript 中添加 classpath:
classpath "org.qiyi.video.mcg.arch:plugin:$version"
这两个分别是核心代码库和 gradle 插件库的路径。 在 Application 或 library Module 中使用核心库:
implementation "org.qiyi.video.mcg.arch:core:$version"
在 application Module 中使用 gradle 插件:
apply plugin: 'org.qiyi.video.mcg.arch.plugin'
使用方式
为 Dispatcher 配置进程
由于 Dispatcher 负责管理所有进程信息,所以它应该运行在存活时间最长的进程中。 如果不进行配置,Dispatcher 默认运行在主进程中。 但是考虑到在有些 App 中,主进程不一定是存活时间最长的(比如音乐播放 App 中往往是播放进程的存活时间最长), 所以出现这种情况时开发者应该在 application module 的 build.gradle 中为 Dispatcher 配置进程名,如下:
dispatcher{
process ":downloader"
}
在这里,":downloader"进程是存活时间最长的.
初始化
最好是在自己进程的 Application 中进行初始化(每个进程都有自己的 InterStellar 对象),代码如下:
InterStellar.init(Context);
本地服务的注册与使用
本地服务注册
本地服务的注册有两种方法,一种是直接调用接口的全路径名和接口的实现,如下:
InterStellar.registerLocalService(ICheckApple.class.getCanonicalName(),new CheckApple());
还有一种是调用接口 class 和接口的实现,其实在内部也是获取了它的全路径名,如下:
InterStellar.registerLocalService(ICheckApple.class,new CheckApple());
其中 ICheckApple.class 为接口,虽然也可以采用下面这种方式注册:
InterStellar.registerLocalService("wang.imallen.blog.moduleexportlib.apple.ICheckApple",CheckAppleImpl.getInstance());
但是考虑到混淆问题,非常不推荐使用这种方式进行注册,除非双方能够协商一致使用这个 key(因为实际上 InterStellar 只需要保证有一个唯一的 key 与该服务对应即可).
本地服务使用
注册完之后,与服务提供方同进程的任何模块都可以调用该服务,获取服务的方式与注册对应,也有两种方式,一种是通过接口的 class 获取,如下:
ICheckApple checkApple = InterStellar.getLocalService(ICheckApple.class);
还有一种方法是通过接口的全路径名获取,如下:
ICheckApple checkApple = InterStellar.getLocalService(ICheckApple.class.getCanonicalName());
与注册类似,仍然不推荐使用如下方式来获取,除非双方始终协商好使用一个唯一的 key(但是这样对于新的调用方或者新加入的开发者不友好,容易入坑):
ICheckApple checkApple = InterStellar.getLocalService("wang.imallen.blog.moduleexportlib.apple.ICheckApple");
具体使用,可以察看 applemodule 中 LocalServiceDemo 这个 Activity。
本地接口的 Callback 问题
如果是耗时操作,由本地接口自己定义 Callback 接口,调用方在调用接口时传入 Callback 对象即可。
远程服务的注册与使用
远程服务的注册与使用略微麻烦一点,因为需要像实现 AIDL Service 那样定义 aidl 接口。
远程接口的定义与实现
依靠@in,@out,@inout,@oneway 这 4 个注解定义可进行 IPC 通信的接口,这四个注解分别对应于 aidl 中的 in,out,inout 和 oneway 修饰符。 函数参数不添加注解的话,默认为@in. 如下是一个典型的接口定义:
public interface IAppleService {
int getApple(int money);
float getAppleCalories(int appleNum);
String getAppleDetails(int appleNum, String manifacture, String tailerName, String userName, int userId);
@oneway
void oneWayTest(Apple apple);
String outTest1(@out Apple apple);
String outTest2(@out int[] appleNum);
String outTest3(@out int[] array1, @out String[] array2);
String outTest4(@out Apple[] apples);
String inoutTest1(@inout Apple apple);
String inoutTest2(@inout Apple[] apples);
}
而接口的实现跟普通接口基本一样,除了要为@out 和@inout 的参数赋值之外:
public class AppleService implements IAppleService {
@Override
public int getApple(int money) {
return money / 2;
}
@Override
public float getAppleCalories(int appleNum) {
return appleNum * 5;
}
@Override
public String getAppleDetails(int appleNum, String manifacture, String tailerName, String userName, int userId) {
manifacture = "IKEA";
tailerName = "muji";
userId = 1024;
if ("Tom".equals(userName)) {
return manifacture + "-->" + tailerName;
} else {
return tailerName + "-->" + manifacture;
}
}
@Override
public synchronized void oneWayTest(Apple apple) {
if(apple==null){
Logger.d("Man can not eat null apple!");
}else{
Logger.d("Start to eat big apple that weighs "+apple.getWeight());
try{
wait(3000);
//Thread.sleep(3000);
}catch(InterruptedException ex){
ex.printStackTrace();
}
Logger.d("End of eating apple!");
}
}
@Override
public String outTest1(Apple apple) {
if (apple == null) {
apple = new Apple(3.2f, "Shanghai");
}
apple.setWeight(apple.getWeight() * 2);
apple.setFrom("Beijing");
return "Have a nice day!";
}
@Override
public String outTest2(int[] appleNum) {
if (null == appleNum) {
return "";
}
for (int i = 0; i < appleNum.length; ++i) {
appleNum[i] = i + 1;
}
return "Have a nice day 02!";
}
@Override
public String outTest3(int[] array1, String[] array2) {
for (int i = 0; i < array1.length; ++i) {
array1[i] = i + 2;
}
for (int i = 0; i < array2.length; ++i) {
array2[i] = "Hello world" + (i + 1);
}
return "outTest3";
}
@Override
public String outTest4(Apple[] apples) {
for (int i = 0; i < apples.length; ++i) {
apples[i] = new Apple(i + 2f, "Shanghai");
}
return "outTest4";
}
@Override
public String inoutTest1(Apple apple) {
Logger.d("AppleService-->inoutTest1,apple:" + apple.toString());
apple.setWeight(3.14159f);
apple.setFrom("Germany");
return "inoutTest1";
}
@Override
public String inoutTest2(Apple[] apples) {
Logger.d("AppleService-->inoutTest2,apples[0]:" + apples[0].toString());
for (int i = 0; i < apples.length; ++i) {
apples[i].setWeight(i * 1.5f);
apples[i].setFrom("Germany" + i);
}
return "inoutTest2";
}
}
远程服务的注册
与本地接口的注册略有不同,远程接口注册的是继承了 Stub 类的 IBinder 部分,注册方式有传递接口 Class 和接口全路径名两种,如下:
InterStellar.registerRemoteService(IAppleService.class,new AppleService());
远程服务的使用
- 由于 InterStellar 利用 bindService()来提升通信过程中的优先级,对于在 Fragment 或者 Activity 中使用的情形,可在 onDestroy()时自动释放连接,所以需要调用先调用 with();
- 由于跨进程只能传递 IBinder,所以只能获取到远程服务的 IBinder 之后,再调用 XX.Stub.asInterface()获取到它的代理.
以 FragmentActivity 中使用为例,如下的 this 是指 FragmentActivity:
IAppleService appleService = InterStellar.with(BananaActivity.this).getRemoteService(IAppleService.class);
if (appleService != null) {
int appleNum = appleService.getApple(120);
Logger.d("appleNum:" + appleNum);
float calories = appleService.getAppleCalories(30);
Logger.d("calories:" + calories);
Apple apple = new Apple(1.3f, "Guangzhou");
String desc = appleService.outTest1(apple);
Logger.d("now apple:" + apple.toString());
}
其他的在 android.app.Fragment,android.support.v4.app.Fragment,以及普通的 Activity 中远程服务的使用类似,可查看 app module 中的 CustomFragment,CustomSupportFragment,FragActivity 等,不再赘述。
值得注意的是,远程服务其实既可在其他进程中调用,也可以在同进程中被调用,当在同一进程时,虽然调用方式一样,但其实会自动降级为进程内普通的接口调用,这个 binder 会自动处理.
生命周期自动管理的问题
对于 IPC,为了提高对方进程的优先极,在使用 InterStellar.with().getRemoteService()时会进行 bindService()操作。 既然进行了 bind 操作,那自然要进行 unbind 操作以释放连接了,目前有如下两种情形。
对于在 Fragment 或者 Activity 中,并且是在主线程中调用的情形,只要在获取远程服务时利用 with()传递 Fragment 或者 Activity 对象进去,InterStellar 就会在 onDestroy()时自动释放连接,不需要开发者做任何 unbind()操作。
对于在子线程或者不是 Fragment/Activity 中的情形,只能传递 Application Context 去获取远程服务。在使用完毕后,需要手动调用 InterStellar 的 unbind()释放连接:
public static void unbind(Class<?> serviceClass);
public static void unbind(Set<Class<?>> serviceClasses);
如果只获取了一个远程服务,那么就使用前一个 unbind()方法;否则使用后一个。
License
BSD-3-Clause. See the BSD-3-Clause file for details.
TODO List
- 远程服务的 Callback
- 事件总线
支持
- Sample 代码
- 阅读 Wiki 或者 FAQ
- 联系 bettarwang@gmail.com