Gradle详解

1:什么是构建工具呢

构建工具就是自动化帮我们完成一系列的编译打包的流程。
如果没有构建工具,我们就需要一遍一遍的执行命令去打包,比如打包APK要用javac去编译代码,再用AAPT去编译资源文件,然后编译DEX组合APK最后签名,如果每改一次代码预览都要这么干的话,那肯定是相当费劲的,所以自动化构建工具就诞生了。同时,构建工具也帮助我们做依赖管理,比如在Android Studio之前,我们用Eclipse开发,没有构建工具的情况下,我们要依赖一个三方库,需要把jar包下载下来,然后再放进项目里,这个过程是比较繁琐的,而且在升级jar版本的时候又要重复操作一遍,如果其他项目要用的话,又要把这个jar包再手动复制一遍,但是Gradle是支持依赖传递的,通过不同的依赖方式就可以改变依赖作用域。

Gradle作为构建工具,主要是帮助我们编译打包apk的,apk是由各种文件组成的,比较多见的是代码文件和资源文件,那其实可以理解为Gradle本质上是在帮我们管理这些散落在各处的文件,比如代码文件有app目录下的源码、module、还有依赖的jar和aar等等,而配置可以决定我们依赖哪些代码,也可以决定哪些代码进入merge,以及打出来的apk产物是release还是debug,但是这类配置往往并不是固定不变的,它是根据开发人员的需求走的,比如提测用debug包,发布用release包,针对厂商适配可能还要再定制一个渠道包,又或者我需要修改一下版本号等等,这些都是通过配置来改的,具有一定的动态可配性。

Gradle中的所有内容都基于两个基本概念:项目(Project)和任务(Task)。每个Gradle构建都是由一个或多个project组成。每个project都是有一个或者多个任务组成。任务之间具有依赖关系,保证了任务的执行顺序。任务代表构建执行的一些原子工作。这可能是编译某些类,创建JAR,生成Javadoc或将一些jar或者aar文件发布到maven仓库。
groovy:
Groovy 说白了就是把写 Java 程序变得像写脚本一样简单。写完就可以执行,Groovy 内部会将其编译成 Java class 然后启动虚拟机来执行。当然,这些底层的渣活不需要你管。实际上,由于 Groovy Code 在真正执行的时候已经变成了 Java 字节码,所以 JVM 根本不知道自己运行的是 Groovy 代码.

 当我们用 Android Studio 进行安装包构建的时候,会发现其实是运行了一连串的 Task,也就是说其实是这些 task 的配合,最终构建出我们的 APK 的:

