Android使用Gradle插件统一组件化架构各组件的版本(下)

648 阅读3分钟

上篇博客Android使用Gradle插件统一组件化架构各组件的版本(上)遗留了两个问题,今天抽空又去研究了下, 还没看过上篇博客的先去了解下再来看这篇。

  1. 单Module想要以App形式运行
  2. Module源码和Aar之间的切换,或者Module源码和maven之间的切换

问题1

随着项目越做越大,可能后期拆分的业务Module越来越多,随之编译速度会变慢,但是其实我们增加需求还是改需求一次不可能涉及到很多个Module代码的同时修改,

那么我们是不是可以改哪个业务层就只编译那个业务层的Module呢,其他业务层不参与编译。

目前这个问题目前已经完美解决了,只需要一个boolean变量即可切换LibraryApplication

源码,这次代码在分支test,建议下载源码体验一下

本文主要讲相比上篇改动较大的地方

LibsBuildPlugin

package com.tools.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.PluginContainer

/**
 * @author: cb
 * @date: 2023/10/12
 * @desc: 统一多Module中Gradle的配置
 */
class LibsBuildPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        try {
            with(target) {

                var isModuleApp = false
                try {
                    //需要注意这里key用了module的名称+(_app),例如:proj_search_app
                    // 在gradle.properties定义
                    val key = "${project.name}_app"
                    isModuleApp = providers.gradleProperty(key).get() == "true"
                } catch (e: Exception) {
                }

                println("isApp=$isModuleApp")

                //判断是否需要app单独运行
                if (isModuleApp) {
                    plugins.apply {
                        apply("com.android.application")
                        addCommonPluginContainer(this)
                    }
                } else {
                    plugins.apply {
                        apply("com.android.library")
                        addCommonPluginContainer(this)
                    }
                }

                //根据不同配置,走App还是Library
                if (isModuleApp) {
                    configModuleApp(extensions, "${project.name}.application.id")//后面会讲
                } else {
                    configLibrary(extensions)//后面会讲
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }


    /**
     * 添加公共的插件
     * @param pluginContainer PluginContainer
     */
    private fun addCommonPluginContainer(pluginContainer: PluginContainer) {
        pluginContainer.apply("kotlin-android")
        pluginContainer.apply("kotlin-parcelize")
    }
}

主要注意isModuleApp这个boolean值走的不同的if else

providers.gradleProperty(key).get() == "true"

这段代码主要取项目根目录中的gradle.properties里面定义的boolean值,假如我们的proj_home Module,那就需要再gradle.properties中定义proj_home_app=true,详细代码gradle.properties,只需要一个变量就可以切换LibraryApplication

PlugExt.kt

package com.tools.plugin

import ProjectVersion
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.DefaultConfig
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.JavaVersion
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions

/**
 * module以单独app进行调试,可以理解成单元测试
 * @param ex ExtensionContainer
 */
fun configModuleApp(ex: ExtensionContainer, appId: String?) {
    configApp(ex, appId ?: "com.default.app", true)
}

/**
 * 真实的app,也就是最终合并了所有module的app
 */
fun configRealApp(ex: ExtensionContainer) {
    configApp(ex, null, false)
}

/**
 * App配置
 * @param ex ExtensionContainer
 * @param appId String? 单独module需要指定包名
 * @param isModuleApp Boolean 是否是module形式的app
 */
private fun configApp(ex: ExtensionContainer, appId: String?, isModuleApp: Boolean) {
    ex.configure<ApplicationExtension> {
        compileSdk = ProjectVersion.compileSdk
        defaultConfig {
            //如果业务module以app形式单独运行,需要设定一个包名
            if (appId != null) {
                applicationId = appId
            }
            minSdk = ProjectVersion.minSdk
            targetSdk = ProjectVersion.targetSdk
            versionCode = ProjectVersion.versionCode
            versionName = ProjectVersion.versionName
            vectorDrawables.useSupportLibrary = true
            commonDefaultConfig(this)
        }
        sourceSet(this, isModuleApp)
        //Application和library相同的配置
        unifiedConfiguration(this)
    }
}

/**
 * 设置资源目录
 * @param extension ApplicationExtension
 * @param isModuleApp Boolean
 */
private fun sourceSet(extension: ApplicationExtension, isModuleApp: Boolean) {
    extension.sourceSets {
        getByName("main") {
            if (isModuleApp) {
                //根据实际情况设置单独module app的AndroidManifest路径
                manifest.srcFile("src/app/AndroidManifest.xml")
                java {
                    srcDirs("src/app")//重点
                    srcDirs("src/main/java")
                }
                res {
                    srcDirs("src/main/res")//重点
                    srcDirs("src/app/res")
                }
            } else {
                manifest.srcFile("src/main/AndroidManifest.xml")
            }
        }
    }
}

//省略代码。。。。。。。。

主要是configApp()sourceSet()两个方法

configApp()

如果Module需要单独运行肯定需要需要设置ApplicationId也就是包名,为了不和源App冲突,这里可以随便填写,反正是开发自己测试用,demo中使用 configModuleApp(extensions, "${project.name}.application.id")也就是Module的名称来作为包名

sourceSet()

如果Module需要单独运行肯定需要需要设置logo、app_name、AndroidManifest等等,所以sourceSet()主要就是做这件事情的,不想每个Module都写一份,所以在插件里统一处理了;

需要注意的就是目录要对应上哦,比如

  java {
      srcDirs("src/app")//重点
      srcDirs("src/main/java")
  }

image.png

目录要对应上源码位置

如上图proj_searchgradle.properties中设置proj_home_app=true,可以看到Android Studio已经扫描并加载src/app目录里面的java代码和resAndroidManifest

如需打包正式Release包时记得将gradle.properties中值设置为false,不然打不了包会报错,src/app目录里面的class资源AndroidManifest打正式包的时候不会打包进APK的,这点放心。

验证编译速度

所有Module都参与打包的情况

app.png

大概23秒

单独运行proj_home

首页单独module.png

6秒,这差距很明显吧,两次测试都是clear后编译的哦,大大提升的开发效率,下载源码自己亲身体验下吧。

问题2

需求场景:比如App更新相关的Module可能长时间都不会修改,但是每次clear后都会参与编译,会增加我们的编译耗时,这个时候我们就可以把更新Module打包成Aar或者推到公司的maven库;

好处

  1. 提高编译速度
  2. 稳定性,App更新这种比较重要的功能,保证组内开发改其他需求的时候不会修改到相关的代码

其实跟问题1类似的,就是一个变量来控制源码和Aar之间的切换;

源码分支 需要切换到aar分支;

这里不展开细说了,下载源码,切换分支,看这个提交记录的修改

image.png

这里需要注意的就是,Aar需要提前打出来放到需要引用的Module中的libs目录下,然后再将gradle.properties中切换开关打开即可。

这里主要为了演示,只操作的Aar源码之间的切换,maven库没有所以这里不展示了