学习了 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 Studio
的 clean
是一个道理。
4. ext
ext
是自定义属性,现在很多人都喜欢把所有关于版本的信息都利用 ext
直接在此文件中用(咱们的项目就这样用的),或者放在另一个自己新建的 gradle
文件(version.gradle)
中集中管理,这样在 build.gradle
文件中输入了 apply from:'version.gradle'
这句话,我们就可以读取到该文件下 ext
的信息,version.gradle
内容如下:
讲完 Project
的 build
文件,就来讲讲最后也是内容最多的文件了。
二、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 studio
的 Android
视图下生成 jniLibs
文件夹,可以方便我们存放 jar
包和库文件,其中 Android
视图下的 jniLibs
和 project
视图下的 libs
指向同一文件夹(app → libs)
,如下图所示:
- flavorDimensions
官网翻译过来是风味维度,我个人理解为特点维度。
为啥把productFlavors
放在下面讲,因为productFlavors
和flavorDimensions
经常一起使用。
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: "(测试)"]
之后,在 AndroidManifest
的 label
加上 "${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
工程中,它已经帮我们内置了 debug
和 release
两个构建类型,两种模式主要车别在于,能否在设备上调试以及签名不一样,其他代码和文件资源都是一样的。一般用在代码混淆,而指定的混淆文件在下图的目录上,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{}
我们平时用的最多的大概就这个了,看下图:
- 首先第一句
compile fileTree(include: ['.jar'], dir: 'libs')*
,这样配置之后本地libs
文件夹下的扩展名为jar的都会被依赖,非常方便。 - 如果你要引入某个本地
module
的话,那么需要用compile project('×××')
。 - 如果要引入网上仓库里面的依赖,我们需要这样写
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实用技巧