> Configure project :gradlepractice
> Task :aidlclient:preBuild UP-TO-DATE
> Task :aidlclient:preDebugBuild UP-TO-DATE
> Task :aidlclient:mergeDebugNativeDebugMetadata NO-SOURCE
> Task :aidlvaslibrary:preBuild UP-TO-DATE
> Task :aidlvaslibrary:preDebugBuild UP-TO-DATE
> Task :aidlvaslibrary:packageDebugRenderscript NO-SOURCE
> Task :aidlclient:compileDebugRenderscript NO-SOURCE
> Task :aidlclient:dataBindingMergeDependencyArtifactsDebug
> Task :aidlclient:dataBindingMergeGenClassesDebug
> Task :aidlclient:generateDebugResValues
> Task :aidlclient:generateDebugResources
> Task :aidlvaslibrary:compileDebugRenderscript NO-SOURCE
> Task :aidlvaslibrary:generateDebugResValues
> Task :aidlvaslibrary:generateDebugResources
> Task :aidlvaslibrary:compileDebugAidl
> Task :aidlvaslibrary:packageDebugResources
> Task :aidlclient:compileDebugAidl NO-SOURCE
> Task :aidlclient:packageDebugResources
> Task :aidlclient:parseDebugLocalResources
> Task :aidlvaslibrary:parseDebugLocalResources
> Task :aidlclient:generateDebugBuildConfig
> Task :aidlclient:javaPreCompileDebug
> Task :aidlvaslibrary:writeDebugAarMetadata
> Task :aidlclient:createDebugCompatibleScreenManifests
> Task :aidlclient:extractDeepLinksDebug
> Task :aidlvaslibrary:extractDeepLinksDebug
> Task :aidlvaslibrary:generateDebugBuildConfig
> Task :aidlvaslibrary:javaPreCompileDebug
> Task :aidlvaslibrary:processDebugManifest
> Task :aidlvaslibrary:compileDebugLibraryResources
> Task :aidlclient:mergeDebugShaders
> Task :aidlclient:checkDebugAarMetadata
> Task :aidlvaslibrary:generateDebugRFile
> Task :aidlclient:processDebugMainManifest
> Task :aidlclient:processDebugManifest
> Task :aidlclient:compileDebugShaders NO-SOURCE
> Task :aidlclient:generateDebugAssets UP-TO-DATE
> Task :aidlvaslibrary:mergeDebugShaders
> Task :aidlvaslibrary:compileDebugShaders NO-SOURCE
> Task :aidlvaslibrary:generateDebugAssets UP-TO-DATE
> Task :aidlvaslibrary:packageDebugAssets
> Task :aidlclient:mergeDebugResources
> Task :aidlclient:mergeDebugAssets
> Task :aidlclient:mapDebugSourceSetPaths
> Task :aidlclient:compressDebugAssets
> Task :aidlclient:processDebugJavaRes NO-SOURCE
> Task :aidlvaslibrary:processDebugJavaRes NO-SOURCE
> Task :aidlvaslibrary:bundleLibResDebug NO-SOURCE
> Task :aidlclient:checkDebugDuplicateClasses
> Task :aidlclient:dataBindingGenBaseClassesDebug
> Task :aidlclient:mergeDebugJavaResource
> Task :aidlvaslibrary:compileDebugJavaWithJavac
> Task :aidlclient:desugarDebugFileDependencies
> Task :aidlvaslibrary:bundleLibCompileToJarDebug
> Task :aidlclient:processDebugManifestForPackage
> Task :aidlvaslibrary:bundleLibRuntimeToDirDebug
> Task :aidlclient:mergeDebugJniLibFolders
> Task :aidlvaslibrary:mergeDebugJniLibFolders
> Task :aidlvaslibrary:mergeDebugNativeLibs NO-SOURCE
> Task :aidlvaslibrary:copyDebugJniLibsProjectOnly
> Task :aidlclient:validateSigningDebug
> Task :aidlclient:writeDebugAppMetadata
> Task :aidlclient:writeDebugSigningConfigVersions
> Task :aidlclient:mergeDebugNativeLibs NO-SOURCE
> Task :aidlclient:stripDebugDebugSymbols NO-SOURCE
> Task :aidlclient:mergeLibDexDebug
> Task :aidlclient:mergeExtDexDebug
> Task :aidlclient:processDebugResources
> Task :aidlclient:compileDebugJavaWithJavac
> Task :aidlclient:dexBuilderDebug
> Task :aidlclient:mergeProjectDexDebug
> Task :aidlclient:packageDebug
> Task :aidlclient:createDebugApkListingFileRedirect
> Task :aidlclient:assembleDebug

 

2:C盘的.gradle目录:

依赖项下载和缓存: 当 Gradle 构建执行时,它需要下载项目所需的各种依赖项,包括库、插件和其他文件。这些文件会被下载到 .gradle/caches 子目录中,并以哈希值进行命名以防止冲突。已下载的依赖项将在缓存中保存,以便在将来的构建中重复使用,从而加快构建速度。

caches 子目录下,您可能会看到以下一些常见的子目录:

gradle缓存路径为C:\Users\taoying\.gradle\caches\modules-2\files-2.1\文件路径\hash\文件名,主要是再modules-2.1中

  • modules-2:这个目录存储 Gradle 依赖项的二进制文件,通常是 .jar 文件。每个依赖项都有一个哈希值命名的子目录,其中包含依赖项的二进制文件和元数据。

  • wrapper:如果项目使用了 Gradle Wrapper,那么 Gradle Wrapper 的相关文件和下载的 Gradle 版本会存储在这个目录中。

  • transform-1 目录:通常,这个目录用于存储从 Java 字节码转换为 Dalvik 字节码(Dex)的过程中生成的文件。这些文件包括 Dalvik 字节码、优化后的字节码、资源文件等。transform-1 目录中的文件名可能会使用哈希值等来确保唯一性和避免冲突。
  • transforms-2:这个目录存储 Android Gradle 插件进行的一些转换操作的缓存数据,如 Dex 转换。

  • transform-3 目录: 这个目录通常用于存储将多个 Dex 文件合并成一个或进行 Dex 优化的过程中生成的文件。在 Android Gradle 构建中,如果应用程序的方法数超过了 Dalvik 虚拟机的限制,构建系统会对多个 Dex 文件进行合并和优化,这就是 transform-3 目录的典型用途之一。
  •  

 

