Skip to content

Latest commit

 

History

History
148 lines (95 loc) · 5.53 KB

组件代码隔离.md

File metadata and controls

148 lines (95 loc) · 5.53 KB

其实,如果我们的组件工程已经拆分为了本体与 SDK 两个工程,那么代码隔离就不是必要的了!

它就只是在壳工程依赖组件的时候会起作用。

实现

组件的代码隔离也有几种方式,下面开始介绍。

runtimeOnly

这个方法,是最简单的方案。但是会有点不足,就是不支持 databinding。

动态判断

要做到代码隔离,核心点就是在写代码的时候看不到依赖的组件,但是在编译运行的时候又能看得到依赖的组件。

那么就有了一种方式:动态的判断工程的 task,如果是 assemble的主工程等,就将依赖添加上去,其他时候不添加依赖。

那么接下来需要攻克的问题就是,如何动态的添加依赖?

方法有很多,我们一个一个介绍。

JIMU的独立运行方案

将依赖的组件配置在 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))
}

BRouter 的方案

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 依赖这个空白工程。

代码隔离后的资源问题

资源问题:

  1. 资源是应该每个组件维护自己的,还是应该将所有组件的资源维护到一个资源工程?
  2. 如果每个组件维护自己的资源的话,那么就需要避免资源覆盖,还要考虑组件的通用资源如何处理。

现在的组件化相关文章都说的是资源冲突,我个人感觉叫资源覆盖更加合理,因为,libA 与 libB 都有一个叫 arrow.png 的图片,那么打成 apk,肯定就只剩一个了,如果这两个图片相同还好,不同的话,UI上就乱掉了,所以组件之间才会添加前缀来避免这个问题。

一般来说,上层的会覆盖下层的资源,但是实际我测试的却不是这样,比如 app 与 lib 都有一个 app_name 的字符串,那么最终展示出来的是哪个字符串,似乎与 app_name 里面的字符串内容有关系,不一定是 app 里面的覆盖 lib 里面的。

我个人比较偏向于将资源统一放到一个工程里面,因为要区分组件的资源是否是公共资源还是挺麻烦的,毕竟,apk的体积优化也很重要。