gradle版本_Android 重构 持续优化统一管理 Gradle

5a0c98be691ae98b95aa46a038a846f9.gif

101

次推文

LZ-Says

我介意着你的不介意。

ff11b59700d993295cb9219cda50517b.png

前言

借着韩哥哥要求重构的机会,正好好好回顾下以前遗忘/忽略的知识点。

记录下有关 Gradle 优化之路:

  • Android|模块化探索抽取 basic 简化子 module 冗余 

  • Android 重构 | 统一管理 Gradle 依赖版本 

大概的方向或者说最终目标精简后如下:

一次引用,全文(项目)使用,避免团队协作引入重复依赖;

自带依赖更新提示;

支持跳转等常规操作。

最重要的,依然是便于维护。

从最初的创建 config.gradle 到现在的 basic_depend.gradle,虽说今天更比昨天强,但是依然不是很满意。

ext 方式虽然是 Google 官方目前推荐,并且当前一些主流库也采用此种方式,实际使用起来,个人还是有部分不方便。比如说不支持跳转,不支持更新等等,人呐,总想得到更多。

在查阅了多个文档后,再次准备优化/升级一波,继续让韩总蒙圈。 

一、buildSrc 搞起来

将官方的描述用 Google 翻译了一遍,如下:

复杂的构建逻辑通常很适合作为自定义任务或二进制插件进行封装。自定义任务和插件实现不应存在于构建脚本中。buildSrc 只要不需要在多个独立项目之间共享代码,就可以非常方便地使用该代码。

该目录 buildSrc 被视为包含的构建。发现目录后,Gradle 会自动编译并测试此代码,并将其放入构建脚本的类路径中。对于多项目构建,只能有一个 buildSrc 目录,该目录必须位于根项目目录中。buildSrc 应该比脚本插件更可取,因为它更易于维护,重构和测试代码。

buildSrc 使用适用于 Java 和 Groovy 项目的相同源代码约定。它还提供对 Gradle API 的直接访问。

Google Develop

思索许久,个人简单总结下:

  • buildSrc 存在于 Gradle 编译期;

  • 同样 buildSrc 支持(单独项目)共享代码,例如一个项目中多个 module 都可以直接调用。

buildSrc 实践

描述下操作步骤:

  • 在项目根目录下创建 buildSrc 目录,随后新建 build.gradle.kts 文件;

  • 创建 src 目录,以及对应管理版本文件;

  • 替换直接使用原有依赖

build.gradle.kts 内容如下: 

// 导入 Kotlin 插件import org.gradle.kotlin.dsl.`kotlin-dsl`plugins {    `kotlin-dsl`}repositories {    jcenter()}/** * 禁用测试报告(Gradle 默认会自动创建测试报告) */tasks.withType {    reports.html.isEnabled = false    reports.junitXml.isEnabled = false}/** *  isFork:将编译器作为单独的进程运行。 *  该过程在构建期间将被重用,因此分叉开销很小。分叉的好处是,内存密集型编译是在不同的过程中进行的,从而导致主 Gradle 守护程序中的垃圾回收量大大减少。 *  守护程序中较少的垃圾收集意味着 Gradle 的基础架构可以运行得更快,尤其是在您还使用的情况下 --parallel。 * *  isIncremental:增量编译。Gradle 可以分析直至单个类级别的依赖关系,以便仅重新编译受更改影响的类。自 Gradle 4.10 起,增量编译是默认设置。 */tasks.withType {    options.isFork = true    options.isIncremental = true}/** * 禁用关于使用实验性 Kotlin 编译器功能的警告 */kotlinDslPluginOptions {    experimentalWarning.set(false)}

Dependencies.kt,这是我定义的版本管理的文件,部分内容如下:

@file:Suppress("SpellCheckingInspection")/** * @author HLQ_Struggle * @date 2020/7/27 * @desc 统一管理类 */// 统一管理项目中的版本信息object Versions {    // Build Config    const val compileSDK = 29       // 编译 SDK 版本    const val buildTools = "29.0.3" // Gradle 编译项目工具版本    const val minSDK = 23           // 最低兼容 Android 版本    const val targetSDK = 29        // 最高兼容 Android 版本    // App Version    const val appVersionCode = 1           // 当前版本编号    const val appVersionName = "1.0"       // 当前版本信息    // 。。。}// 统一管理项目中使用的依赖库object Deps {    // Gradle    const val androidGradle = "com.android.tools.build:gradle:${Versions.androidGradlePlugin}"    // Kotlin    const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"    const val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"    const val kotlinxCoroutines =        "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.kotlinxCoroutines}"    // ...}

