关于组件化, 相信很多人耳熟能详, 网上的组件化框架也如雨后春笋, 最近做了一些 组件化调研, 开始着手探索适合自己项目的一条组件化之路, 在此分享一下, 欢迎指正交流
在阅读了大部分组件化相关的文章和框架之后, 大致能总结出以下几点:
1 组件的路由的注册和中央路由的采集
2 组件间的通信 (跨进程) 和打包之后的模块间通信(同进程), 以及其兼容(跨 / 同进程)
3 组件向外提供服务, 以及组建间的服务的同异步互调, 以及其兼容(跨 / 同进程)
我们的目标以及达到的成果:
最小代价组件化, 最简单的配置最灵活的切换
组件路由自动化注册, 中央路由自动化采集
服务自动注册, 兼容同异步, 兼容跨 / 同进程
代码量少的可怜, 实在一句多余的代码都不想写的懒人体验
一组建自动注册和中央路由自动收集 APT+SPI
第一步, 路由 Map 自动化注册交给(APT)annotationProcessor
具体细节无需多言, 注解标识目标 url + 注解处理器集中处理生成代码, 借助 javapoet 和 auto-service, 实现自动注册路由到组件 Map
注意这一步, 生成的代码类都会被标注上 @AutoService, 成为路由注册服务的提供者
第二步, 中央路由采用 SPI 自动收集
Java 提供的 SPI 全名就是 Service Provider Interface, 下面是一段官方的解释,, 其实就是为某个接口寻找服务的机制, 有点类似 IOC 的思想, 将装配的控制权移交给 ServiceLoader
SPI 在平时我们用到的会比较少, 但是在 Android 模块开发中就会比较有用, 不同的模块可以基于接口编程, 每个模块有不同的实现 service provider, 然后通过 SPI 机制自动注册到一个配置文件中, 就可以实现在程序运行时扫描加载同一接口的不同 service provider 这样模块之间不会基于实现类硬编码, 可插拔
注上 @AutoService 的接口实现类, 会在 META-INF 下自动生成接口的服务实现列表
META-INF 下自动生成接口的服务实现列表
在最终打包的 Application 自动采集子组件的路由器:
- ServiceLoader<IRouterRulesCreator> loader = ServiceLoader.load(IRouterRulesCreator.class);
- for (IRouterRulesCreator rules : loader) Router.addRouterRule(rules);
在独立运行的组件 Application 也是如此
第三步, Messager 扩展 OkBus 实现跨 / 同进程的无差别操作
在未组件化之前, 使用 OkBus 的 APP 架构图为:
未组件化之前 OkBus 的 APP 架构图
在同一进程中, 使用 OKBus 在不同模块间传递 Message 数据
组件化之后的架构图为:
Messager 扩展 OkBus 实现跨进程
特点:
组件化和非组件化对 OkBus 来说使用方式完全一样, 无感知无差别, 旧代码基本不用改
Messenger 相对于 ContentProviderSocketAIDL 操作最简单, 它是对 AIDL 的 Message 传递做了封装, Message 可以作为任何序列数据的载体
只有一个服务器, 再多组件整体架构也不冗乱
自动判断单组件运行和多组件打包状态,
模块自动化注册, 数据自动经过服务器转发, 可以到达 APP 内的组件的任何一环
实现原理: 服务器保存客户端注册的信使, 收到消息时, 遍历转发
- //1. 根据模块 ID 保存所有的客户端信使
- private ConcurrentHashMap < Integer,
- Messenger > mClientMessengers = new ConcurrentHashMap < >();
- //2. 收到消息时转发给其他模块的处理器, 来源模块除外
- Enumeration keys = mClientMessengers.keys();
- while (keys.hasMoreElements()) {
- int moduleId = (int) keys.nextElement();
- Messenger mMessenger = mClientMessengers.get(moduleId);
- if (moduleId != msg.arg1) { // 不是目标来源模块, 进行分发
- Message _msg = Message.obtain(msg);
- try {
- mMessenger.send(_msg);
- } catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
第四步, OkBus 实现同异步服务互调
服务互调就是 OkBus 的两个消息, 触发服务一个消息, 返回结果一个消息
异步互调就是两个消息, 对应两个回调
- /**
- * 服务规则:
- * 1 服务的请求 ID 必须是负值(正值表示事件)
- * 2 服务的请求 ID 必须是奇数, 偶数表示该服务的返回事件,
- * 即: requestID-1 = returnID
- * 例如 -0xa001 表示服务请求 -0xa002 表示 - 0xa001 的服务返回
- */
- /**
- * 注册服务
- *
- * @param serviceId 服务 id
- * @param callback 服务调用的回调
- * @param <T> 服务返回的数据范型
- */
- public <T> void registerService(final int serviceId, final CallBack<T> callback) {
- okBus.unRegister(serviceId);// 服务提供者只能有一个
- okBus.register(serviceId, new Event() {
- @Override
- public void call(Message msg) {
- //TODO 优化到子线程
- OkBus.getInstance().onEvent(serviceId - 1, callback.onCall(msg));
- }
- });
- }
- /**
- * 异步调用服务
- *
- * @param serviceId 服务 id
- * @param callback 回调
- */
- public void fetchService(final int serviceId, final Event callback) {
- if (serviceId > 0 || serviceId % 2 == 0) {
- assert false : "请求 ID 必须是负奇值!";
- return;
- }
- //1 先注册回调
- okBus.register(serviceId - 1, new Event() {
- @Override
- public void call(Message msg) {
- callback.call(msg);
- okBus.unRegister(serviceId - 1);// 服务是单次调用, 触发后即取消注册
- }
- }, Bus.BG);
- //2 通知目标模块
- okBus.onEvent(serviceId);
- }
两个即时的消息一来一回, 就完成了服务的互调, 服务因为是实时调用, 因此调用完之后立马注销回调即可
同步调用则是在异步调用的基础上加了锁:
- /**
- * 同步调用服务
- *
- * @param serviceId 服务 ID
- * @param timeout 超时时间
- * @return
- */
- public synchronized <T> T fetchService(final int serviceId, int timeout) {
- final CountDownLatch latch = new CountDownLatch(1);
- final AtomicReference<T> resultRef = new AtomicReference<>();
- service.execute(new Runnable() {
- @Override
- public void run() {
- fetchService(serviceId, new Event() {
- @Override
- public void call(Message msg) {
- try {
- resultRef.set((T) msg.obj);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- latch.countDown();
- }
- }
- });
- }
- });
- try {
- latch.await(timeout, TimeUnit.SECONDS); // 最多等待 timeout 秒
- } catch (Exception e) { // 等待中断
- e.printStackTrace();
- }
- return resultRef.get();
- }
由于主线程加锁来实现的同步, 所以要根据不同组件的 ANR 触发上限传入 timeout
同时, 所有数据接收的处理必须放到子线程, 否则就是死锁:
- private class WorkThread extends Thread {
- Handler mHandler;
- @Override
- public void run() {
- Looper.prepare();
- mHandler = new ServiceHandler();
- mMessenger = new Messenger(mHandler);
- Looper.loop();
- }
- public void quit() {
- mHandler.getLooper().quit();
- }
- }
注意: 子线程 Handler 需要自己 Looper.prepare
第五步, 组件和服务的自动化注册
原理也是 SPI,1 声明一个组件:
- @AutoService(IModule.class)
- public class Module extends BaseModule {
- @Override
- public void afterConnected() {
- }
- @Override
- public int getModuleIdId() {
- return Constants.MODULE_B;
- }
- }
2SPI 自动注册
- // 自动注册组件服务
- ServiceLoader<IModule> modules = ServiceLoader.load(IModule.class);
- for (IModule module : modules) module.init();
其它
1 主 app 壳的处理:
- dependencies {
- if (isDebug.toBoolean()) {// 调试阶段, 只保证基本逻辑不报错
- implementation project(":lib")
- } else {// 打包阶段, 才真正的引入业务逻辑模块
- implementation project(":module_a")
- implementation project(":module_b")
- implementation project(":module_service")
- }
- }
2 组件只有当做单独 APP 运行时才有自己的 application
- sourceSets {
- main {
- if (isDebug.toBoolean()) {
- manifest.srcFile 'src/main/debug/AndroidManifest.xml'// 这里面才有 application
- } else {
- manifest.srcFile 'src/main/AndroidManifest.xml'
- }
- }
- }
3 全局组件开关 gradle.properties 设置 isDebug,gradle 自动切换
- if (isDebug.toBoolean()) {
- apply plugin: 'com.android.application'
- } else {
- apply plugin: 'com.android.library'
- }
来源: https://juejin.im/entry/5a9e853af265da238a2ff76f