得到Android组件化出来已经两年多了,公司项目也集成了该方案,这里我就自己的理解分享一下大概的原理:
组件化的核心问题
组件化最基本的其实还是创建多个module,各自业务在各自的module中写,互不干扰,且互不依赖,代码隔离,完全解耦。
- 组件化的核心就是,每个module既可以独立运行,又可以被当成library进行依赖。那么这里就涉及到第一个核心问题:如何在独立运行和library之间进行转换。
- 如果一个应用独立运行必须有个入口Activity,然而在被依赖时这个入口又不需要,这里就涉及到第二个问题:需要有一些代码只有在单独运行的时候被加载,而被当做依赖时不能加载
- 在开发期间module之间完全隔离,无法相互引用,这里就涉及到第三个问题:通信。这里又包含两部分通信问题,一是UI跳转,二是逻辑上的通信,比如一个module需要调用另一个module中的东西等。
基于以上三点问题,就分析一下以下三点核心流程
核心流程
- 动态转换module的类型插件com.android.application/com.android.library
- 加载runalone中的代码和xml
- 注册通信接口Service(IApplicationLike)及路由跳转
关键参数
- isRunAlone:是否需要独立运行,如果为true,需要配置runalone文件夹
- debugComponent:debug模式下运行需要依赖的其他组件
- compileComponent:非debug模式下运行需要依赖的其他组件
- applicationName:组件Application的全路径,比如:com.allin.medrecor ddossier.runalone.application.MedApplication
- isRegisterCompoAuto:是否自动注册通信接口,如果为false,需要手动注册可以通过反射进行注册
一、动态转换module的类型插件
- 通过当前运行的taskName转化成组装任务
@Override
void apply(Project project) {
...
AssembleTask assembleTask = getTaskInfo(project.gradle.startParameter.taskNames)
...
}
/**
* 转化当前执行的module
* @param taskNames
* @return
*/
private AssembleTask getTaskInfo(List<String> taskNames) {
AssembleTask assembleTask = new AssembleTask()
for (String task : taskNames) {
if (task.toUpperCase().contains("ASSEMBLE")
|| task.contains("aR")
|| task.toUpperCase().contains("INSTALL")
|| task.toUpperCase().contains("TINKERPATCH")
|| task.toUpperCase().contains("RESGUARD")) {
if (task.toUpperCase().contains("DEBUG")) {
assembleTask.isDebug = true
}
assembleTask.isAssemble = true
String[] strs = task.split(":")
assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "all")
break
}
}
return assembleTask
}
- 获取控制是否可以独立运行的参数isRunAlone的值,并动态修改,默认每个module中的isRunAlone的值都为true,在打包模式下,当运行的是当前组件的时候,isRunAlone为true不变,其他module修改isRunAlone的值为false
@Override
void apply(Project project) {
...
//需要独立运行的组件需要设置isRunAlone参数
if (!project.hasProperty(IS_RUN_ALONE)) {
throw new RuntimeException("你要在【" + module + "】的gradle.properties文件中配置isRunAlone参数")
}
boolean isRunAlone = Boolean.parseBoolean(project.properties.get(IS_RUN_ALONE))
Log.i("isRunAlone = " + isRunAlone)
//获取主项目名称, 下面两种方法都可以
String mainModuleName = project.rootProject.property(MAIN_MODULE_NAME)
// String mainModuleName = project.rootProject.properties.get("mainmodulename")
//对于要编译的组件和主项目,isRunAlone修改为true,其他修改为false
if (isRunAlone && assembleTask.isAssemble) {
isRunAlone = module == compilerModuleName || module == mainModuleName
}
//将修改后的isRunAlone值重新赋值给当前组件
project.setProperty(IS_RUN_ALONE, isRunAlone)
...
}
- 需要独立运行,则加载独立运行的必要但是非独立运行不需要的代码,也就是runalone文件夹下的代码,并动态设置module的插件类型com.android.application/com.android.library,及加载依赖包
@Override
void apply(Project project) {
...
//根据配置添加各种组件依赖,并自动化生成组件加载代码
if (isRunAlone) {
//将当前运行的组件添加application插件
project.apply plugin: 'com.android.application'
//配置独立运行代码路径
if (module != mainModuleName) {
project.android.sourceSets {
main {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/runalone/java']
res.srcDirs = ['src/main/res', 'src/main/runalone/res']
}
}
}
Log.i("apply plugin is com.android.application")
if (assembleTask.isAssemble && module == compilerModuleName) {
//开始打包组件,添加其他组件为依赖包
compileComponent(assembleTask, project)
//开始进行字节码插入,自动注册通信接口服务
// project.android.registerTransform(new ComponentCodeTransform(project))
}
} else {
//如果不是当前运行的组件,自动添加library插件
project.apply plugin: 'com.android.library'
Log.i("apply plugin is com.android.library")
}
...
}
/**
* 自动添加依赖,只有在运行assemble(装配)任务的时候才会添加依赖,所以在开发期间,各组件完全隔离,这里是解耦的关键
* 支持两种语法:module或者groupId:artifactId:version(@aar),前者之间引用module工程,后者使用maven中已经发布的aar
* 本地依赖 ==> debugComponent/compileComponent:组件1,组件2,...
* 远程依赖 ==> com.luojilab.reader:readercomponent:1.0.0,androidx.appcompat:appcompat:1.1.0,...
* @param assembleTask
* @param project
*/
private void compileComponent(AssembleTask assembleTask, Project project) {
//需要依赖的组件
String components
if (assembleTask.isDebug) {
components = project.properties.get(DEBUG_COMPONENT)
} else {
components = project.properties.get(COMPILE_COMPONENT)
}
if (components == null || components.length() == 0) {
Log.i("当前组件没有依赖任何组件")
return
}
//进行双重校验
String[] compileComponents = components.split(",")
if (compileComponents == null || compileComponents.length == 0) {
Log.i("当前组件没有依赖任何组件")
return
}
//开始添加依赖
for(String str: compileComponents) {
if (str.contains(":")) {
project.dependencies.add("implementation", str)
Log.i("添加远程依赖:" + str)
} else {
project.dependencies.add("implementation", project.project(':' + str))
Log.i("添加本地依赖:" + str)
}
}
}
至此,就完成了module插件的动态化配置,及动态依赖其他组件的配置
二、通信
UI跳转
这里本人自己的项目用的是阿里路由,ARouter