Android 组件化之通讯(多模块,多进程)

项目地址:github.com/wutongke/Mo…javascript

1. 引子

写这篇文章主要是有两个缘由:java

  1. 以前写过一篇Android组件化开发实践,组件化最直接的表现形式就是工程中包含了多个业务Module,代码要解耦,可是业务间要解耦几乎是不可能的,因而就要涉及到业务间的通讯,表如今代码上就是Module间通讯。其实在文章提到的ActivityRouter就是模块间通讯很好的一个library,可是其主要做为Activity Router来使用,传递数据的能力有限(固然稍微改造一下代码,交换数据仍是很容易解决的)。
  2. 最近看到了Spiny同窗的Android架构思考(模块化、多进程) ,写的很是好,在掘金上分享后获得了很多同窗的点赞。尤为文中提到的多进程方案,本身也很是感兴趣,因而就去看了源码,颇有想象力,可是在使用时我的感受也有一些小问题,因而就fork了一份代码,开始对代码进行了一些修改,本文中将主要介绍一下这些修改。

2. 模块间通讯原理

咱们先来看一下架构图,图片来源于blog.spinytech.com/2016/12/28/…android

路由架构

从A、B到N的多个Module都引用了Common库,同时Main Module还引用了A、B、N这几个Module,通过这样的处理以后,全部的Module之间的相互调用就都消失了,耦合性下降,全部的通讯统一都交给Router来处理分发,而注册工做则交由Main Module去进行初始化。这个架构思想其实和Binder的思想很相似,采用C/S模式,模块之间隔离,数据经过共享区域进行传递。模块与模块之间只暴露对外开放的Action,因此也具有面向接口编程思想。git

架构图中每一个红色的Action都是能够提供的服务,而Provider是一个服务的集合,每一个Module中能够有一个或者多个Provider,当程序开始执行时,module会把Provider注册到Router Module。Action服务请求流程以下:github

  1. 任意代码建立一个RouterRequest,包含Provider和Action信息,向Router进行请求。
  2. Router接到请求,经过RouterRequest的Provider信息,在内部的HashMap中查找对应的Provider。
  3. Provider接到请求,在内部的HashMap中查找到对应的Action信息。
  4. Action调用invoke方法。
  5. 返回invoke方法生成的ActionResult。
  6. 将Result封装成RouterResponse,返回给调用者。

3. 多进程架构模块通讯原理

仍是先来看下架构图:编程

Router是JVM级别的单例模式,并不支持跨进程访问。也就是说,你的后台进程的全部Provider、Action,是注册给后台Router的。当你在前台进程调用的时候,根本调用不到其余进程的Action。json

解决方案是单独提取一个Wide Router模块,全部的Local Route与Wide Router经过进程间通讯的方式创建链接,Action请求若是在Local Router中找不到时,则经过WideRouter与其它进程创建链接,WideRouter充当局域网之间的路由。一次跨进程的Action请求以下图所示:架构

以上的内容主要来自于Android架构思考(模块化、多进程) ,目前也已经有了相应的demo,你们能够去尝试一下,体验多进程App的乐趣。app

4. 能够改进的地方

关于ModularizationArchitecture的使用,有相应的文档:ModularizationArchitecture 使用教程。我的在使用后感受又一些不太方便的地方,主要有三点:dom

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接口,而且分别提供了两个能够自定义的成员变量requestObjectresult,用户在创建请求数据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. 使用教程

项目地址:github.com/wutongke/Mo…

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,天天推送移动开发前沿技术文章:

wutongke

推荐阅读:

Android 组件化开发原理和配置

Android组件化开发实践