天天看点

Android组件化Gradle插件Calces源码解析

随着很多公司的业务越来越多样化和复杂化,组件化开发也越来越流行,为我们调试代码和多人协助开发带来了巨大的好处,我们肯定会遇到下面几个痛点:

  • 组件是否单独运行
if (isDebug) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
           

通过gradle.properties配置isDebug变量来处理组件作为app还是lib的存在。

  • 组件与组件之间的Manifest合并问题
sourceSets {
        main {
            if (rootProject.ext.isBuildApp) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                //移除debug资源
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
                java {
                    exclude 'debug/**'
                }
            }
        }
    }
           

通过config.gradle中的变量判断该moudle到底用哪个AndroidManifest.xml文件,这两个的区别在于进入的时候是否需要配置activity启动intent-filter。

其实上面的这些操作我们都可以通过Gradle插件来帮我们管理组件,已经有人帮我们实现了这个想法Gradle自动实现Android组件化模块构建,为了深入理解构建方式本文分析下该开源项目calces-gradle-plugin,阅读本文前可以先阅读关于Groovy的语法的文章Gradle自定义Plugin(上)。

我们先看下项目结构目录:

Android组件化Gradle插件Calces源码解析

1处是定义的配置的moudle信息模型层,2是处理组件与组件之间的Manifest合并问题的,3是针对app壳工程编写的Plugin插件 ,4是针对非壳工程moudle编写的Plugin插件 AppConfigPlugin和ModulesConfigPlugin编写完后需要在下面的目录对应配置下。我们先看AppConfigExt类,这个一个gradle配置信息入口的类

class AppConfigExt {

    boolean isDebug = false //组件单独开启开关
    //容纳object的容器,它的特点是它的内部使用SortedSet实现的必须有一个public的构造函数,
    // 接受string作为一个参数,必须有一个叫做name 的property
    NamedDomainObjectContainer<AppExt> apps //宿主载体
    NamedDomainObjectContainer<LibraryExt> modules  //组件

    AppConfigExt(Project project){
        apps = project.container(AppExt) //创建object的容器对象
        modules = project.container(LibraryExt)
    }

    def isDebug(boolean isDebug){
        this.isDebug = isDebug
    }

    def apps(Closure closure){
        apps.configure(closure)
    }


    def modules(Closure closure){
        modules.configure(closure)
    }

    @Override
    String toString() {
        return "isDebug: $debugEnable\n" +
                "apps: ${apps.isEmpty()? "is empty" : "$apps"}"+
                "modules: ${modules.isEmpty()? "is empty" : "$modules"}"
    }
}
           

AppExt 这个类是宿主App

class AppExt extends ModulesExt{
    String dependMethod = "implementation" //默认依赖的方式
    List<String> modules = new ArrayList<>() //依赖组件的名称集合

    AppExt(String name) {
        super(name)
    }
......
           

各个组件的属性配置

class LibraryExt extends ModulesExt {
    boolean isRunAlone = false //是否独立运行
    String runAloneSuper //该moudle所依赖的子模块

    LibraryExt(String name) {
        super(name)
    }
 ........
           

宿主App和各个组件的基类

class ModulesExt {
    String name  //组件名称
    String applicationId  //组件applicationId
    String mainActivity   //该组件启动的activity

    ModulesExt(String name){
        this.name = name //如果有父节点必须写构造方法
    }
}
           

先用AppConfigPlugin处理整个项目项目build.gradle的配置信息

public class AppConfigPlugin  implements Plugin<Project> {
    private static final String EXTENSION_NAME = "appConfig"

    @Override
    void apply(Project project) {
        AppConfigExt appConfigExt = new AppConfigExt(project)
        // project.extensions,本扩展类其实就是只用于set、get的JavaBean类
        project.extensions.add(EXTENSION_NAME, appConfigExt)
        configApp(project)
    }

    void configApp(Project project) {
        List<String> moduleList = new ArrayList<>()
        NamedDomainObjectContainer<AppExt> appList
        AppConfigExt appConfigExt
        //当前project配置状态进行回调afterEvaluate(project开始配置前调用)
        //和beforeEvaluate,afterEvaluate(project配置完成后回调)
        project.afterEvaluate {
            //得到配置的信息bean
            appConfigExt = project.extensions.getByName(EXTENSION_NAME) as AppConfigExt
            appList = appConfigExt.apps
            checkRepeat(appConfigExt)
            checkModules(appConfigExt,moduleList)

        }
        initChildModules(moduleList, project)
        println("project child modules: $moduleList")
    }