举个两个栗子,如何使用:

  • 根目录下 build 如何使用:

直接通过在 Dependencies 文件中定义的分组名去获取对应的属性即可,如下所示:

buildscript {    // ...    dependencies {        classpath Deps.androidGradle        classpath Deps.kotlinGradlePlugin        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }}// ...
  • 其它 module 目录下 build 如何使用:

同理,当然也可以采用直接倒入整个对应分组方式,直接使用对应属性,例如:

// 这里采用直接倒入定义的 Deps 以及 Versions 分组方式import static Deps.*import static Versions.*apply plugin: 'com.android.library'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'android {    // 这里就可以直接使用对应的属性    compileSdkVersion compileSDK    buildToolsVersion buildTools    defaultConfig {        minSdkVersion minSDK        targetSdkVersion targetSDK        versionCode appVersionCode        versionName appVersionName        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"        consumerProguardFiles 'consumer-rules.pro'    }    // ...}dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    // 同理,这里也是一样,直接使用对应的属性名即可    implementation kotlinStdLib    implementation appcompat    implementation coreKtx    api 'com.google.android.material:material:1.2.0'    testImplementation junit    androidTestImplementation extJunit    androidTestImplementation espresso    api mmkv    api 'com.airbnb.android:lottie:3.4.1'}

这种方式比较有好的几个特点如下:

  • 支持跳转;

  • 支持智能提示;

  • Gradle 编译时介入,感脚很湿高大上

但是关键的更新提示呢?

ummm,不开森。

加个 gif 配图吧~

e8abdfb8a1fb9e1ea86d28e6e649df01.gif

手动编写 buildSrc 需要注意:

  • 目录结构:例如:buildSrc/src/main/kotlin(java)

  • 在 build.gradle.kts 中添加 jcenter(),否则 kotlin-dsl 加载失败

二、refreshVersions 使用(2020/09/15)

网上搜到关于 refreshVersions 的描述,觉得蛮合适,尝试一波。

大概的优势在于以下几点:

  • 集中管理依赖

  • 以最小成本提示依赖升级

操作步骤如下:

Step 1:修改 settings.gradle 文件

// settings.gradle.ktsimport de.fayard.refreshVersions.RefreshVersionsSetup// Here you might have some pluginManagement block:pluginManagement {    //...}buildscript {    repositories { gradlePluginPortal() }    dependencies.classpath("de.fayard.refreshVersions:refreshVersions:0.9.5")}rootProject.name = 'Your Android Project Name'include ':app'include ':helper'include ':weight'// include other moduleRefreshVersionsSetup.bootstrap(settings)

Step 2:同步后执行命令

./gradlew migrateToRefreshVersionsDependenciesConstants --console=plain

根据提示进行依赖替换:

fbb6aa525bfc1ba1c5c14e33456af577.png

随后生成 versions.properties 文件:

## suppress inspection "SpellCheckingInspection" for whole file## suppress inspection "UnusedProperty" for whole file#### Dependencies and Plugin versions with their available updates## Generated by $ ./gradlew refreshVersions## Please, don't put extra comments in that file yet, keeping them is not supported yet.version.androidx.appcompat=1.1.0##             # available=1.2.0-alpha01##             # available=1.2.0-alpha02##             # available=1.2.0-alpha03##             # available=1.2.0-beta01##             # available=1.2.0-rc01##             # available=1.2.0-rc02##             # available=1.2.0##             # available=1.3.0-alpha01##             # available=1.3.0-alpha02## 。。。

有一点觉得不舒服的地方是,它内置了 Android 一部分的依赖,而对于我们实际开发中使用其它依赖,则显示不太友好了,如下图:

992e9fbb0eabe37a22f54265035eaec4.png

研究好一段时间,各种蒙圈,实际的效果还是不是太满意,如果能在 buildSrc 的基础上新增版本更新就更好了。

三、buildSrc 结合 task(2020/09/17)

不得不说,掘金大佬很多,很友善,这不,沉璧浮光cbfg 大佬教我一招~

文末已附上链接,感兴趣的小伙伴可以直接拉到底自行学习~

我简单总结下大佬的实践思路:

  • 新建 versions.gradle 用于存放依赖/插件配置,在这里支持依赖更新/提示;

  • 新建 updateDependencies.gradle task,用于将更新后的依赖/插件同步 groovy;

  • 使用直接调用 groovy 即可。

Step 1:在项目根目录下创建 buildSrc 目录

Step 2:新建 version.gradle 依赖/插件管理

大佬在日志中以及写的很明确了,这里我单独说下我期间遇到的坑,或者是重点吧,让看到此文的小伙伴更快的上手。

  • version 之间是一些版本的配置 , 解析后会放到 Dependencies.kt 的 object Versions 中,必须存在,如下: 

/**/  // 对应的版本信息def compileSDK = 29       // 编译 SDK 版本def buildTools = "29.0.3" // Gradle 编译项目工具版本def minSDK = 23           // 最低兼容 Android 版本def targetSDK = 29        // 最高兼容 Android 版本/**/  
  • dep 之间是插件/依赖库引用路径,解析后会放到 Dependencies.kt 的 object Deps 中,同样必须存在,如下:

    /**/  // gradlePluginimplementation "com.android.tools.build:gradle:4.0.1" // permissionsDispatcher:Android 6.0 动态权限管理implementation "org.permissionsdispatcher:permissionsdispatcher:4.7.0"/**/  

这里需要注意下,// 后第一位代表你在使用中调用的名称,:后代表对当前依赖的描述。

完整的 version.gradle 内容如下(篇幅有限,移除部分项目中使用依赖):

dependencies {    /* readme *     *     * 为了统一管理插件/依赖库版本,避免版本冲突,统一将插件/依赖库信息配置在此文件中,     * 通过gradlew updateDependencies task     * 解析此文件生成对应内容到Dependencies.kt中进行统一引用     *     *  之间是一些版本的配置 , 解析后会放到Dependencies.kt的object Versions中     *     *  之间是插件/依赖库引用路径 , 解析后会放到Dependencies.kt的object Deps中     *     * 配置插件/依赖库引用说明:     * 0、版本配置格式:def  =      * 1、配置插件/依赖库引用路径前备注格式:// : ,这个部分会被解析确定插件/依赖库引用名称     * 2、配置插件/依赖库引用路径时以 implementation 作为开头     * 3、更新配置后执行 updateDependencies.gradle 的 updateDependencies task 同步更新到Dependencies.kt     *     *  Extra:     *  [Google's Maven Repository] (https://dl.google.com/dl/android/maven2/index.html)     */    /*     * Version 部分     */    /**/    def compileSDK = 29       // 编译 SDK 版本    def buildTools = "29.0.3" // Gradle 编译项目工具版本    def minSDK = 23           // 最低兼容 Android 版本    def targetSDK = 29        // 最高兼容 Android 版本    /**/    /*     * 插件/依赖库路径部分     */    /**/    // gradlePlugin    implementation "com.android.tools.build:gradle:4.0.1"     /**     * Third Lib     */    // indicator:指示器    implementation "com.hlq-struggle:indicator:1.0.0"    // permissionsDispatcher:Android 6.0 动态权限管理    implementation "org.permissionsdispatcher:permissionsdispatcher:4.7.0"    // permissionsDispatcherProcessor    implementation "org.permissionsdispatcher:permissionsdispatcher-processor:4.7.0"    // mmkv:基于 mmap 的高性能通用 key-value 组件    implementation "com.tencent:mmkv-static:1.1.0"     /**/}

Step 3:新建(拷贝)大佬提供的 updateDependencies.gradle

相关日志写的很清楚了,大家仔细阅读即可。

以下内容主要是将 version 中按照规则写好的依赖/插件进行同步 groovy 中。

/** * 将versions.gradle/xVersion.gradle中配置的版本信息生成到src/main/groovy/Dependencies.groovy中 * 执行该task方法: * 方法1: * New: 在gradle task列表面板点击'Execute Gradle Task'(类似大象的)按钮,在输入框输入'-p buildSrc updateDependencies'然后点回车键; * Deprecated: 在gradle task列表面板点击'Run Gradle Task'(类似大象的)按钮,在'Gradle Project'栏选中buildSrc模块,在'Command line'栏输入'updateDependencies'然后点击'OK'; * 方法2: * New: 在Terminal输入'gradlew -p buildSrc updateDependencies'然后执行 * Deprecated: 在Terminal输入'gradlew updateDependencies'然后执行 * 方法3: * AS->Edit Configurations->Gradle,点击左上角'+'添加配置: * Name: $projectName:buildSrc [updateDependencies] * Gradle project: $projectName/buildSrc * Tasks: updateDependencies * 点击'Apply'保存此配置,后续在项目的 gradle task 列表中就可以找到此 task 双击执行了 */task("updateDependencies") {    String inputFilePath = "versions.gradle"    String outputFilePath = "src/main/groovy/Dependencies.groovy"    // 将inputFilePath声明为该Task的inputs    inputs.file(inputFilePath)    // 将outputFilePath声明为outputs    outputs.file(outputFilePath)    doLast {        File inputFile = file(inputFilePath)        if (!inputFile.exists()) {            return        }        String inputTxt = inputFile.text        StringBuilder builder = new StringBuilder()        /*         * 解析拼接版本object         */        builder.append("/**\n")                .append(" * 版本信息\n")                .append(" */\n")                .append("interface Versions {\n")        String startFlag = "/**/"        String endFlag = "/**/"        int start = inputTxt.indexOf(startFlag)        int end = inputTxt.indexOf(endFlag)        builder.append("    ")                .append(inputTxt.substring(start + startFlag.length(), end).trim())                .append("\n}\n\n")        /*        * 解析拼接依赖object        */        builder.append("/**\n")                .append(" * 依赖库路径\n")                .append(" */\n")                .append("interface Deps {\n")        startFlag = "/**/"        endFlag = "/**/"        start = inputTxt.indexOf(startFlag)        end = inputTxt.indexOf(endFlag)        String depsTxt = inputTxt.substring(start + startFlag.length(), end).trim()        int implementationIndex        int doubleSlashIndex        while (true) {            implementationIndex = depsTxt.indexOf("implementation")            if (implementationIndex == -1) {                break            }            doubleSlashIndex = depsTxt.lastIndexOf("//", implementationIndex)            String namePart            String name            while (true) {                namePart = depsTxt.substring(doubleSlashIndex + 2, implementationIndex)                name = namePart.split(":")[0].trim()                if (!name.contains("/")) {                    break                }                doubleSlashIndex = depsTxt.lastIndexOf("//", doubleSlashIndex - 2)            }            depsTxt = depsTxt.replaceFirst("implementation", String.format("def %s =", name))        }        builder.append("    ")                .append(depsTxt)                .append("\n}\n")        String resultTxt = builder.toString()        file(outputFilePath).withPrintWriter("utf-8", { writer ->            writer.print(resultTxt)            writer.flush()        })    }}

Step 4:新建最后的 build.gradle

apply plugin: 'groovy'apply from: 'updateDependencies.gradle'

Step 5:执行同步命令

当然大佬提供了三种方案,挑选个人习惯的一种即可。

2ce8a9407af15e60c627b2cea4fde167.png

在 Step 3 中拷贝如下命令:

-p buildSrc updateDependencies

注意我画红线的地方,这是 AS 提供的一个类似历史记录的操作,很方便的记录下我们上次使用的 task,省的每次都输入。

af5bda4b6c14abb3b365d19128f61ee8.png

执行速度还是蛮快的,随后变生成了我们的 groovy 文件:

67a003e4245bb6166b011d2d919f2f82.png

大概截取此文件内容,其实就是和我们的 versions.gradle 一样,不信你看:

f3b3e054e367b10aa3bc95e4eab6dac7.png

Step 6:如何使用?

到这里其实就很 easy 了,简单举例。

Versions 使用:

d5de52349cf33b91687348346a522f1b.gif

Deps 使用:

f95e1e48f7cbbeeca54b11da30266b4c.gif

如何更新以及同步?

图片没抓到,查看原文吧~

感谢掘金大佬~

四、基于 basic 继续封装抽取 build

基本完善之后,默默的赶紧还是有点不舒服的地方,例如:

现在的架构是一个 app 下对应其它 module,而每个 module 都会有一些相同却不相同的内容,如果后期调整,难倒我要一个个去修改吗?岂不是又让鸡老大一通鄙视么。

想想,再好好想想。 

04fabf64d2bd85adf4fb1e3681d5713e.png

之前曾经做过一个 basic 抽取,同样将共有参数/信息提取到 basic.gradle 中,每个 module apply,这样就是减少不少代码量。

请看我封装好的 basic.gradle:

apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'android {    // 指定用于编译项目的 API 级别    compileSdkVersion Versions.compileSDK    // 指定在生成项目时要使用的 SDK 工具的版本,Android Studio 3.0 后不需要手动配置。    buildToolsVersion Versions.buildTools    // 指定 Android 插件适用于所有构建版本的版本属性的默认值    defaultConfig {        minSdkVersion Versions.minSDK        targetSdkVersion Versions.targetSDK        versionCode 1        versionName "1.0"        // 仅保留中文资源        resConfigs "zh"        // 启用多 dex 文件        multiDexEnabled true        ndk {            // 设置支持的SO库架构            abiFilters "armeabi", "x86"        }        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"    }    // 配置 Java 编译(编码格式、编译级别、生成字节码版本)    compileOptions {        encoding = 'utf-8'        sourceCompatibility JavaVersion.VERSION_1_8        targetCompatibility JavaVersion.VERSION_1_8    }    kotlinOptions {        jvmTarget = JavaVersion.VERSION_1_8.toString()    }    // 开启视图绑定 兼容 Gradle 4.x 及以上版本    buildFeatures {        dataBinding = true        viewBinding = true // gradle 5.x +    }    lintOptions {        // lint 异常后继续执行        abortOnError false    }}/** * implementation:不会向下传递,仅在当前 module 生效; api:向下传递,所依赖的 module 均可使用 */dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation Deps.kotlinStdlibJdk7    implementation Deps.appcompat    implementation Deps.coreKtx    testImplementation Deps.junit    androidTestImplementation Deps.extJunit    androidTestImplementation Deps.espressoCore}

我的 app build.gradle:

apply plugin: 'com.android.application'apply from: "../basic.gradle"android {    // 指定 Android 插件适用于所有构建版本的版本属性的默认值    defaultConfig {        applicationId "com.pwccn.fadvisor"    }    // 封装项目的所有构建类型配置    buildTypes {        debug {            // Log 控制器 - 输出日志            buildConfigField "boolean", "LOG_DEBUG", "true"            // 对调试 build 停用 Crashlytics            ext.enableCrashlytics = false            // 禁止自动生成 build ID            ext.alwaysUpdateBuildId = false            // 关闭资源缩减            shrinkResources false            // 关闭代码缩减            minifyEnabled false            // 关闭 zipAlign 优化            zipAlignEnabled false            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'        }        release {            // Log 控制器 - 禁止输出日志            buildConfigField "boolean", "LOG_DEBUG", "false"            // 启用资源缩减            shrinkResources true            // 启动代码缩减            minifyEnabled true            // 开启 zipAlign 优化            zipAlignEnabled true            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'        }    }}/** * implementation:不会向下传递,仅在当前 module 生效;api:向下传递,所依赖的 module 均可使用 */dependencies {    implementation Deps.legacySupportV4    implementation Deps.lifecycleExtensions    implementation Deps.lifecycleViewModelKtx    // 模块化部分导入部分    // helper    implementation project(path: ':helper')    // weight    implementation project(path: ':weight')    // 常用三方依赖导入部分    // 。。。}

我的 helper module:

apply plugin: 'com.android.library'apply from:"../basic.gradle"dependencies {    api Deps.constraintLayout    api Deps.xPopup    // helper    implementation project(path: ':helper')}

ummm,basic 配合 buildSrc,果然觉得巴适许多。

强烈推荐第三种以及第四种结合使用,那感觉,真的是 feel 倍儿爽~

参考资料

  • 配置项目全局属性

  • Use buildSrc to abstract imperative logic

  • refreshVersions 

  • 掘金之路(一)统一管理插件和依赖库信息->buildSrc

  • maven.google.com

  • BuildSrcDemo 

欢迎各位关注

不定期发布

见证成长路

b3ef9e3be2a2bfe0c5a6f14ffdd813ca.png d006a573a38d34b037bac7f90270a090.png fcaf4bf16e356a5475bfd8d2b2c1b202.gif 觉得不错,右下角点个好看呗~0440b67302cb1ff4507eeeb07de197c4.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值