Gradle 基础知识与原理(3)-- build.gradle 配置详解

学习了 Gradle 的基础知识与原理,咱们再来看 build.gradle 的配置详解,见下方内容:

一、Project 的 build.gradle 文件

// Top-level build file where you can add configuration options common to all sub-projects/modules.
// 翻译:顶级生成文件,您可以在其中添加所有子项目/模块通用的配置选项。

buildscript {//这里是gradle脚本执行所需依赖,分别是对应的maven库和插件
    ext.kotlin_version='1.5.30'
    repositories {
        google()//从Android Studio3.0后新增了google()配置,可以引用google上的开源项目
        jcenter()//是一个类似于github的代码托管仓库,声明了jcenter()配置,可以轻松引用 jcenter上的开源项目
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'////此处是android的插件gradle,gradle是一个强大的项目构建工具
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {//这里是项目本身需要的依赖,比如项目所需的maven库
    repositories {
        google()
        jcenter()
    }
}

// 运行gradle clean时,执行此处定义的task任务。
// 该任务继承自Delete,删除根目录中的build目录。
// 相当于执行Delete.delete(rootProject.buildDir)。
// gradle使用groovy语言,调用method时可以不用加()。
task clean(type: Delete) {
    delete rootProject.buildDir
}

1. buildscript{}

闭包里是 gradle 脚本执行所需依赖,分别是对应的 maven 库和第三方插件。

1.1 repositories{} 闭包:

配置远程仓库,该闭包中声明了 jcenter()google() 的配置,其中 jcenter 是一个代码托管仓库,上面托管了很多 Android 开源项目,在这里配置了 jcenter 后我们可以在项目中方便引用 jcenter 上的开源项目,从 Android Studio3.0 后新增了 google() 配置,可以引用 google 上的开源项目。

1.2 dependencies{} 闭包:

配置构建工具,该闭包使用 classpath 声明了一个 Gradle 插件,由于 Gradle 并不只是用来构建 Android 项目,因此此处引入相关插件来构建 Android 项目,其中 '3.0.0' 为该插件的版本号,可以根据最新的版本号来调整。

2. allprojects{}

闭包里是项目本身需要的依赖,比如项目所需的 maven 库。

问:
为什么同一个 build.gradle(Project)文件中 buildscript 和 allprojects 里面的内容基本上是一样的呢,
他们的区别在哪?

答:
buildscript 中的声明是 gradle 脚本自身需要使用的资源,
就是说他是 gradle 自己需要的资源,跟 module 其实并没有什么关系。
而 allprojects 声明的却是你所有 module 所需要使用的资源,
就是说如果你的每个 module 都需要用同一个第三库的时候,你可以在 allprojects 里面声明。

3. task clean(type: Delete){}**

运行 gradle clean 时,执行此处定义的 task。该任务继承自 Delete,删除根目录中的 build 目录。相当于执行 Delete.delete(rootProject.buildDir)。其实这个任务的执行就是可以删除生成的 Build 文件的,跟 Android Studioclean 是一个道理。

4. ext

ext 是自定义属性,现在很多人都喜欢把所有关于版本的信息都利用 ext 直接在此文件中用(咱们的项目就这样用的),或者放在另一个自己新建的 gradle 文件(version.gradle)中集中管理,这样在 build.gradle 文件中输入了 apply from:'version.gradle' 这句话,我们就可以读取到该文件下 ext 的信息,version.gradle 内容如下:

讲完 Projectbuild 文件,就来讲讲最后也是内容最多的文件了。

二、Module 的 build.gradle 文件:

从文件内容可以看出,主要分为三大部分,如下图所示:

//--------------------------------------------------第一部分----------------------------------------
plugins {
    id 'com.android.application'
}
//或 apply plugin: 'com.android.application'

//--------------------------------------------------第二部分----------------------------------------
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.lifecycledemo"
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        ndk {
            abiFilters('armeabi-v7a', 'arm64-v8a')
        }

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    //目录指向配置
    sourceSets {
        main {
          //指定lib库目录
          jniLibs.srcDirs = ['libs']
        }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    // 配置 Java 编译(编码格式、编译级别、生成字节码版本)
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
//--------------------------------------------------第三部分----------------------------------------
dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

1. plugins{}

在讲 AGP 的时候有讲到一丢丢,就是通过 plugins {...} 引入插件。
这种叫做引入 Gradle 插件,而 Gradle 插件大致分为分为两种:

  • apply plugin:'×××':叫做二进制插件,二进制插件一般都是被打包在一个 jar 里独立发布的,比如我们自定义的插件,再发布的时候我们也可以为其指定 plugin id,这个 plugin id 最好是一个全限定名称,就像你的包名一样;
  • apply from:'×××':叫做应用脚本插件,其实这不能算一个插件,它只是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是 from 关键字.后面紧跟的坫一个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用 HTTP URL

虽然脚本插件不是一个真正的插件,但是不能忽视它的作用.它是脚本文件模块化的基础,我们可以把庞大的脚本文件进行分块、分段整理,拆分成一个个共用、职责分明的文件,然后使用 apply from 来引用它们,比如我们可以把常用的函数放在一个 Utils.gradle 脚本里,供其他脚本文件引用。示例中我们把 App 的版本名称和版本号单独放在一个脚本文件里,清晰、简单、方便、快捷.我们也可以使用自动化对该文件自动处理,生成版本。
看下面代码:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'walle'
apply from: 'walle.gradle'
apply plugin: 'com.huawei.agconnect'

这里就引用了脚本插件:walle.gradle,可去项目中看源码。

说说 Gradle 插件的作用

把插件应用到你的项目中,插件会扩展项目的功能,帮助你在项目的构建过程中做很多事情。

  • 可以添加任务到你的项目中,帮你完成一些亊情,比如测试、编译、打包。
  • 可以添加依赖配置到你的项目中,我们可以通过它们配置我们项目在构建过程中需要的依赖.比 如我们编译的时候依赖的第三方库等。
  • 可以向项目中现有的对象类型添加新的扩展属性、 方法等,让你可以使用它们帮助我们配置、优化构建,比如 android{} 这个配置块就是 Android Gradle 插件为 Project 对象添加的一个扩展。
  • 可以对项目进行一些约定,比如应用 Java 插件之后,约定 src/main/java 目录下是我们的源代码存放位置,在编译的时候也是编译这个目录下的 Java 源代码文件。

然后我们说说 'com.android.application'

Android Gradle 插件的分类其实是根据 Android 工程的属性分类的。在 Andriod 中有 3 类工程,一类是 App 应用工程,它可以生成一个可运行的apk应用;一类是 Library 库工程,它可以生成 AAR 包给其他的 App 工程公用,就和我们的 Jar 一样,但是它包含了 Android 的资源等信息,是一个特殊的 Jar 包;最后一类是 Test 测试工程,用于对 App 工程或者 Library 库工程进行单元测试。

  • App 插件 id:com.android.application
  • Library 插件 id:com.android.library
  • Test 插件id:com.android.test

一般一个项目只会设置一个 App 插件,而 module 一般是会设置为 Library 插件

2. android{}

Android 插件提供的一个扩展类型,可以让我们自定义 Android Gradle 工程,是 Android Gradle 工程配置的唯一入口。

2.1 compileSdkVersion

是编译所依赖的 Android SDK 的版本,这里是 API Level

2.2 buildToolsVersion

是构建该 Android 工程所用构建工具的版本。

2.3 defaultConfig{}
  • applicationId
    配置我们的包名,包名是 app 的唯一标识,其实他跟 AndroidManifest 里面的 package 是可以不同的,他们之间并没有直接的关系。 package 指的是代码目录下路径;applicationId 指的是 app 对外发布的唯一标识,会在签名、申请第三方库、发布时候用到。
  • minSdkVersion
    是支持的 Android 系统的 api level,这里是 19 ,也就是说低于 Android 19 版本的机型不能使用这个 app
  • targetSdkVersion
    表明我们是基于哪个 Android 版本开发的,这里是 30
  • versionCode
    表明我们的 app 应用内部版本号,一般用于控制 app 升级,当然我在使用的 bugly 自动升级能不能接受到升级推送就是基于这个。
  • versionName
    表明我们的 app 应用的版本名称,一般是发布的时候写在 app 上告诉用户的,这样当你修复了一个 bug 并更新了版本,别人却发现说怎么你这个 bug 还在,你这时候就可以自信的告诉他自己看下 app 的版本号。(亲身经历在撕逼的时候可以从容的应对)
  • multiDexEnabled
    用于配置该 BuildType 是否启用自动拆分多个 Dex 的功能。一般用程序中代码太多,超过了 65535 个方法的时候。
  • ndk{}
    多平台编译,生成有so包的时候使用,包括四个平台 'armeabi', 'x86', 'armeabi-v7a', 'mips'。一般使用第三方提供的 SDK 的时候,可能会附带 so 库。
  • sourceSets
    源代码集合,是Java插件用来描述和管理源代码及资源的一个抽象概念,是一个 Java 源代码文件和资源文件的集合,我们可以通过 sourceSets 更改源集的 Java 目录或者资源目录等。

譬如像上面代码,配置 jniLibs.srcDirs = ['libs'],可以在 Android studioAndroid 视图下生成 jniLibs 文件夹,可以方便我们存放 jar 包和库文件,其中 Android 视图下的 jniLibsproject 视图下的 libs 指向同一文件夹(app → libs),如下图所示:

  • flavorDimensions
    官网翻译过来是风味维度,我个人理解为特点维度。
    为啥把 productFlavors 放在下面讲,因为 productFlavorsflavorDimensions 经常一起使用。
2.4 productFlavors

在我看来他就是 Gradle 的多渠道打包,你可以在不同的包定义不同的变量,实现自己的定制化版本的需求。
比如设置不同的包名、应用名等。场景:当我们使用友盟统计时,通常需要设置一个渠道 ID ,那么我们就可以利用 productFlavors 来生成对应渠道信息的包,如:

android {
    productFlavors {
        wandoujia {
            //豌豆荚渠道包配置
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
            //manifestPlaceholders的使用在后续章节(AndroidManifest里的占位符)中介绍
        }
        xiaomi {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
            applicationId "com.wiky.gradle.xiaomi" //配置包名

        }
        _360 {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
        }
        //...
    }
}

当然也有更简洁的方式:

android {
    productFlavors {
        wandoujia {}
        xiaomi {}
        _360 {}
       //...
    }

    productFlavors.all {
        //批量修改,类似一个循序遍历
        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }
}
  • manifestPlaceholders
    占位符,我们可以通过它动态配置 AndroidManifest 文件一些内容,譬如 app 的名字:

看看上图,我们就能发现我们在 productFlavors 中定义 manifestPlaceholders = [APP_NAME: "(测试)"] 之后,在 AndroidManifestlabel 加上 "${APP_NAME}",我们就能控制每个包打出来的名字是我们想要不同的名字,譬如测试服务器和生产服务器的包应该名字不一样。

  • **dimension **
2.5 buildTypes{}
 buildTypes {// 生产/测试环境配置
        release {// 生产环境
            buildConfigField("boolean", "LOG_DEBUG", "false")//配置Log日志
            buildConfigField("String", "URL_PERFIX", "\"https://release.cn/\"")// 配置URL前缀
            minifyEnabled false//是否对代码进行混淆
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//指定混淆的规则文件
            signingConfig signingConfigs.release//设置签名信息
            pseudoLocalesEnabled false//是否在APK中生成伪语言环境,帮助国际化的东西,一般使用的不多
            zipAlignEnabled true//是否对APK包执行ZIP对齐优化,减小zip体积,增加运行效率
            applicationIdSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
            versionNameSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
        }
        debug {// 测试环境
            buildConfigField("boolean", "LOG_DEBUG", "true")//配置Log日志
            buildConfigField("String", "URL_PERFIX", "\"https://test.com/\"")// 配置URL前缀
            minifyEnabled false//是否对代码进行混淆
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//指定混淆的规则文件
            signingConfig signingConfigs.debug//设置签名信息
            debuggable false//是否支持断点调试
            jniDebuggable false//是否可以调试NDK代码
            renderscriptDebuggable false//是否开启渲染脚本就是一些c写的渲染方法
            zipAlignEnabled true//是否对APK包执行ZIP对齐优化,减小zip体积,增加运行效率
            pseudoLocalesEnabled false//是否在APK中生成伪语言环境,帮助国际化的东西,一般使用的不多
            applicationIdSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
            versionNameSuffix 'test'//在applicationId 中添加了一个后缀,一般使用的不多
        }
    }

构建类型,在 Android Gradle 工程中,它已经帮我们内置了 debugrelease 两个构建类型,两种模式主要车别在于,能否在设备上调试以及签名不一样,其他代码和文件资源都是一样的。一般用在代码混淆,而指定的混淆文件在下图的目录上,minifyEnabled=true 就会开启混淆:

常用关键字:

关键字 解释
buildConfigField 自定义函数变量
applicationIdSuffix 应用id后缀
versionNameSuffix 版本名称后缀
debuggable 是否生成一个debug的apk
minifyEnabled 是否混淆
proguardFiles 混淆文件
signingConfig 签名配置
manifestPlaceholders 是否去除未利用的资源,默认false,表示不去除
zipAlignEnable 是否使用zipalign工具压缩
multiDexEnabled 是否拆成多个Dex
multiDexKeepFile 指定文本文件编译进主Dex文件中
multiDexKeepProguard 指定混淆文件编译进主Dex文件中
2.6 signingConfigs

签名配置,一个 app 只有在签名之后才能被发布、安装、使用,签名是保护 app 的方式,标记该 app 的唯一性。如果 app 被恶意删改,签名就不一样了,无法升级安装,一定程度保护了我们的 app。而 signingConfigs 就很方便为我们提供这个签名的配置。storeFile 签名文件,storePassword 签名证书文件的密码,storeType 签名证书类型,keyAlias 签名证书中秘钥别名,keyPassword 签名证书中改密钥的密码。

默认情况下,debug 模式的签名已经被配置好了,使用的就是 Android SDK 自动生成的 debug 证书,它一般位于$HOME/.android/debug.keystore,其 key 和密码是已经知道的,一般情况下我们不需要单独配置 debug 模式的签名信息。

2.7 dexOptions{}

我们知道,Android 中的 Java 源代码被编译成 class 字节码后,在打包成 apk 的时候
被dx命令优化成 Android 虚拟机可执行的 DEX 文件。
DEX 文件比较紧凑,Android 费尽心思做了这个 DEX 格式,就是为了能使我们的程序在 Android 中平台上运行快一些。对于这些生成 DEX 文件的过程和处理,Android Gradle 插件都帮我们处理好了,Android Gradle 插件会调用 SDK 中的 dx 命令进行处理。

但是有的时候可能会遇到提示内存不足的错误,大致提示异常是 java,lang.OutOfMemoryError: GC overhead limit exceeded,为什么会提示内存不足呢?

其实这个 dx 命令只是一个脚本,它调用的还是 Java 编写的 dx.jar 库,是 Java 程序处理的,所以当内存不足的时候,我们会看到这个 Java 异常信息.默认情况下给 dx 分配的内存是一个 G8 ,也就是 1024MB
所以我们只需要把内存设置大一点,就可以解决这个问题,如下代码,我们把内存设置为 4g

android {
...
    dexOptions {
        javaMaxHeapSize "4g"
    }
...
}

dependencies{}
我们平时用的最多的大概就这个了,看下图:

  1. 首先第一句 compile fileTree(include: ['.jar'], dir: 'libs')*,这样配置之后本地 libs 文件夹下的扩展名为jar的都会被依赖,非常方便。
  2. 如果你要引入某个本地 module 的话,那么需要用 compile project('×××')
  3. 如果要引入网上仓库里面的依赖,我们需要这样写 compile group:'com.squareup.okhttp3',name:'okhttp',version:'3.0.1' ,当然这样是最完整的版本,缩写就把 group、name、version 去掉,然后以 ":" 分割即可。

但是到了 gradle3.0 以后 build.gradle 中的依赖默认为 implementation ,而不是
之前的 compile 。另外,还有依赖指令 api

问题:gradle 3.0中依赖implementation、api的区别:
其实 api 跟以前的 compile 没什么区别,将 compile 全部改成 api 是不会错的;
implementation 指令依赖是不会传递的,也就是说当前引用的第三方库仅限于本 module 内使
可参考:
android gradle依赖:implementation 和compile的区别

3. Gradle 实用技巧

比如:

  • Gradle 依赖树查询
  • 使用循环优化Gradle依赖管理
  • 支持代码提示的Gradle依赖管理
  • Gradle 模块化
  • Library模块Gradle代码复用
  • 资源文件分包
  • AAR依赖与源码依赖快速切换

可参考:
7个你应该知道的Gradle实用技巧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 155,958评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,179评论 1 286
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,792评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,409评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,723评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,174评论 1 204
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,543评论 2 306
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,270评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,899评论 1 236
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,235评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,771评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,139评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,688评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,646评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,194评论 2 265
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,145评论 2 258

推荐阅读更多精彩内容