1. 引子
写这篇文章主要是有两个原因:
- 之前写过一篇Android组件化开发实践,组件化最直接的表现形式就是工程中包含了多个业务Module,代码要解耦,但是业务间要解耦几乎是不可能的,于是就要涉及到业务间的通信,表现在代码上就是Module间通信。其实在文章提到的ActivityRouter就是模块间通信很好的一个library,但是其主要作为Activity Router来使用,传递数据的能力有限(当然稍微改造一下代码,交换数据还是很容易解决的)。
- 最近看到了Spiny同学的Android架构思考(模块化、多进程) ,写的非常好,在掘金上分享后得到了不少同学的点赞。尤其文中提到的多进程方案,自己也非常感兴趣,于是就去看了源码,很有想象力,但是在使用时个人感觉也有一些小问题,于是就fork了一份代码,开始对代码进行了一些修改,本文中将主要介绍一下这些修改。
2. 模块间通信原理
我们先来看一下架构图,图片来源于blog.spinytech.com/2016/12/28/…
从A、B到N的多个Module都引用了Common库,同时Main Module还引用了A、B、N这几个Module,经过这样的处理之后,所有的Module之间的相互调用就都消失了,耦合性降低,所有的通信统一都交给Router来处理分发,而注册工作则交由Main Module去进行初始化。这个架构思想其实和Binder的思想很类似,采用C/S模式,模块之间隔离,数据通过共享区域进行传递。模块与模块之间只暴露对外开放的Action,所以也具备面向接口编程思想。
架构图中每个红色的Action都是可以提供的服务,而Provider是一个服务的集合,每个Module中可以有一个或者多个Provider,当程序开始执行时,module会把Provider注册到Router Module。Action服务请求流程如下:
- 任意代码创建一个RouterRequest,包含Provider和Action信息,向Router进行请求。
- Router接到请求,通过RouterRequest的Provider信息,在内部的HashMap中查找对应的Provider。
- Provider接到请求,在内部的HashMap中查找到对应的Action信息。
- Action调用invoke方法。
- 返回invoke方法生成的ActionResult。
- 将Result封装成RouterResponse,返回给调用者。
3. 多进程架构模块通信原理
还是先来看下架构图:
Router是JVM级别的单例模式,并不支持跨进程访问。也就是说,你的后台进程的所有Provider、Action,是注册给后台Router的。当你在前台进程调用的时候,根本调用不到其他进程的Action。
解决方案是单独提取一个Wide Router模块,所有的Local Route与Wide Router通过进程间通信的方式建立连接,Action请求如果在Local Router中找不到时,则通过WideRouter与其它进程建立连接,WideRouter充当局域网之间的路由。一次跨进程的Action请求如下图所示:
以上的内容主要来自于Android架构思考(模块化、多进程) ,目前也已经有了相应的demo,大家可以去尝试一下,体验多进程App的乐趣。
4. 可以改进的地方
关于ModularizationArchitecture的使用,有相应的文档:ModularizationArchitecture 使用教程。个人在使用后感觉又一些不太方便的地方,主要有三点:
4.1 通信数据格式问题
目前发起请求时传递的数据是RouterRequest
,接收的数据是RouterReponse
,两种类型的数据中包含的只有字符串数据,在实际的进程间通信时传递的数据也是字符串(代码中转为了json数据)。这种方式不能传递自定义的数据,在数据使用时也要手动解析字符串,比较繁琐。
其实Android为进程间通信提供了Parcelable,可以通过这种方式非常方便地传递数据。
4.2 线程切换问题
demo中采用新建线程的方式请求异步数据:
final RouterResponse response = LocalRouter.getInstance(MaApplication.getMaApplication())
.route(MainActivity.this, RouterRequest.obtain(MainActivity.this)
.domain("com.spinytech.maindemo:music")
.provider("music")
.action("play"));
response.isAsync();
new Thread(new Runnable() {
@Override
public void run() {
try {
final String temp = response.getData();
final long time = System.currentTimeMillis() - startTime;
handler.post(new Runnable() {
@Override
public void run() {
try {
Toast.makeText(MainActivity.this, "async:" + response.isAsync() + " cost:" + time + " response:" + response.get(), Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();复制代码
代码看起来不够优雅,采用Rxjava 的方式会使得线程切换更加优雅,Router模块可以使用Rxjava的方式返回结果。
4.3 Provider、Action 注册问题
代码中Provider、Action需要手动注册,如果增加一个Action,需要有多个地方进行变动,这里可以采用apt的方式来自动进行注册。
5. 解决方案
针对以上的三个问题,对代码进行来部分修改,如下:
5.1 进程间通信数据格式的修改
请求数据RouterResquest
和返回数据MaActionResult
分别实现了Parcelable接口,并且分别提供了两个可以自定义的成员变量requestObject
和result
,用户在建立请求数据RouterResquest
和返回数据MaActionResult
可以把自定义的数据传递进去,需要注意的是传递的自定义类型也要实现Parcelable接口。
//自定义数据
public class Song implements Parcelable {
public String name;
public Song(String name) {
this.name = name;
}
protected Song(Parcel in) {
name = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Song> CREATOR = new Creator<Song>() {
@Override
public Song createFromParcel(Parcel in) {
return new Song(in);
}
@Override
public Song[] newArray(int size) {
return new Song[size];
}
};
}复制代码
//RouterResquest中设置了自定义类型Song
RouterRequestUtil.obtain(MainActivity.this)
.domain("com.spinytech.maindemo:music")
.provider("music")
.action("play")
.reqeustObject(new Song("see you"))
)
//MaActionResult中设置自定义类型Song
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_SUCCESS)
.msg("play success")
.result(new Song("lili"))
.build();复制代码
5.2 Provider、Action自动生成
// 注解Provider,程序运行后将自动注册MusicProvider到com.spinytech.maindemo:music到Router
@Provider(processName = "com.spinytech.maindemo:music")
public class MusicProvider extends MaProvider{
@Override
protected String getName() {
return "music";
}
}
// 注解Action,程序运行后将自动注册PlayAction到Provider
@Action(processName = "com.spinytech.maindemo:music", providerName = "music")
public class PlayAction implements MaAction<Song>复制代码
5.3 Rxjava 的引入
引入Rxjava之后,修改LocalRoute的route方法,使之返回Observable<MaActionResult>
,在调用时可以非常方便地使用Rxjava切换线程:
LocalRouter.getInstance(MaApplication.getMaApplication())
.rxRoute(MainActivity.this, RouterRequestUtil.obtain(MainActivity.this)
.domain("com.spinytech.maindemo:pic")
.provider("pic")
.action("pic")
.data("is_big", "0"))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.from(ThreadPool.getThreadPoolSingleton()))
.subscribe(new Consumer<MaActionResult>() {
@Override
public void accept(MaActionResult maActionResult) throws Exception {
Toast.makeText(MainActivity.this, maActionResult.getMsg(), Toast.LENGTH_SHORT).show();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "error", Toast.LENGTH_SHORT).show();
}
});复制代码
6. 使用教程
6.1 在项目中集成
6.1.1 在project的build.gradle中dependencies块中支持apt:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'复制代码
6.1.2 所有Module中配置apt插件:
apply plugin: 'com.neenbedankt.android-apt'复制代码
dependencies块中设置:
apt 'com.github.wutongke.modularization:compile:1.1.1'
compile 'com.github.wutongke.modularization:macore:1.1.1'复制代码
6.2 创建自定义Application
6.2.1 实际Application
我们知道一个app中只有一个Application,所以在主Module中定义Application,然后在其它模块中根据需要实现逻辑Application即可,然后启动时注册逻辑Application,即可管理其生命周期:
public class MyApplication extends MaApplication {
//多进程中注册各个进程的Router,可以参考第3小节的原理图
@Override
public void initializeAllProcessRouter() {
WideRouter.registerLocalRouter("com.spinytech.maindemo",MainRouterConnectService.class);
WideRouter.registerLocalRouter("com.spinytech.maindemo:music",MusicRouterConnectService.class);
WideRouter.registerLocalRouter("com.spinytech.maindemo:pic",PicRouterConnectService.class);
}
//注册各个模块中的逻辑Application,每个模块中可以注册多个逻辑
//Applicatoin,设置优先级,可以调整模块中多个逻辑Application的
//调用顺序
@Override
protected void initializeLogic() {
registerApplicationLogic("com.spinytech.maindemo",999, MainApplicationLogic.class);
registerApplicationLogic("com.spinytech.maindemo",998, WebApplicationLogic.class);
registerApplicationLogic("com.spinytech.maindemo:music",999, MusicApplicationLogic.class);
registerApplicationLogic("com.spinytech.maindemo:pic",999, PicApplicationLogic.class);
}
//设置是否支持多进程
@Override
public boolean needMultipleProcess() {
return true;
}
}复制代码
当然这个自定义的Application需要注册到manifest文件中。
使用多进程提供服务的模块需要继承LocalRouterConnectService
,并且在manifest中注册服务:
public class MusicRouterConnectService extends LocalRouterConnectService {
@Override
public boolean onUnbind(Intent intent) {
Log.e("MRCS","onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("MRCS","onDestroy");
}
}复制代码
<service android:name=".MusicRouterConnectService"
android:process=":music"/>复制代码
6.2.2 逻辑Application
逻辑Application通过继承BaseApplicationLogic,实现相应的方法即可被回调。
public class BaseApplicationLogic {
protected MaApplication mApplication;
public BaseApplicationLogic() {
}
public void setApplication(@NonNull MaApplication application) {
mApplication = application;
}
public void onCreate() {
}
public void onTerminate() {
}
public void onLowMemory() {
}
public void onTrimMemory(int level) {
}
public void onConfigurationChanged(Configuration newConfig) {
}
}
//逻辑Application只需要继承BaseApplicationLogic,注册后
//生命周期会被回调
public class MainApplicationLogic extends BaseApplicationLogic {
@Override
public void onCreate() {
super.onCreate();
}
}复制代码
6.3 自定义Provider和Action
定义Provider
@Provider(processName = "com.spinytech.maindemo:music")
public class MusicProvider extends MaProvider{
@Override
protected String getName() {
return "music";
}
}复制代码
定义Action
@Action(processName = "com.spinytech.maindemo:music", providerName = "music")
public class PlayAction implements MaAction<Song> {
@Override
public boolean isAsync(Context context, RouterRequest<Song> requestData) {
return false;
}
@Override
public MaActionResult invoke(final Context context, final RouterRequest<Song> requestData) {
Intent intent = new Intent(context, MusicService.class);
intent.putExtra("command", "play");
context.startService(intent);
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_SUCCESS)
.msg("play success")
.result(new Song("lili"))
.build();
Handler handler = new Handler(context.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
if (requestData != null && requestData.getRequestObject() != null) {
Toast.makeText(context, "歌曲名字:" + requestData.getRequestObject().name + "(并不知道)", Toast.LENGTH_SHORT).show();
}
}
});
Logger.d("com.spinytech", requestData.getRequestObject().name);
return result;
}
@Override
public String getName() {
return "play";
}
}复制代码
可以看到定义Provider和Action时分别使用了@Provider
和@Action
注解,这样可以在程序编译时完成自动的注册,不需要手动注册到Router了。
其中 @Provider
需要设置进程名字,@Action
需要设置进程名字和注册到的Provider名字:
@Provider(processName = "com.spinytech.maindemo:music")
@Action(processName = "com.spinytech.maindemo:music", providerName = "music")复制代码
6. 4 调用Action
6.4.1 建立Action调用
首先需求建立一个请求RouterRequest
,说明要请求的内容:
RouterRequestUtil.obtain(MainActivity.this)
.domain("com.spinytech.maindemo:music")
.provider("music")
.action("play")
.reqeustObject(new Song("see you"))复制代码
可以通过RouterRequestUtil的obtain方法快速建立请求,上例中请求的Action位于"com.spinytech.maindemo:music"进程,Provider是"music",Action是"play",并且传递了相应的参数new Song("see you")。
然后使用Rxjava的方式请求Action:
LocalRouter.getInstance(MaApplication.getMaApplication())
.rxRoute(MainActivity.this, RouterRequestUtil.obtain(MainActivity.this)
.domain("com.spinytech.maindemo:music")
.provider("music")
.action("play")
.reqeustObject(new Song("see you"))
)
.subscribeOn(Schedulers.from(ThreadPool.getThreadPoolSingleton()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<MaActionResult>() {
@Override
public void accept(MaActionResult maActionResult) throws Exception {
Toast.makeText(MainActivity.this, maActionResult.getMsg(), Toast.LENGTH_SHORT).show();
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(MainActivity.this, "error", Toast.LENGTH_SHORT).show();
}
});复制代码
6.4.2 处理请求
在music模块中处理刚刚发出的请求,6.3中定义的Provider和Action其实就是处理6.4.1中的请求的,并且返回了MaActionResult
:
MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_SUCCESS)
.msg("play success")
.result(new Song("lili"))
.build();复制代码
7. 总结
6小节介绍了ModularizationArchitecture的基本使用,ModularizationArchitecture提供了完整的demo,大家可以clone代码参考,有任何问题可以在该项目下提issue,欢迎交流。
注意:在demo的gradle.properties中可以配置Local属性,从而根据需要设置使用本地的library,还是远端的library,更改Local后注意sync。
欢迎关注公众号wutongke,每天推送移动开发前沿技术文章:
推荐阅读: