其实,如果我们的组件工程已经拆分为了本体与 SDK 两个工程,那么代码隔离就不是必要的了!
它就只是在壳工程依赖组件的时候会起作用。
组件的代码隔离也有几种方式,下面开始介绍。
这个方法,是最简单的方案。但是会有点不足,就是不支持 databinding。
要做到代码隔离,核心点就是在写代码的时候看不到依赖的组件,但是在编译运行的时候又能看得到依赖的组件。
那么就有了一种方式:动态的判断工程的 task,如果是 assemble的主工程等,就将依赖添加上去,其他时候不添加依赖。
那么接下来需要攻克的问题就是,如何动态的添加依赖?
方法有很多,我们一个一个介绍。
将依赖的组件配置在 app 下面的 gradle.properties 中:
isRunAlone=true
debugComponent=sharecomponent,sharecomponentkotlin,readercomponent
compileComponent=sharecomponent,sharecomponentkotlin,readercomponent
然后,在工程的插件里面,判断是否是 assemble 任务,运行的是不是主工程,如果都满足的话,就动态的添加这些工程:
if (assembleTask.isAssemble && module.equals(compileModule)) {
...
project.dependencies.add("compile", project.project(':' + str))
}
JIMU 是采用的配置文件的方式来动态的添加依赖工程来实现代码隔离,我自己实现的方式是利用了 groovy 里面的一个语法特性:
在一个对象上调用一个不存在的方法的时候,它并不会直接报错,而是会看看你有没有在别的位置来实现这个这个方法
举个例子,我们添加依赖,一般这样写:
implementation project(':login')
但是,如果我们改成如下的方式呢:
implementation component(project(':login'))
毫无疑问的,编译肯定会报错,它说的是在 DefaultDependencyHandler 上没有找到 component 这个方法:
> Could not find method component() for arguments [project ':login'] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
那么,我们可以给 DefaultDependencyHandler 加上这个方法:
project.dependencies.metaClass.component { Object value ->
...
}
这样,DefaultDependencyHandler 就有了 component 这个方法。这相当于什么?相当于我们 hook 了依赖方法,那么我们可以自定义依赖的返回值。
所以,接下来,思路就很清晰了:
com.aprz.brouter.CodeIsolationPlugin
@Override
void apply(Project project) {
if (!project.rootProject.hasProperty(mainModuleName)) {
throw new RuntimeException("请在根工程的 gradle.properties 里面配置 mainModuleName 属性,比如(mainModuleName=app)")
}
String moduleName = project.path.replace(":", "")
String mainModuleName = project.rootProject.property(mainModuleName)
// 2. 是否是打包任务
boolean assembleTask = isAssembleTask(project.gradle.startParameter.taskNames)
project.dependencies.metaClass.component { Object value ->
// 打包主 module
// 添加依赖
if (moduleName == mainModuleName && assembleTask) {
return value
}
// 否则,随便 return 一个空的
return project.fileTree(["dir": "_brouter_not_exist", "exclude": "**"])
}
}
private static boolean isAssembleTask(List<String> taskNames) {
for (String task : taskNames) {
if (task.toUpperCase().contains("ASSEMBLE")
|| task.contains("aR")
|| task.contains("asR")
|| task.contains("asD")
|| task.toUpperCase().contains("TINKER")
|| task.toUpperCase().contains("INSTALL")
|| task.toUpperCase().contains("RESGUARD")) {
return true
}
}
return false
}
这样,一个代码隔离的插件就做完了,50行代码,美滋滋。我最中意这个插件,用极少的代码来实现一个满足需求的功能。
建立一个空白工程,它 implementation 依赖组件工程,壳工程 implementation 依赖这个空白工程。
资源问题:
- 资源是应该每个组件维护自己的,还是应该将所有组件的资源维护到一个资源工程?
- 如果每个组件维护自己的资源的话,那么就需要避免资源覆盖,还要考虑组件的通用资源如何处理。
现在的组件化相关文章都说的是资源冲突,我个人感觉叫资源覆盖更加合理,因为,libA 与 libB 都有一个叫 arrow.png 的图片,那么打成 apk,肯定就只剩一个了,如果这两个图片相同还好,不同的话,UI上就乱掉了,所以组件之间才会添加前缀来避免这个问题。
一般来说,上层的会覆盖下层的资源,但是实际我测试的却不是这样,比如 app 与 lib 都有一个 app_name 的字符串,那么最终展示出来的是哪个字符串,似乎与 app_name 里面的字符串内容有关系,不一定是 app 里面的覆盖 lib 里面的。
我个人比较偏向于将资源统一放到一个工程里面,因为要区分组件的资源是否是公共资源还是挺麻烦的,毕竟,apk的体积优化也很重要。