本文也是我看了很多关于Gradle书籍和博客中总结出来的,还有平时自己开发的一些gradle配置使用。算是一篇总结gradle使用和科普的文章,当然如果在开发中碰到一些需要了解的gradle也会在后续对本文进行更新。
如果你想用Gradle构建你的Android项目,那么你需要创建一个构建脚本,这个脚本通常被叫作build.gradle。Gradle构建脚本的书写没有基于传统的XML文件,而是基于Groovy的领域专用语言(DSL)。
当用Android Studio创建一个新项目时,会默认生成三个Gradle文件。其中的两个文件——settings.gradle和build.gradle位于项目的根目录。另外一个build.gradle文件则在Androidapp模块内被创建。
include ':app'
buildscript {
repositories {
// Gradle 4.1及更高版本包括使用google()
//方法支持Google的Maven 仓库。你需要包含这
//个repo来下载Android Gradle插件3.0.0或更高版本。
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
repositories代码块将JCenter配置成一个仓库,在这种情况下,一个仓库意味着一系列的依赖包,或者说,在我们应用和依赖项目中可使用的一系列可下载的函数库。JCenter是一个很有名的Maven库。
dependencies代码块用于配置构建过程中的依赖包。这也意味着你不能将你的应用或依赖项目所需要的依赖包包含在顶层构建文件中。默认情况下,唯一被定义的依赖包是Gradle的Android插件。每个Android模块都需要有Android插件,因为该插件可使其执行Android相关的任务。
allprojects代码块可用来声明那些需要被用于所有模块的属性。你甚至可以在all-projects中创建任务,这些任务最终会被运用到所有模块。
注意:这里我们看到的是Android Gradle插件版本,而不是我们的Gradle版本,Android Gradle 其实只是 Gradle 的一个插件,是 Google 基于 Gradle 提供的插件接口所做的一些扩展。而这个插件版本也是根据gradle版本开发的,因为在我们引入项目的是时候往往总会因为插件和gradle版本不对应产生很多问题。
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "practice.kagura.com.toolbarshadedemo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
//recyclerview
compile 'com.android.support:recyclerview-v7:23.2.1'
}
关于依赖的几个引入配置
新配置 弃用配置 行为 implementation compile Gradle将依赖项添加到编译类路径,并将依赖项打包到构建输出。但是,当您的模块配置 implementation
依赖项时,它让Gradle知道您不希望模块在编译时将依赖项泄漏给其他模块。也就是说,依赖性仅在运行时可用于其他模块。使用这种依赖性配置,而不是
api
或compile
(不建议使用)可导致 显著编译时间的改进,因为它减少了构建系统需要重新编译的模块的数量。例如,如果implementation
依赖项更改其API,则Gradle仅重新编译该依赖项以及直接依赖于它的模块。大多数应用和测试模块都应使用此配置。api compile Gradle将依赖项添加到编译类路径并构建输出。当一个模块包含一个 api
依赖项时,它让Gradle知道该模块想要将该依赖项传递给其他模块,以便它们在运行时和编译时都可用。此配置的行为就像
compile
(现在已弃用),但您应谨慎使用它,并且只能将您需要的依赖项可传递地导出到其他上游使用者。这是因为,如果api
依赖项更改其外部API,Gradle将重新编译在编译时有权访问该依赖项的所有模块。因此,拥有大量api
依赖项可以显着增加构建时间。除非您希望将依赖项的API公开给单独的模块,否则库模块应该使用implementation
依赖项。compileOnly provided Gradle仅将依赖项添加到编译类路径(即,它不会添加到构建输出中)。这在您创建Android模块时非常有用,并且在编译期间需要依赖项,但在运行时将其存在是可选的。
如果使用此配置,则库模块必须包含运行时条件以检查依赖项是否可用,然后正常更改其行为,以便在未提供时仍可正常运行。这有助于通过不添加不重要的瞬态依赖项来减小最终APK的大小。此配置的行为就像
provided
(现在已弃用)。runtimeOnly apk Gradle仅将依赖项添加到构建输出,以便在运行时使用。也就是说,它不会添加到编译类路径中。此配置的行为就像 apk
(现在已弃用)。annotationProcessor compile 要添加对作为注释处理器的库的依赖关系,必须使用 annotationProcessor
配置将其添加到注释处理器类路径 。这是因为使用此配置可以通过将编译类路径与注释处理器类路径分开来提高构建性能。如果Gradle在编译类路径上找到注释处理器,它将停用 编译避免,这会对构建时间产生负面影响(Gradle 5.0及更高版本忽略编译类路径上的注释处理器)。Android Gradle Plugin假定依赖是一个注释处理器,如果它的JAR文件包含以下文件:。如果插件检测到编译类路径上的注释处理器,则会产生构建错误。
META-INF/services/javax.annotation.processing.Processor
依赖类型
implementation project(':mylibrary')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.example.android:app-magic:12.3'
远程库依赖的时候,我们往往希望能引用到最新库,这时候可以使用动态化版本依赖。例如: dependencies {
compile 'com.android.support:support-v4:22.2.+'
compile 'com.android.support:appcompat-v7:22.2+'
compile 'com.android.support:recyclerview-v7:+'
}
在使用动态化版本时,需要格外小心。如果你允许Gradle获取最新版本,则很可能Gradle获取的依赖版本并不稳定,它会导致构建中断。更糟糕的是,其会导致在构建服务器上和你自己的机器上运行着不同版本的依赖,从而导致应用程序的行为不一致。
一个项目中有哪些任务可被使用,可以运行gradlew tasks,该命令会打印出所有可用的任务。
*在mac系统需要运行./gradlew tasks命令查看,在windows则可以直接运行gradlew task命令*
在新建的Android项目中,其会包括Android tasks、build tasks、build setuptasks、help tasks、install tasks、verification tasks和其他tasks。
Android插件基本任务
除了扩展这些任务之外,Android插件还添加了一些新的任务。下面是一些值得注意的新任务:
以上任务我们可以使用命令./gradlew installDebug(任务名) 来运行,如果是多模块则可以使用./gradlew :app:installDebug(任务名)来运行。(用命令可能会比在as中直接运行速度快一些)
自定义Task需要一些Groovy基础,不过熟悉Java很快就会上手,先来看如何自定义一个Task
task hello {
println 'Hello,world!'
}
然后通过./gradlew hello 来执行task,我们就会看到gradle打印出hello world,从输出结果上看,我们会以为我们执行了这个task,但实际“Hello,world!”在执行该任务之前就已被打印出来了。为了理解发生了什么,我们需要了解Gradle构建的生产周期。在任一Gradle构建中,都有三个阶段:初始化阶段、配置阶段和执行阶段。这只是执行了配置阶段,如果我们再写一个一样的task去执行会发现两个task都被打印了出来。
Gradle生命周期
1.初始化阶段
会去读取根工程中 setting.gradle 中的 include 信息,决定有哪几个工程加入构建,创建 project 实例,比如下面有三个工程: include ':app', ':lib1', ':lib2 。
2.配置阶段
会去执行所有工程的 build.gradle 脚本,配置 project对象,一个对象由多个任务组成,
此阶段也会去创建、配置task及相关信息。3.运行阶段
根据gradle命令传递过来的task名称,执行相关依赖任务。Task 的 Action 会在这个阶段执行。
如果你想在执行阶段给一个任务添加动作,则可以使用下面的表示法:
task hello << {
println 'Hello,world!'
}
可以看到task的定义只比上一个多了一个<<符合,他的意思是告知Gradle,代码在执行阶段执行,而不是在配置阶段,其实这也是doLast方法的短标记形式,与doLast相对的还有一个doFirst方法,通过这两个方法我们可以控制执行阶段代码的执行顺序。
task hello {
println 'Configuration'
doLast {
println 'Goodbye'
}
doFirst {
println 'Hello'
}
}
多任务执行顺序
如果此时有两个任务需要执行,那么如何来控制他们的执行顺序,这里也有针对task执行顺序的操作,使用mustRunAfter(),当两个任务task1和task2都需要被执行,则可以定义task2.mustRunAfter task1 这样task1就会在task2之前执行。
多任务依赖
同理如果task1需要依赖task2执行,则可以使用dependsOn(),可以使用task2.dependsOn task1,这样当我们只执行task2时他将会触发task1执行。
当然定义任务也有很多种写法,例如:只需要熟悉就好
task(hello) << {
println 'Hello,world!'
}
task('hello') << {
println 'Hello,world!'
}
tasks.create(name: 'hello') << {
println 'Hello,world!'
}
一个app包只有签名之后才可以发布,安装使用,而一般当我们生成release时需要使用我们生成的签名证书文件进行签名,然而我们不可能在生成release包是每次都去用签名文件进行签名,使用gradle可以自动使用我们配置好的签名信息。
最佳做法:
storePassword=xxxx
keyPassword=xxxx
keyAlias=xxxx
storeFile=xxxx.jks
以上四属性分别对应signingConfigs{}配置块。 signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']//签名证书密钥别名
keyPassword keystoreProperties['keyPassword']//密钥密码
storeFile file(keystoreProperties['storeFile'])//证书文件
storePassword keystoreProperties['storePassword']//证书文件密码
}
}
因为要获取keystore配置文件因此我们需要定义变量获取配置文件中的配置信息。
// keystore.properties file, in the rootProject folder.
def keystorePropertiesFile = rootProject.file("keystore.properties")
// Initialize a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()
// Load your keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
在build.gradle同时定义获取keystore配置文件信息。
记得在buildType中配置打包签名信息即可
buildTypes {
debug {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
release {
minifyEnabled true
// Zipalign优化
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
以上定义好之后就可以在我们构建release包时自动为我们进行签名,如果有改动只需要改动配置文件即可。
在我们添加依赖的时候往往都很少采用动态获取最新版配置,因为我们需要在依赖中写明版本号,在多模块构建时如果有依赖版本的修改就会变得十分麻烦,其实我们可以通过在project build.gradle下面配置ext代码块,他是定义额外属性的一种方式。你可以使用属性来动态定制构建过程。例如:
ext{
//版本号相关
versionCode = 10
versionName = '1.0.0'
compileSdkVersion = 26
minSdkVersion = 15
targetSdkVersion = 26
}
我们可以这样配置一些共有的信息,然后在模块build.gradle中使用
defaultConfig {
applicationId "com.xxxx.xxxxxxx"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
模板,可依照需求进行修改。
def appName(String outputFileName, String versionName) {
String appName = outputFileName.replace("app-", "").replace(".apk", "");
String debugName = appName.replace("-debug", "");
if (appName == debugName) {// 定义release版本生成的apk的名字
appName = appName.replace("-release", "") + visionName(versionName) + compilePackTime() + ".apk";
} else { // 定义debug版本生成的apk的名字
appName = debugName + visionName(versionName) + compilePackTimeD() + "_debug.apk";
}
return appName;
}
// 版本号转换
def visionName(String visionName) {
return "_V" + visionName.replace(".", "") + "_";
}
// 当前时间
def compilePackTime() {
return (new Date().format("yyyyMMdd HHmm", TimeZone.getTimeZone("GMT+8"))).replace(" ", "_")
// //
}
def compilePackTimeD() { //debug模式下name包含时分(秒)会出现 安装时错误:The APK file does not exist on disk
return (new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+8")))
}
android{
......
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
outputFileName = appName(outputFile.name,
defaultConfig.versionName)
} else {//这里使用之前定义apk文件名称
outputFileName =
"${variant.productFlavors[0].name}_${defaultConfig.versionName}_${compilePackTime()}_.apk"
}
}
}
}
一般来说app在未发布前,接口地址都是测试环境,而在发布时会切换到生产环境,我们有的时候需要经常修改代码来切换,这也可以通过修改gradle来做到。
android {
buildTypes {
debug {
buildConfigField "String","API_URL", "\"http://test.example.com/api\""
buildConfigField "boolean","LOG_HTTP_CALLS","true"
}
release {
buildConfigField "String","API_URL", "\"http://example.com/api\""
buildConfigField "boolean","LOG_HTTP_CALLS","false"
}
}
}
这样在代码中我们就可以通过BuildConfig类来引用API_URL直接做到修改url地址,注意地址需要有转义
网络上很多资料需求不同,后续会有单独博客讲解
1.4.1加速构建参数
Gradle有一个叫作jvmargs的属性可以让你为JVM的内存分配池设置不同的值。对构建速度有直接影响的两个参数是Xms和Xmx。Xms参数用来设置初始内存大小,Xmx用来设置最大内存。你可以在gradle.properties文件中,手动设置这些值:org.gradle.jvmargs=-Xms256m -Xmx1024m
3.多模块构建加速配置:org.gradle.confgureondemand,它会忽略正在执行的task不需要的模块来限制在配置阶段的时间消耗。
1.4.2 优化apk
使用ProGuard,它可以缩减APK文件大小,还可以在编译期优化、混淆和预校验你的代码。在gradle中使用minifyEnabled的布尔类型属性,设置为true来激活ProGuard。激活之后我们可以通过配置proguard-rules.pro来保留一下我们不需要删除和混淆的类,网上对于这个文件的配置已经有很多模板这里不贴配置。
自动缩减:
直接配置shrinkResources为true
android {
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
}
}
}
如果有动态资源被删除,可以在res/raw 下配置keep.xml,防止资源被删除。
手动缩减:
除去语言包以及一些密度图片,或者so不同架构适配文件。可以通过手动配置方法,
android {
defaultConfig {
resConfigs “en",“da",“nl"
}
}
保留语言
android {
defaultConfig {
resConfigs “hdpi",“xhdpi",“xxhdpi",“xxxhdpi"
}
}
保留图片
android{
defaultConfig{
ndk {
abiFilters 'armeabi-v7a'//, 'armeabi', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
}
}
}
so库