随着移动端快产品速迭代,多人合作已成为一种标杆,如果是传统的项目都放在一个module中代码耦合严重,存在各种各样的诟病,所以组件化越来越盛行。下面我们一步步揭开组件化的神秘面纱。
首先我们看下整体结构
说明:app是壳工程,下面是业务模块分别有login,my等模块,业务模块都依赖libmvi基础库,基础库里面有业务组件都会用到的功能比如:基础类、glide、retrofit等,有些组价用到,有些用不到的工具模块,单独抽成工具模块比如libutil,libshare,可被组件按需依赖。
实施组件化
模块划分
按项目业务抽取module和工具模块,为了方便管理,抽取config.gradle做版本统一管理,util.gradle做工具模块依赖......
单独编译
在gradle.properties中声明isBuildModule,isBuildModule 为 true 时可以使每个组件独立运行, false 则可以将所有组件集成到宿主 App 中,编写业务module.gradle,每个业务组件必须依赖这个。
单独编译需要新建alone文件夹放入AndroidManifest.xml标识启动入口,关于源码控制和isBuildModule 切换详见module.gradle配置
if (isBuildModule.toBoolean()) {
apply plugin: 'com.android.application'
configSigning project
} else {
apply plugin: 'com.android.library'
}
sourceSets {
main {
if (isBuildModule.toBoolean()) {
//独立运行
manifest.srcFile 'src/main/alone/AndroidManifest.xml'
} else {
//合并到宿主
manifest.srcFile 'src/main/AndroidManifest.xml'
resources {
//正式版本时,排除alone文件夹下所有调试文件
exclude 'src/main/alone/*'
}
}
}
}
Application业务分发
以前的项目第三方的初始化全在 项目的application中做的,现在组件化需要单一职责,不同的三方库的初始化放到不同的业务模块,具体步骤如下:
1.新建module模块:providerservice (作为组件通信和application业务分发),业务模块需依赖它进行通信
2.新建interface IApp ,代理生命App周期
interface IApp {
fun attachBaseContext(base: Context)
fun onCreate(base: Context)
fun onTerminate(base: Context)
}
3.不同的业务模块实现IApp,并在对应AndroidManifest.xml注册
class LoginApp: IApp {
override fun attachBaseContext(base: Context) {
}
override fun onCreate(base: Context) {
//初始化一键登录,第三方库啥的
}
override fun onTerminate(base: Context) {
//释放一些资源
}
}
<meta-data
android:name="com.ikang.loginmodule.LoginApp"
android:value="ConfigAppLife" />
4.编写CommonManifestParser解析所有的ConfigAppLife到集合中,再编写代理类AppDelegate,代理所有模块的生命周期函数调用,最后在BaseApp相应调用。
// CommonManifestParser
val appInfo =context.packageManager.getApplicationInfo( context.packageName, PackageManager.GET_META_DATA)
if (appInfo.metaData != null) {
for (key in appInfo.metaData.keySet()) {
if (MODULE_VALUE == appInfo.metaData[key]) {
modules.add(parseModule(key))
}
}
}
// BaseApp
override fun onTerminate() {
super.onTerminate()
mAppDelegate.onTerminate(this)
}
业务组件通信
①采用原始方式
1、providerservice模块中在声明接口IService,然后在声明登陆模块暴露的功能有哪些
interface ILoginService : IService {
val accountId: String
}
2、在登陆模块loginmodule实现ILoginService
class LoginServiceImpl : ILoginService {
override val accountId: String = "张三"
}
3、在loginApp传入实现
class LoginApp: IApp {
override fun onCreate(base: Context) {
//传统方式通信
LoginServiceControl.accountService = LoginServiceImpl()
}
......
4.现在即可在mymodule模块MeFragment中获取登陆模块的用户Id
LoginServiceControl.accountService.accountId
②采用arouter通信跟上面的类似,只不过arouter是编译期apt自动生成代码,放入到路由表map中的,然后扫描所有的dex中的指定报名的IProvider实现的
1、providerservice模块中在声明接口ILoginService ,然后在声明登陆模块暴露的功能有哪些
interface ILoginService :IProvider {
val accountId: String
}
- 2.在登陆模块loginmodule实现ILoginService
@Route(path = ProviderPath.Provider.LOGIN)
class LoginServiceRouter : ILoginService {
override val accountId: String = "张三"
.......
}
3.现在即可在mymodule模块MeFragment中获取登陆模块的用户Id
@Autowired
@JvmField var loginService: ILoginService? = null
③基于事件总线
使用 LoacaBroadcastManage或者RxBus进行通信
其他约定
-
实体类放在本身的 module 中是无法传递的,可以下沉,放到业务模块下边的providerservice中,或者在抽取一个datamodule类
-
权限管理我们将 normal 级别的权限申请都放到 Base module 中,然后在各个 module 中分别申请 dangerous 的权限 。 这样分配的好处在于当添加或移除单一模块时,隐私权限申请也 会跟随移除 ,能做到最大程度的权限解祸 。不要将权限全部转交到每个 module 中,包括普通权限的声明 ,因为这样会增加 AndroidManifest的合并检测的耗时’。
-
组件化资源冲突
//1、AndroidMainfest 冲突问题
AndroidMainfest 中引用了 Application 的 app:nam巳 属性 ,当出现冲 突 时, 需要使 用 tools:replace=”android:name”来声明 Application 是可被替换 的 。
//2、依赖jar包冲突
当包冲突出现时,可以先检查依赖报告,使用命令 gradledependencies查看依赖目录树
//3、防止资源重复,resourcePrefix 这个值只能 限定 XML 中的资源,井不能限定图片资源,所有图片资源仍然需要手动去修改资源名 。
android {
resourcePrefix "纽件名”
}
//组件化混淆,每个module独有的引用库混淆放到各自 的 proguard-rule.pro 中 。
defaultConfig {
......
consumerProguardFiles 'proguard-rules.pro'
}
代码部署
多模块可以采用 git submodule\git subtree,或者上传到 公司私服maven上统一管理。