    //运行后获取项目存在的所有组件
    void initChildModules(List<String> moduleList ,Project project){

        if (project.childProjects.isEmpty()){
            moduleList.add(project.toString()
                    .replace("project ","")
                    .replace('\'',''))
            return
        }
        //运行时遍历获取所有存在的组件(App壳工程和寄生组件)
        project.childProjects.entrySet().forEach{
            initChildModules(moduleList, it.value)
        }

    }

    //检查配置的模块是否重复
    static void checkRepeat(AppConfigExt appConfigExt){
        //取出App壳工程组件名称列表  以name字段为分组条件 
        //很重要的一点是NamedDomainObjectContainer容器节点的名称是唯一的,是按照节点的名称排序的,
        //如果有相同的节点名称 后面会覆盖前面的 (这个需要自行实验才能理解)
        //并且必须有一个public的构造函数,接受string作为一个参数,必须有一个叫做name 的property
        //这个property默认就是节点的名称 需要和项目路径对应,如果不填写默认为该配置的名字 
        //就是moudle节点的名称(如配置名为app的话,name则为:app)
        //倒入规则和setting.gradle中的include规则保持一致
        Map<String,List<AppExt>> appGroupMap =
                appConfigExt.apps.groupBy{ it.name.startsWith(':') ? it.name : new String(":" + it.name)}

        //k指的是name v指List<AppExt>   如果多个moudle节点的名称不同 但是里面的内容一样
        //当v.size() > 1的时候说明apps组件名称重复
        appGroupMap.forEach{
            k,v ->
                if (v.size() > 1){
                    throw new IllegalArgumentException("app is repeat. app name: [$k]")
                }
        }

        //取出寄生组件名称列表
        Map<String,List<LibraryExt>> moduleGroupMap =
                appConfigExt.modules.groupBy{ it.name.startsWith(':') ? it.name : new String(":" + it.name)}

        //k指的是name v指List<LibraryExt>  如果多个moudle节点的名称不同 但是里面的内容一样
        //这个时候就表示v.size() > 1说明apps宿主名称名称重复
        moduleGroupMap.forEach{
            k,v ->
                if (v.size() > 1){
                    throw new IllegalArgumentException("modules is repeat. modules name: [$k]")
                }
        }

    }

    //检查配置的模块名称是否合理正确
    static void checkModules(AppConfigExt appConfigExt,
                             List<String> projectModules){
        Set<String> configSet = new HashSet<>() //配置的所有组件集合
        Set<String> modulesSet = new HashSet<>() //真实获取的组件集合
        if (projectModules != null){
            modulesSet.addAll(projectModules)
        }
        List<String> notFoundList = new ArrayList<>()

        List<String> appNameList = appConfigExt.apps
                .stream()
                .map{it.name.startsWith(':') ? it.name : new String(":" + it.name)}.collect()

        List<String> moduleNameList =
                appConfigExt.modules.
                        stream().
                        map{
                            String name = it.name.startsWith(':') ? it.name : new String(":" + it.name)
                            //App壳工程的名字不能出现在其他组件当中
                            if (appNameList.contains(name)){
                                throw new IllegalArgumentException("$it.name already configured " +
                                        "as an application, please check appConfig")
                            }
                            name
                        }.
                        collect()

        println "moduleNameList = $moduleNameList"

        configSet.addAll(appNameList)
        configSet.addAll(moduleNameList)

        configSet.forEach{
            if(!modulesSet.contains(it)){
                notFoundList.add(it)
            }
        }

        //寄生组件配置的组件名称不存在
        if (notFoundList.size() > 0){
            throw  new IllegalArgumentException(
                    "not fount modules = " + notFoundList
            )
        }

        //App壳工程依赖的组件不存在
        appConfigExt.apps.stream().forEach{ app ->
            app.modules.stream().forEach{
                if (! configSet.contains(it)){
                    throw  new IllegalArgumentException(
                            "appConfig error , can not find $app.name modules $it by project" )
                }
            }
        }

        println("modules: " + configSet)
    }


}

           

然后处理每个moudle的build.gradle的配置信息的配置信息

public class ModulesConfigPlugin implements Plugin<Project> {

    private static final String PARENT_EXTENSION_NAME = "appConfig"

    @Override
    void apply(Project project) {
        AppConfigExt appConfigExt = getAppConfigExtension(project)
        configModules(project, appConfigExt)
    }