3:构建流程:

  1. 初始化阶段:

    • 执行 settings.gradle 构建脚本 , 查看当前的工程有哪些子模块 , 工程的顶层配置有哪些 , 如 rootProject.name 工程名称 ;
    • 为每个 build.gradle 构建脚本 创建对应的 Project 实例对象 ;
  2. 配置阶段:会解析 工程根目录 和 每个模块 下的 build.gradle 构建脚本 , 确定 任务分组 , 任务之间的 依赖关系 , 执行顺序 等 , 然后对任务进行配置 ; 注意这里 只对任务进行配置 , 不会执行任务 ;
    在 编写完 build.gradle 构建脚本 后 , 并 不会生成 Gradle 任务 , 在右侧的 Gradle 面板中找不到自定义的 Gradle 任务 , 需要点击 " Sync Now " 按钮 , 进行 配置阶段 操作 , 才会在右侧 Gradle 面板中 生成自定义的 Gradle 任务 , 并且 将指定的任务分配的指定的分组 , 任务间的依赖关系 , 执行先后顺序 也会进行处理配置 ;

  3. 任务执行阶段: 在这个阶段,Gradle 开始执行定义的任务。任务可以是预定义的任务,也可以是自定义的任务。Gradle 会根据任务的依赖关系和执行顺序执行这些任务,以完成项目的构建流程。任务的执行可能涉及编译代码、运行测试、生成文档、打包文件等操作。

  4. 输出阶段: 一旦任务执行完成,Gradle 将生成的输出(例如编译后的类文件、资源、测试报告、打包的应用等)放入适当的目录中,以便在其他阶段使用。

  5. 构建缓存: Gradle 支持构建缓存,它可以缓存已构建的输出以便下次构建时重用。这有助于加快构建速度,特别是在多次构建之间代码没有变化的情况下。

  6. 结束阶段: 构建过程完成后,Gradle 会生成构建报告,其中包含了构建过程的详细信息,例如哪些任务被执行、花费的时间、产生的输出等。

3:Task

1:Task的简单例子

task hello{
    print 'hello world'
}

执行./gradlew hello输出:

> Configure project :gradlepractice
hello world
> Task :gradlepractice:hello UP-TO-DATE

2:闭包:

task hello{
doLast{
print "do last "
}
doFirst{
print "do first "
}
print 'hellow test'
}

等同于:

task hello({
    doLast({
        print "do last "
    })
    doFirst({
        print "do first "
    })
    print 'hellow test'
})

 :3:task的生命周期

Task最特殊的一个地方,他会涉及到2个不同的生命周期,配置期和执行期,而配置期的代码永远是先于执行期代码执行的;

由于前两句代码在doLast和doFirst方法中,所以,这两行代码的执行是在执行期,因此总是后于最后一句的输出

task hello{
    doLast{
        print "do last "
    }
    doFirst{
        print "do first "
    }
    print 'hellow test'
}

输出:

> Configure project :gradlepractice
hellow test
> Task :gradlepractice:hello
do first do last 
BUILD SUCCESSFUL in 646ms
1 actionable task: 1 executed

 

4:依赖

task依赖:B依赖A,执行B的时候,一定先执行A

task A{
    print 'AAAAAAAAAA'
}

task B(dependsOn: A){
    print 'BBBBBBBBBB'
}

输出:

> Configure project :gradlepractice
AAAAAAAAAABBBBBBBBBB
> Task :gradlepractice:A UP-TO-DATE
> Task :gradlepractice:B UP-TO-DATE

BUILD SUCCESSFUL in 896ms

4:文件解析:

1:根目录下的build.gradle的buildScript:
repositories配置的是dependences的仓库地址;

 

 这里的依赖给具体项目中的plugin使用:

意思就是application这个插件需要从google或者mavenCentral这个仓库的com.android.tools.build:gradle这个地址去下载;

 

2:buildscript和allprojects的区别:

buildscript和allprojects中都有repositories和dependencies,两者的区别就是buildscript中的配置主要是为gradle本身服务的,gradle插件需要依赖的一些库什么的都在这里配置,allprojects则是为项目中的所有module配置的共同模块。

3:等效的写法:

 

4:buildTypes:

当这个地方配置release的时候,默认的是由两个,debug和release,debug是默认的,无需在这里配置

 我们可以在main的同一级目录下创建一个relesae目录,并且目录层级和main一样。

这样,release里面的BuildUtil这个类,当我们切换到release版本的时候,就可以得到执行。

 

 当我在buildTypes添加一个选项internal的,sync之后,Build Variants里面会多出一个internal选项:

 initWith debug:延用debug的基本配置,再次基础上可以再做自己的配置。

 

5:productFlavors,flavorDimensions:多渠道打包:

flavorDimensions 'color', 'city'
    productFlavors{
        white{
            dimension 'color'
        }
        black{
            dimension 'color'
        }
        fuzhou{
            dimension 'city'
        }
        shanghai{
            dimension 'city'
        }
    }

在BuildVariants里面形成一个多维的选择:

 

6:依赖的api和implemntation的区别:

使用implementation,a项目引用library1,library1引用library2,libraray2改变,不需要重新编译a,只需要重新编译library1和library2,但其实使用的时候,都是有重新打包的,这里影响的仅仅是编译的过程,达到提高我们提高编译速度的目的。
在多层次模块化(大于等于三层module)开发时,如果都是本地依赖,implementation相比api,主要优势在于减少build time。如果只有两层module,api与implemention在build time上并无太大的差别。
 
