为什么要组件化?
医动力 App 作为公司的核心产品已经有多年历史了, 随着版本的不断迭代, 功能越来越多, 代码量越来越大, 不可避免的会产生一下问题:
业务越来越复杂, 维护成本高;
业务耦合度高, 代码越来越臃肿, 团队内部多人协作开发困难;
编译时间长, 每修改一处代码后都要重新编译打包测试, 导致非常耗时;
开发测试困难, 每次修改都必须打包整个项目运行.
因此, 为了提高项目的可维护性和开发效率, 组件化成为了必然.
组件化的目标
首先看看老版本的医动力的项目工程结构
项目分为 Doctor 和 Patient, 它们之间公共的代码放在 common 中, common 中会依赖一些第三方的库. 随着项目的迭代, Doctor 和 Patient 里面的代码会越来越耦合, 因为单个 module 中仅仅是以包名作为功能的划分, 而包之间是可以随意调用的.
AndroidStudio/IDEA 是多模块管理的, 组件化的思路就是把不同的业务模块拆分到各个子 Module 中, 同时这些业务 Module 之间不会存在直接的调用, 这样当我们移除项目中的某个业务 Module 的时候不会影响整体的项目运行, 如下图所示:
可以看到有「ModuleIM」,「ModuleFollow」,「ModuleDiary」三个业务模块, 它们都会引用 Common 模块而获取一些基础库的支持, 同时这些业务模块是可以单独运行的. 后续会将更多的模块独立出来, 完成彻底的组件化.
如何组件化?
Android 的组件化的技术点主要在两个方面:
配置组件独立运行的能力;
组件之间的通信
第一点是通过在 module 的 gradle.build 中切换
- apply plugin: 'com.android.application'
- apply plugin: 'com.android.library',
复制代码
并设置对应模式下加载的 AndroidManifest.xml 和 src 的路径.
第二点由于组件之间是没有直接依赖的关系的, 要想让它们通信就必须把它们注册在一个公共的地方, 这里可以通过路由, 也可以通过注册接口.
目前组件化的方案在网上有很多, 由于组件化并不涉及 Android 系统级别的操作, 因此是比较成熟稳定的. 我们知道, 组件化是需要花费大量时间和精力的, 很难做到把项目彻底的拆分成组件, 那有没有一种方式可以让我们不改动原有项目 (改动很小) 的情况下, 将新功能进行组件化开发?
因此我们选择了使用 CC 来进行组件化的改造
Component Caller(CC)介绍
业界首个支持渐进式组件化改造的 Android 组件化开源框架 https://github.com/luckybilly/CC
引用自 CC 官方的两张图能让我们很快的明白什么叫做渐进式组件化?
在渐进式组件化的方案中, 可以先不用解耦, 只需要让单独运行的组件能够调用到主 App 中的功能即可. 思路是这样的:
新业务以组件形式开发
新组件需要调用的主 App 中的业务, 在对应的模块中创建一个组件类, 对外暴露对应的服务, 供其它组件调用, 并不需要现在就将这个模块解耦
新组件通过跨 App 的方式调用主 App 中的组件
主 App 也可以通过跨 App 的方式调用到单独运行的组件 App 中的组件
在同一个 module 中可以创建多个组件类, 将来解耦时将对应的组件类移动到解耦后的 module 中即可
关于 CC 的技术实现细节可以查看其 github 主页的 wiki 系列文章 https://github.com/luckybilly/CC/wiki/
组件化实践
CC 的集成
1. 在项目的根 gradle.build 中加入:
- buildscript {
- dependencies {
- ... ...
- classpath 'com.billy.android:autoregister:1.4.1'
- }
- }
复制代码
这个 autoregister 插件是用来在编译期间动态扫描并修改 class 文件, 实现组件的自动注册, 具体配置后面会提到
2. 在 Doctor 和 Patient 这两个主 App 的 gradle.build 中加入
- ext.mainApp = true // 设置为 true, 表示此 module 为主 app module, 一直以 application 方式编译
- apply from: '../cc-setting.gradle'
复制代码
cc-setting.gradle 中主要进行了以下几点操作:
读取 local.properties 文件中的配置来区分组件是否以独立 app 的方式编译;
添加 com.billy.android:cc:1.1.0 依赖;
自动注册组件的配置
3. 实现 IComponent 接口创建组件类
我们统一在 Doctor 和 Patient 中的 exports 包内创建 App 对外提供的组件, 不同业务的组件放在创建在对于的类中管理
IComponent 的实现也很简单, 提供一个模块名称 name 和对不同 action 的处理
主 App 的配置就这么多, 接下来要新建一个组件 Module
4. 业务 module 的配置
这里需要设置 module 独立运行时的 applicationId, 同时指定 resourcePrefix 资源前缀(防止不同模块之间资源文件的冲突)
因为设置了 module 的独立运行, 就需要准备一份 module 在独立运行模式下的 AndroidManifest 文件, 路径在 src/main/debug 下
5. 业务 module 提供 IComponent 接口
和主 App 一样, 模块要提供服务给其他模块调用, 需要提供实现 IComponent 的子类, 因为业务 module 提供的服务会比较少且单一, 我们将它放在包名下的 ExportComponent 下
7. 主 App 设置需要依赖的 module
- dependencies {
- api fileTree(include: ['*.jar'], dir: 'libs')
- addComponent 'module_follow'
- addComponent 'module_diary'
- addComponent 'module_im'
- }
复制代码
通过 addComponent 方式添加依赖, 其内部会根据业务 module 的运行模式决定是否依赖
6. 设置业务 module 的运行模式
业务 module 的运行模式包括开发模式和集成模式
开发模式: 会以 App 的方式运行
集成模式: 打包在主 App 中
打开 local.properties 文件
- module_follow=true
- module_diary=true
- module_im=false
复制代码
设置模块名称等于 true 或者 false(没有设置则为 false)
当等于 true 则该模块会以 App 运行, 这时候打包主 App 的时候是不会把该模块打包进去
当等于 false 时则不能独立运行, 打包主 App 的时候会一起打包进去
运行调试
现在你可以将组件单独运行起来了, 但是由于业务组件是不包含登录功能的, 因此它是没有用户登录状态的, 所以我们需要通过 CC 的组件调用去主 App 中获取
补充一点, 如果想要跨 App 调用首先需要打开 CC 的设置
CC.enableRemoteCC(true);
复制代码
网络请求组件化
项目中用到的网络请求框架是 okhttp+retrofit, 我们希望不同 module 中使用不同的 retrofit 的 Service 实例, 比如在 Module_follow 中我们会创建 FollowService.java 来处理当前模块的网络请求
在集成开发模式下我们可以通过组件调用去获取主 App 中的 Retrofit 对象, 我们只需要在主 App 中定一个 name=http,action=getRetrofit 的 IComponent;
但在开发模式下, 跨进程调用组件是传输不了 Retrofit 对象的, 因为 Android 的跨进程只能传输 Parcelable 对象, 这里我们可以在本 module 中提供一个相同名称的 IComponent, 在里面去获取主 App 的 baseUrl 和 token, 并创建新的 Retrofit 对象, 这样就可以透明的处理获取 Retrofit 对象了.
为什么以上方式可行呢? 因为 CC 在进行组件化调用的时候, 会检查当前模块是否存在要调用的模块, 如果存在则会调用本地的, 不存在才会去跨进程调用. 最后我们可以把这些模拟操作抽到一个 lib_mock 的 module 里面复用.
组件化小结
组件化后带来的一些变化:
编译时间明显缩短
开发人员之间可以通过模块分工
可以在模块中尝试新的技术而不担心影响全局(Kotlin)
CC 已知局限:
开发模式不能在 Android8.0 及以上环境运行, 开发的时候可以使用虚拟机或者低版本的手机, 集成模式不影响.
引用
CC: 基于总线的 android 组件化开发框架
Android 彻底组件化方案实践 https://www.jianshu.com/p/1b1d77f58e84
来源: https://juejin.im/post/5b9df66ee51d450e9874cdf3