    static void configModules(Project project, AppConfigExt appConfigExt){
        if (appConfigExt == null){
            throw new NullPointerException("can not find appConfig")
        }
        List<AppExt> filterList = appConfigExt.apps.stream()
                .filter{ (it.name.startsWith(':') ? it.name : new String(":" + it.name)).endsWith(project.name) }
                .skip(0).collect()  //Java8 Stream 语法

        if (filterList != null && filterList.size() > 0){ //说明当前是已App壳工程编译
            AppExt appExt = filterList.get(0)
            AppPlugin appPlugin = project.plugins.apply(AppPlugin)
             //App壳工程的build.gradle文件设置ApplicationId
            appPlugin.extension.defaultConfig.setApplicationId(appExt.applicationId)
            //检查配置app壳工程的Manifest文件信息
            new AppManifestStrategy(project).resetManifest(appExt)  
             //检查配置app壳工程的依赖问题
            dependModules(project, appExt, appConfigExt)
        }else {
             //检查配置子moudle独立运行
            modulesRunAlone(project,appConfigExt.modules, appConfigExt.isDebug)
        }

    }

    //app壳工程依赖
    static void dependModules(Project project, AppExt appExt, AppConfigExt appConfigExt){
        //遍历appConfigExt.modules数据和appExt.modules数据对比得到交集然后以map形式返回
        Map<String,LibraryExt> moduleExtMap = appConfigExt.modules.stream().filter{
            modules ->
                String modulesName = appExt.modules.stream().find{ it.contains(modules.name) }
                modulesName != null && !modulesName.isEmpty()
        }.collect(Collectors.toMap({ it.name},{ it -> it}))


        if (appExt.modules != null && appExt.modules.size() > 0){
            List<String> modulesList = appExt.modules.stream()
                    .filter{ //遍历数据并检查其中的元素时使用 类似if
                //是否开启debug模式 debug为true的时候 moudle的isRunAlone必须要是true才能被依赖
                appConfigExt.isDebug ? (moduleExtMap != null && !moduleExtMap[it].isRunAlone) : true }
            .map{ //map生成的是个一对一映射,for的作用
                //添加依赖 implementation XXXX模块
                project.dependencies.add(appExt.dependMethod, project.project(it))
                it
            }.collect()
            println("build app: [$appExt.name] , depend modules: $modulesList")
        }
    }

    //获取父节点的配置信息
    AppConfigExt getAppConfigExtension(Project project){
        try{
            //项目根节点下的配置信息
            return project.parent.extensions.getByName(PARENT_EXTENSION_NAME) as AppConfigExt 
        }catch (UnknownDomainObjectException ignored){
            if (project.parent != null){
                getAppConfigExtension(project.parent)
            }else {
                throw new UnknownDomainObjectException(ignored as String)
            }
        }
    }

    //子moudle独立运行
    private static void modulesRunAlone(Project project, NamedDomainObjectContainer<LibraryExt> modules, boolean isDebug){
        List<LibraryExt> filterList = modules.stream().filter{ it.name.endsWith(project.name) }.skip(0).collect()
        if (filterList != null && filterList.size() > 0){//当前子moudle编译
            LibraryExt moduleExt = filterList.get(0)

            if (isDebug && moduleExt.isRunAlone){//开启debug模式并且isRunAlone为true
                AppPlugin appPlugin = project.plugins.apply(AppPlugin)
                appPlugin.extension.defaultConfig.setApplicationId(moduleExt.applicationId) //设置App工程ApplicationId
                if (moduleExt.runAloneSuper != null && !moduleExt.runAloneSuper.isEmpty()){ //
                    project.dependencies.add("implementation", project.project(moduleExt.runAloneSuper))
                    println("build run alone modules: [$moduleExt.name], runSuper = $moduleExt.runAloneSuper")
                }else{
                    println("build run alone modules: [$moduleExt.name]")
                }
                if (moduleExt.mainActivity != null && !moduleExt.mainActivity.isEmpty()){
                    //检查配置app壳工程的Manifest文件信息
                    new AppManifestStrategy(project).resetManifest(moduleExt)
                }
            }else{
                project.plugins.apply(LibraryPlugin)//设置App依赖工程
                //检查配置依赖工程的Manifest文件信息
                new LibraryManifestStrategy(project).resetManifest(moduleExt)
            }
        }

    }

}
           

处理每个moudle的时候需要处理每个moudle的AndroidManifest文件信息

abstract class ManifestStrategy {

    protected String path
    protected GPathResult manifest
    boolean edit = false //AndroidManifest配置文件是否修改过