7:BuildConfig:

BuildConfig 是 Android 开发中一个自动生成的类,它包含了应用程序的构建配置信息和常量。这个类可以帮助开发者在应用程序代码中访问构建配置信息,以便根据不同的构建环境执行不同的操作。

BuildConfig 类通常位于应用程序的包名下,可以在 BuildConfig.java 文件中找到,它由 Android 构建工具自动生成并维护。以下是 BuildConfig 的一些常见用途:

  1. 获取构建类型信息BuildConfig 包含一个常量字段 DEBUG,它用于指示当前构建是否为调试构建。开发者可以使用这个字段来编写特定于调试或发布构建的代码。例如,你可以在调试构建中启用日志记录,而在发布构建中禁用日志记录。

    java
  • if (BuildConfig.DEBUG) {
        // 在调试构建中执行的代码
    } else {
        // 在发布构建中执行的代码
    }
  • 访问构建变量BuildConfig 还包含其他构建配置信息,如应用程序的版本号、应用程序ID等。你可以使用这些字段来获取这些信息,而无需硬编码在代码中。

    java
    String versionName = BuildConfig.VERSION_NAME; // 获取应用程序的版本名
    String applicationId = BuildConfig.APPLICATION_ID; // 获取应用程序的包名
  • 自定义构建配置:你还可以在 Gradle 构建文件中自定义构建配置,然后将这些配置信息包含在 BuildConfig 中。这可以用于在不同的构建变体中定义不同的配置。

    groovy
  1. buildTypes {
        release {
            buildConfigField "boolean", "ENABLE_FEATURE_X", "true"
        }
        debug {
            buildConfigField "boolean", "ENABLE_FEATURE_X", "false"
        }
    }

    在代码中,你可以使用 BuildConfig.ENABLE_FEATURE_X 来访问此配置信息。

总之,BuildConfig 类对于在 Android 应用程序中根据构建环境进行条件操作,以及访问构建配置信息非常有用。通过使用它,开发者可以更容易地管理不同构建变体之间的差异,以及在代码中访问应用程序的关键配置信息。

 

在实际的 Android 项目中,自定义构建配置和使用 BuildConfig 可以发挥很多作用。以下是一些示例:

  1. 特性开关:在不同的构建类型中可以使用自定义构建配置来启用或禁用特定的功能。这对于开发和测试新功能非常有用,而无需更改代码。

    groovy
  • buildTypes {
        release {
            buildConfigField "boolean", "ENABLE_FEATURE_X", "true"
        }
        debug {
            buildConfigField "boolean", "ENABLE_FEATURE_X", "false"
        }
    }

     

    在代码中,你可以使用 BuildConfig.ENABLE_FEATURE_X 来检查是否启用了特定的功能。

  • 服务器端配置:如果应用程序需要与不同的服务器端进行交互(例如,测试服务器和生产服务器),你可以使用自定义构建配置来指定不同服务器的URL。

    buildTypes {
        release {
            buildConfigField "String", "API_BASE_URL", "\"https://production.example.com/api/\""
        }
        debug {
            buildConfigField "String", "API_BASE_URL", "\"https://test.example.com/api/\""
        }
    }

     

    在应用程序中,你可以使用 BuildConfig.API_BASE_URL 来访问相应的服务器URL。

  • 密钥管理:对于需要API密钥或其他敏感信息的应用程序,可以使用自定义构建配置将这些信息存储在安全的方式中。每个构建类型可以有不同的密钥。

    groovy
  • buildTypes {
        release {
            buildConfigField "String", "API_KEY", "\"YOUR_PROD_API_KEY\""
        }
        debug {
            buildConfigField "String", "API_KEY", "\"YOUR_DEBUG_API_KEY\""
        }
    }

     

    这样,你可以在应用程序代码中使用 BuildConfig.API_KEY 来获取相应的API密钥,而不必在代码中直接硬编码密钥。

  • 调试工具:通过在 BuildConfig 中包含一些调试标志,你可以简化调试和测试过程。例如,你可以在调试版本中启用详细的日志记录,以帮助调试问题。

    buildTypes {
        debug {
            buildConfigField "boolean", "ENABLE_VERBOSE_LOGS", "true"
        }
    }

     

  • 然后在代码中,你可以使用 BuildConfig.ENABLE_VERBOSE_LOGS 来确定是否启用详细的日志记录。

自定义构建配置和 BuildConfig 提供了在不同的构建环境中管理配置和功能开关的灵活性。这使得你可以更好地管理和优化应用程序的行为,而不必在不同构建类型之间进行大量手动更改。

 

posted @ 2023-08-15 07:51  蜗牛攀爬  阅读(2016)  评论(0编辑  收藏  举报