    ManifestStrategy(Project project) {
        path = "${project.getBuildFile().getParent()}/src/main/AndroidManifest.xml"
        File manifestFile = new File(path) //找到moudle下的AndroidManifest.xml文件
        if (!manifestFile.getParentFile().exists() && !manifestFile.getParentFile().mkdirs()) {
            println "Unable to find AndroidManifest and create fail, please manually create"
        }
        manifest = new XmlSlurper().parse(manifestFile)//xml解析
    }

    //子类需要继承 设置启动Activity的IntentFilter属性
    abstract void setMainIntentFilter(def activity, boolean isFindMain)

    void resetManifest(ModulesExt moduleExt) {
        if ([email protected] != moduleExt.applicationId && moduleExt.applicationId != null &&
         !moduleExt.applicationId.isEmpty()) {
            //重新设置manifest的包名(如果设置了 没用默认的applicationId)
            [email protected] = moduleExt.applicationId 
            edit = true
        }

        boolean isFindMain = false //是否找到了启动activity
        if (moduleExt.mainActivity != null && !moduleExt.mainActivity.isEmpty()) {
            manifest.application.activity.each { activity ->
                if ([email protected]'android:name' == moduleExt.mainActivity) {
                    def filter = activity.'intent-filter'.find {
                        [email protected]'android:name' == "android.intent.action.MAIN"
                    }
                    isFindMain = true
                    //如果没有设置IntentFilter条件 代码帮助其设置
                    setMainIntentFilter(activity, filter != null && filter.size() > 0)
                }
            }
        }

        manifest.application.activity.each { activity ->
            def filter = activity.'intent-filter'.find {
                [email protected]'android:name' == "android.intent.action.MAIN"
            }
            if (filter != null
                    && moduleExt.mainActivity != null
                    && !moduleExt.mainActivity.isEmpty()
                    && [email protected]'android:name' != moduleExt.mainActivity) {
                //如果设置了IntentFilter条件 但是启动activity和配置的不一样则清空IntentFilter条件
                filter.replaceNode {} 
                edit = true
            }
        }

        //如果在AndroidManifest配置文件当中没有找到启动activity那么就手动设置
        if (!isFindMain) {
            addMainActivity(manifest.application, moduleExt)
        }

        if (edit) {
            buildModulesManifest(manifest)
        }
    }

    //代码添加启动activity
    void addMainActivity(def application, ModulesExt modulesExt) {
        if (modulesExt.mainActivity != null && !modulesExt.mainActivity.isEmpty()) {
            application.appendNode {
                activity('android:name': modulesExt.mainActivity) {
                    'intent-filter' {
                        action('android:name': "android.intent.action.MAIN")
                        category('android:name': "android.intent.category.LAUNCHER")
                    }
                }
            }
            edit = true
        }

    }


    //动态修改AndroidManifest节点下的配置问题
    void buildModulesManifest(def manifest) {

        def fileText = new File(path)
        StreamingMarkupBuilder outputBuilder = new StreamingMarkupBuilder() //创建XML
        def root = outputBuilder.bind {
            mkp.xmlDeclaration()
            mkp.declareNamespace('android': 'http://schemas.android.com/apk/res/android')
            mkp.yield manifest
        }
        String result = XmlUtil.serialize(root)
        fileText.text = result

    }
}

    //App壳工程的AndroidManifest配置文件  setMainIntentFilter中添加intent-filter
    class AppManifestStrategy extends ManifestStrategy {

        AppManifestStrategy(Project project) {
            super(project)
        }

        @Override
        void setMainIntentFilter(def activity, boolean isFindMain) {
            if (!isFindMain) {
                activity.appendNode {
                    'intent-filter' {
                        action('android:name': "android.intent.action.MAIN")
                        category('android:name': "android.intent.category.LAUNCHER")
                    }
                }
                edit = true
            }
        }

    }


    //依赖moudle工程的AndroidManifest配置文件  setMainIntentFilter中清除intent-filter
    class LibraryManifestStrategy extends ManifestStrategy {

        LibraryManifestStrategy(Project project) {
            super(project)
        }

        @Override
        void setMainIntentFilter(def activity, boolean isFindMain) {
            if (isFindMain) {
                println "build lib"
                activity.'intent-filter'.each {
                    if ([email protected]'android:name' == "android.intent.action.MAIN") {
                        it.replaceNode {}
                        edit = true
                    }
                }
            }
        }
    }

           

以上的代码注释信息便是这个项目的所有代码模块解析了,只有去阅读原作者相关文章和demo跑跑才能理解透彻。至于组件化全局的Application处理和组件通信这些方面的问题本文就不做深入探讨了。具体用法请查看相关说明calces-gradle-plugin。

继续阅读