说明
本文主要介绍和Gradle关系密切、相对不容易理解的配置,偏重概念介绍。部分内容是Android特有的(例如ProductFlavor),其他内容则是所有Gradle工程都相同或类似的知识。
对于一些常规且相对简单的配置项,例如签名配置SigningConfig,不做具体介绍。而部分比较复杂且不太常用的内容,例如Manifest的具体合并规则,只做简单说明,并给出深入学习的相关链接,读者可自行阅读。
文章主要基于Gradle V3.3 + Android Gradle Plugin V2.3 + Android Studio 2.3,在后续版本升级后部分内容可能会改变。
文中的内容,有些是根据源码分析得到,有些是参考了官方文档,还有些参考了网上的文章。如有不正确的地方,欢迎指正。
关于Gradle DSL的语法原理和开发相关知识,可参考我的另一篇文章
Gradle开发快速入门——DSL语法原理与常用API介绍 http://www.paincker.com/gradle-develop-basics
基本问题
开始看本文前,可以思考下面这些关于Gradle的基本问题。文中会对这些问题进行解释。
settings.gradle有什么作用?
repositories {}
语句块的作用?
buildscript
和allprojects
的区别?
ProductFlavor和BuildType的区别?
依赖冲突的原因和常见解决思路?
classpath 'com.android.tools.build:gradle:2.2.3'
,有什么作用?
gradle/wrapper/gradle-wrapper.properties
中的这句有什么作用? distributionUrl=https://services.gradle.org/distributions/gradle-2.14.1-all.zip
命令行中,gradle assemble、gradle assembleDebug、gradle build 的关系?
Android Studio环境下,Gradle Sync操作做了什么工作?
Gradle
Gradle是一个基于Groovy语言的强大的构建系统,Groovy则是在Java基础上扩展的、运行在JVM上的一种脚本语言。
通过丰富的插件扩展,Gradle可以支持Java、JavaWeb、Groovy、Android等工程的编译,同时可以很方便的从Maven、Ant等迁移过来。
C系列语言也有相应的Gradle插件,但Gradle支持最好的还是Java系列语言。
Gradle也是一个命令行可执行程序,可从官网下载Gradle,可执行文件位于bin/gradle
。
执行Gradle任务的过程,主要就是在运行Java/Groovy代码。编译期间如果有代码抛出了异常,就会中断编译过程。
在Android Studio中开发时,编译就是基于Gradle实现的。Android Studio中内置了Gradle。
Gradle官网 https://gradle.org/
Gradle Wrapper
用IDEA/Android Studio创建基于Gradle的工程时,默认会在工程根目录创建GradleWrapper,包括gradlew
可执行脚本和gradle/wrapper
文件夹,其中指定了和工程配套的gradle版本。
在工程根目录下直接执行./gradlew
,会自动将参数传给wrapper指定版本的gradle,执行对应的命令;如果本机还没有该版本的gradle,则会先自动下载。
工程配置和Gradle版本通常需要对应,不正确的Gradle版本可能无法正常编译工程,因此推荐使用GradleWrapper执行Gradle命令。
gradle/wrapper/gradle-wrapper.properties
文件,指定了gradle版本、下载地址、下载的文件存放位置(Mac系统中默认在~/.gradle/wrapper/dists
目录)。此文件内容示例:
1 2 3 4 5 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
在Android Studio/IDEA中,可通过Preferences - Build, Execution, Deployment - Gradle,设置Project-level settings
中Use default gradle wrapper,指定Android Studio使用工程配置的Gradle Wrapper。
DSL、DSL Reference
Gradle使用Groovy语言封装了一整套API,通常把这套API称为DSL(Domain-Specific Languages,领域特定语言)。
通常在我们配置Gradle编译参数时,所写的gradle脚本从形式上来看,像是一门有着特殊语法格式的语言。
Gradle封装的DSL,按照固定格式用很简单的语法就能实现很复杂的配置,大大简化了配置工作。另一方面,也正是由于封装的非常完善,想深入学习Gradle,会感觉无从下手。
可以通过DSL Reference文档查看Gradle DSL支持的语法配置项。例如:
Gradle DSL Reference(Gradle原生支持的DSL配置) https://docs.gradle.org/current/dsl/
Android Plugin DSL Reference(Android的DSL配置) http://google.github.io/android-gradle-dsl/current/
关于Gradle DSL的语法原理和开发相关知识,可参考我的另一篇文章
Gradle开发快速入门——DSL语法原理与常用API介绍 http://www.paincker.com/gradle-develop-basics
Project、RootProject、SubProject (Module)
Project是Gradle中的基本概念之一,即一个工程。一个工程可以包含多个SubProject,也称为Module,最顶层的工程也称为RootProject。
一个标准的Android工程,文件结构如下。
每个build.gradle
对应一个Project,最外层的是RootProject,里面的app是SubProject。
settings.gradle
不是必须的,一般在包含子工程时就需要用这个文件指定,即我们通常所见的include ':app'
脚本。
这里的':app'
就是子工程的名字,通常和文件夹名称对应。
1 2 3 4 settings.gradle build.gradle app build.gradle
StartParameter
Gradle执行时有一些称为StartParameter的参数,StartParameter可在命令行设置,可通过gradle --help
查看。例如:
--quiet
,执行过程中,只显示Error级别的Log。
--stacktrace
,执行过程中,输出所有Exception的stacktrace。
--full-stacktrace
,执行过程中,输出所有Exception的完整stacktrace。
--no-daemon
,不使用Deamon。Deamon是用于加速Gradle执行的后台进程,有些情况下使用Deamon会有问题(可参考 https://docs.gradle.org/current/userguide/gradle_daemon.html )
--offline
,离线模式,不使用网络资源。
在命令行可通过-P
参数传入projectProperties,并在Gradle脚本中获取
1 2 # 命令行传入projectProperties ./gradlew clean -Pkey=value
1 2 print gradle.startParameter.projectProperties.get('key' )
还可以通过-D
参数传入systemPropertiesArgs,并在Gradle脚本中获取
1 2 # 命令行传入systemPropertiesArgs ./gradlew clean -Dkey=value
1 2 print gradle.startParameter.systemPropertiesArgs.get('key' )
gradle.properties
Properties文件格式可由java.util.Properties
解析,包含若干键值对,类似HashMap<String,String>
。
Gradle运行时会自动读取gradle.properties文件并引用其中的属性。有多个位置可以放gradle.properties文件,按优先级从低到高如下:
Project所在目录(即build.gradle所在目录),包括RootProject和SubProject
GradleHome目录(Mac中默认为~/gradle
)
通过gradle命令行-D
参数指定的Property
在gradle.properties文件中,一些保留Key可用于配置Gradle运行环境,例如org.gradle.daemon
用于设置GradleDeamon,org.gradle.logging.level
用于设置Gradle的Log级别等。
详情可参考 https://docs.gradle.org/current/userguide/build_environment.html
除了保留Key以外,其他Key都可以作为变量用于配置工程。例如在Project目录的gradle.properties中统一定义Support包的版本号,然后在build.gradle中使用定义的变量如下。
1 SUPPORT_LIBRARY_VERSION=23.4.0
1 2 3 4 5 6 dependencies { compile "com.android.support:support-v4:${SUPPORT_LIBRARY_VERSION}" compile "com.android.support:appcompat-v7:${SUPPORT_LIBRARY_VERSION}" compile "com.android.support:design:${SUPPORT_LIBRARY_VERSION}" compile "com.android.support:recyclerview-v7:${SUPPORT_LIBRARY_VERSION}" }
Gradle Task
Gradle以Task(任务)的形式组织每一步操作,每个Task执行一个原子操作(例如把Java编译成class文件、把class打成jar/dex文件、APK签名等)。
每个Project包含若干Task,Task之间存在依赖关系,执行一个Task前,会先执行它所依赖的Task。
每个Task有自己的名字(例如'assemble'
),结合其所属Project的名字(例如':app'
),可以组成完整名(例如':app:assemble'
)。
Gradle内建了一个名为tasks的Task,可以列举Project中的所有Task。
执行Task,查看Project中的所有Task
执行Task时,直接把Task名称传给gradle即可。
如果下载了Gradle并配置了环境变量,则可在工程根目录执行:
更推荐的做法,是在工程根目录下调用GradleWrapper执行:
执行结果如下(省略了部分输出):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 $ ./gradlew tasks :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Android tasks ------------- androidDependencies - Displays the Android dependencies of the project. signingReport - Displays the signing info for each variant. sourceSets - Prints out all the source sets defined in this project. Build tasks ----------- assemble - Assembles all variants of all applications and secondary packages. assembleAndroidTest - Assembles all the Test applications. assembleDebug - Assembles all Debug builds. assembleRelease - Assembles all Release builds. build - Assembles and tests this project. buildDependents - Assembles and tests this project and all projects that depend on it. buildNeeded - Assembles and tests this project and all projects it depends on. classes - Assembles main classes. clean - Deletes the build directory. cleanBuildCache - Deletes the build cache directory. compileDebugAndroidTestSources compileDebugSources compileDebugUnitTestSources compileReleaseSources compileReleaseUnitTestSources jar - Assembles a jar archive containing the main classes. mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests. testClasses - Assembles test classes. Build Setup tasks ----------------- init - Initializes a new Gradle build. [incubating] wrapper - Generates Gradle wrapper files. [incubating] Documentation tasks ------------------- javadoc - Generates Javadoc API documentation for the main source code. Help tasks ---------- buildEnvironment - Displays all buildscript dependencies declared in root project 'AndroidLint'. components - Displays the components produced by root project 'AndroidLint'. [incubating] dependencies - Displays all dependencies declared in root project 'AndroidLint'. dependencyInsight - Displays the insight into a specific dependency in root project 'AndroidLint'. dependentComponents - Displays the dependent components of components in root project 'AndroidLint'. [incubating] help - Displays a help message. model - Displays the configuration model of root project 'AndroidLint'. [incubating] projects - Displays the sub-projects of root project 'AndroidLint'. properties - Displays the properties of root project 'AndroidLint'. tasks - Displays the tasks runnable from root project 'AndroidLint' (some of the displayed tasks may belong to subprojects). Install tasks ------------- installDebug - Installs the Debug build. installDebugAndroidTest - Installs the android (on device) tests for the Debug build. uninstallAll - Uninstall all applications. uninstallDebug - Uninstalls the Debug build. uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build. uninstallRelease - Uninstalls the Release build. Verification tasks ------------------ check - Runs all checks. connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices. connectedCheck - Runs all device checks on currently connected devices. connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices. deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers. deviceCheck - Runs all device checks using Device Providers and Test Servers. lint - Runs lint on all variants. lintDebug - Runs lint on the Debug build. lintRelease - Runs lint on the Release build. test - Run unit tests for all variants. testDebugUnitTest - Run unit tests for the debug build. testReleaseUnitTest - Run unit tests for the release build. To see all tasks and more detail, run gradlew tasks --all To see more detail about a task, run gradlew help --task <task> BUILD SUCCESSFUL Total time: 1.367 secs
执行多个Task
如果需要先后执行多个Task,例如tasks和clean,将它们依次传给gradle即可:
排除特定Task
使用gradle的-x
或--exclude-task
参数,可指定执行Task时排除特定Task。例如:
1 ./gradlew build -x check
执行SubProject中的Task
如果想执行子工程':app'
中的Task,可使用Task的完整名执行
也可以切换到子工程目录执行,但切换当前目录会影响Gradle脚本中的相对路径,不推荐。
Task参数、查看Task详细信息
Task可以定义参数,可在执行时从命令行传入。例如Gradle内建了一个叫“help”的Task,带有一个--task
参数,可以用于查看一个Task的详细信息。
查看“tasks”这个Task的详细信息,就可以执行命令如下。其中会显示一个Task的名称、类型、参数、详细介绍、分组等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ ./gradlew help --task tasks :help Detailed task information for tasks Path :tasks Type TaskReportTask (org.gradle.api.tasks.diagnostics.TaskReportTask) Options --all Show additional tasks and detail. Description Displays the tasks runnable from root project 'GradleStudy' (some of the displayed tasks may belong to subprojects). Group help BUILD SUCCESSFUL Total time: 1.051 secs
一些常用GradleTask
clean: 清除build目录编译生成的文件 (Deletes the build directory.)
assemble:编译工程 (Assembles the outputs of this project. [jar])
build:编译并测试工程 (Assembles and tests this project. [assemble, check])
test:单元测试等 (Runs the unit tests. [classes, testClasses])
check:测试工程,包含test (Runs all checks. [test])
wrapper:生成GradleWrapper文件 (Generates Gradle wrapper files. [incubating])
help: 帮助信息 (Displays a help message.)
tasks:查看Project的所有Task (Displays the tasks runnable from root project ‘Xxx’.)
dependencies:查看Project的依赖 (Displays all dependencies declared in root project ‘Xxx’.)
projects: 查看SubProject (Displays the sub-projects of root project ‘Xxx’.)
查看Task依赖树
每个Task都会依赖若干Task,这些Task又会依赖别的Task,所有Task就会形成一个依赖树。
为了更加直观的学习,可以在app/build.gradle
中添加如下的简单脚本,让Gradle输出Task的依赖树。
方法printDependencies通过递归的方式,输出每个Task依赖的Task。afterEvaluate语句块中,先找到assembleDebug
这个Task,然后调用printDependencies输出其依赖树。由于Android中有大量Task依赖,打印出所有Task需要很久,所以这里限制了最大递归深度为3。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def printDependencies(Task task, String prefix, int depth, int maxDepth) { println prefix + task.project.name + ':' + task.name def tasks = task.getTaskDependencies().getDependencies(task) if (depth < maxDepth - 1 ) { tasks.each { t -> printDependencies(t, prefix + '\t' , depth + 1 , maxDepth) } } else { if (tasks.size() > 0 ) { println prefix + '\t' + "(${tasks.size()} child tasks...)" } } } afterEvaluate { println '===============================' def buildTask = tasks.findByName('assembleDebug' ) printDependencies(buildTask, '' , 0 , 3 ) println '===============================' }
执行任意Gradle任务,例如clean,部分输出内容如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ ./gradlew clean =============================== app:assembleDebug app:packageDebug app:processDebugResources (2 child tasks...) app:compileDebugJavaWithJavac (4 child tasks...) app:mergeDebugAssets (2 child tasks...) app:validateSigningDebug app:transformNativeLibsWithMergeJniLibsForDebug (3 child tasks...) app:transformResourcesWithMergeJavaResForDebug (2 child tasks...) app:transformClassesWithDexForDebug (2 child tasks...) app:compileDebugSources app:compileDebugNdk (1 child tasks...) app:compileDebugJavaWithJavac (4 child tasks...) =============================== :clean :app:clean BUILD SUCCESSFUL Total time: 0.975 secs
buildscript与allprojects
在RootProject的build.gradle中,经常会看到buildscript和allprojects两个语句块,并且里面都定义了一些相同的东西。
buildscript,顾名思义,是编译脚本,也就是说编译一个工程时需要的配置,例如常会看到下面这样的脚本,表示编译时要用到Android Gradle Plugin。
1 2 3 4 5 6 7 8 buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' } }
allprojects,则用于配置所有project,包括SubProject,这里面的配置的东西,则是工程代码需要的东西,例如依赖的各种开源库等。
1 2 3 4 5 allprojects { repositories { jcenter() } }
关于编译与Groovy
编译就是将程序源码转换成可执行文件或中间代码的过程。具体到Java,就是将.java
代码文件变成.class
或者进一步打包成.jar
的过程。
Gradle基于Groovy,Groovy是在Java基础上扩展的脚本语言。Groovy有和解释型语言一样的特性,可以直接从源码运行而不需要提前编译。但实际运行过程中,也是先转换成Java的class文件,再运行在JVM上的。
在buildscript的dependencies中,通过classpath语句引用一些编译好的jar包,Gradle在执行时就会将其下载并加入Java的classpath,其中的class在编译时就可以被调用,运行在电脑或云主机上。
Gradle Plugin
Gradle之所以是个强大的构建系统,很重要的一点在于其完善的插件支持。
Gradle内建了Java、Groovy等插件,除此之外,还可以在Gradle提供的一整套API基础上开发插件,实现各种编译打包工作。
Gradle Android Plugin
在Android开发编译时,会有很多Android相关的配置,这些都是由Gradle的Android插件实现的。
在buildscript中,通过dependencies引入了Gradle Android插件:
1 2 3 4 5 buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.3.3' } }
在app/build.gradle中,通过apply的方式,应用了Android插件:
1 2 3 4 5 apply plugin: 'com.android.application' apply plugin: 'com.android.library'
应用了Android插件后,即可在app/build.gradle中使用插件定义的Android相关DSL了:
1 2 3 4 5 6 7 8 9 10 11 12 android { compileSdkVersion 24 buildToolsVersion '25.0.2' defaultConfig { applicationId "com.paincker.lint.demo" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" } }
Repositories
很多从Eclipse转到Android Studio的开发者,刚开始都对Gradle自动下载依赖包的功能印象深刻。
Gradle的依赖管理完全兼容Maven和Ivy,常使用Maven仓库来实现依赖管理,当Library打包完成后上传到Maven仓库,Gradle则会从Maven仓库下载需要的依赖。
Repository就是用来指定所需要的Maven仓库。除了常见的jcenter(),mavenCentral(),还可以指定本地搭建的Maven仓库、指定URL的Maven仓库等,例如国内一些Maven仓库镜像,以及很多公司内部私有的Maven仓库等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 repositories { jcenter() mavenCentral() maven { url 'http://maven.oschina.net/content/groups/public/' } ivy { url "http://repo.mycompany.com/repo" } localRepository { dirs 'lib' } maven { url "sftp://repo.mycompany.com:22/maven2" credentials { username 'user' password 'password' } } }
Dependencies
Gradle依赖管理官方文档 https://docs.gradle.org/current/userguide/dependency_management.html
DependencyNotation
DependencyNotation用于描述要依赖的模块。
外部依赖
通常用group:name:version:classifier@ext
的形式表示。其中group通常用包名,name表示实际的名字,version表示版本,classifier在Android中是ProductFlavor和BuildType的组合(后面会介绍),ext则表示扩展名。
1 2 3 compile "org.gradle.test.classifiers:service:1.0:jdk15@jar" compile group: 'org.gradle.test.classifiers' , name: 'service' , version: '1.0' , classifier: 'jdk15'
Project依赖
1 compile project(':someProject' )
文件依赖
1 2 3 4 5 6 7 dependencies { compile files('hibernate.jar' , 'libs/spring.jar' ) compile fileTree('libs' ) }
参考官方文档 https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html
依赖传递(transitive)
Gradle依赖项可配置transitive属性,表示是否递归解析此模块的依赖项,默认为true。
1 2 3 compile('org.hibernate:hibernate:3.0.5' ) { transitive = true }
依赖树查看
每个模块都会依赖若干模块,这些模块又分别依赖其他模块,形成一个依赖树。Gradle提供了名为dependencies的Task,可查看Project的依赖树,执行效果如下(省略了部分输出)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ ./gradlew :app:dependencies ------------------------------------------------------------ Project :app ------------------------------------------------------------ compile - Classpath for compiling the main sources. +--- com.android.support:appcompat-v7:24.0.0 +--- com.android.support:support-v4:24.0.0 \--- com.android.support:support-annotations:24.0.0 +--- com.android.support:support-vector-drawable:24.0.0 \--- com.android.support:support-v4:24.0.0 (*) \--- com.android.support:animated-vector-drawable:24.0.0 \--- com.android.support:support-vector-drawable:24.0.0 (*) \--- com.android.support.constraint:constraint-layout:1.0.2 \--- com.android.support.constraint:constraint-layout-solver:1.0.2
Artifact
Artifact可以理解成一个模块的具体实现。一个依赖项可以包含多个Artifact,例如依赖项com.demo:mymodule:library:1.0
中可以包含多个不同格式、BuildType的Artifact:
library-1.0-debug.jar
library-1.0-release.jar
library-1.0-debug.aar
library-1.0-release.aar
Module Descriptor、POM文件
Gradle是如何获取到一个模块的依赖项的呢?
在Maven或Ivy仓库中,模块的依赖信息并不包含在Artifact文件中,而是通过ModuleDescriptor文件声明的。
以阿里的Maven仓库为例,用浏览器打开链接 http://maven.aliyun.com/nexus/content/groups/public/com/android/tools/build/gradle/2.3.0/
可以看到com.android.tools.build:gradle:2.3.0
这个模块所包含的文件。其中sources.jar为代码源文件,pom文件则为ModuleDescriptor。
gradle-2.3.0-sources.jar
gradle-2.3.0-sources.jar.md5
gradle-2.3.0-sources.jar.sha1
gradle-2.3.0.jar
gradle-2.3.0.jar.md5
gradle-2.3.0.jar.sha1
gradle-2.3.0.pom
gradle-2.3.0.pom.md5
gradle-2.3.0.pom.sha1
在Maven仓库中,模块的POM文件可以指定默认的Artifact,并声明其依赖项;不支持分别声明多个Artifact的依赖项。AAR工程配置多版本发布的时候,需要考虑这一特性。
If you declare a module dependency, Gradle looks for a module descriptor file (pom.xml or ivy.xml) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of this module (e.g. hibernate-3.0.5.jar) as well as its dependencies (e.g. cglib) are downloaded. If no such module descriptor file exists, Gradle looks for a file called hibernate-3.0.5.jar to retrieve. In Maven, a module can have one and only one artifact. In Gradle and Ivy, a module can have multiple artifacts. Each artifact can have a different set of dependencies.
多Artifact的依赖处理、Artifact only notation
Gradle在处理依赖时,对于有多个Artifact的Maven模块,可在DependencyNotation中声明需要的Artifact,没有声明则使用POM文件指定的默认版本,POM中也没有指定则默认使用和module名一致的jar包。
当使用“@”指定了所依赖模块的Artifact,称为Artifact only notation,此时Gradle只会下载对应的Artifact,而不会下载其依赖,此时可能就需要设置transitive属性。
1 2 3 compile('com.facebook.fresco:fresco:0.10.0@aar' ) { transitive = true }
If no module descriptor file can be found, Gradle by default downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors, you want to download only the artifact jar, without the dependencies. [11] And sometimes you want to download a zip from a repository, that does not have module descriptors. Gradle provides an artifact only notation for those use cases - simply prefix the extension that you want to be downloaded with ‘@’ sign.
依赖冲突分解
依赖项很多时,依赖项之间经常会发生冲突。例如多个SDK分别依赖了不同版本的AppCompat,就可能导致冲突。Gradle提供了一些API可以用来处理依赖冲突。
常见的依赖冲突解决思路可参考:
Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决 http://www.paincker.com/gradle-dependencies
ProductFlavor、BuildType与Build Variant
Android中定义了ProductFlavor和BuildType的DSL。
ProductFlavor
ProductFlavor可以实现一套代码编译成不同的版本,版本之间差异比较小,例如开发版本、测试版本、线上版本;或是发布到某些应用市场的定制版本(例如需要修改一些资源文件)等。
ProductFlavor中包含了一些应用相关的配置,例如minSdkVersion,versionCode等。下面的代码,就是在对默认的ProductFlavor做配置。
1 2 3 4 5 6 7 8 9 android { defaultConfig { applicationId "com.paincker.gradle.demoapplication" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" } }
可以在ProductFlavors中定义新的Flavor并进行配置,覆盖DefaultProductFlavor中的相应配置。
1 2 3 4 5 6 7 android { productFlavors { myflavor { minSdkVersion 20 } } }
ProductFlavor还支持多维度(Multi-flavors),每个纬度之间可以进行组合。例如下面的示例,flavor有abi和version两个纬度,最后就会有6种组合:
x86-freeapp
arm-freeapp
mips-freeapp
x86-paidapp
arm-paidapp
mips-paidapp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 android { ... flavorGroups "abi" , "version" productFlavors { freeapp { flavorGroup "version" ... } paidapp { flavorGroup "version" ... } x86 { flavorGroup "abi" ... } arm { flavorGroup "abi" ... } mips { flavorGroup "abi" ... } } }
参考:多定制的变种版本 https://flyouting.gitbooks.io/gradle-plugin-user-guide-cn/content/multi-flavor_variants.html
BuildType
BuildType本身是软件开发中的通用概念,表示编译版本。
Android中定义了自己的BuildType接口,其中包含了一些编译相关的配置,例如debuggable(是否可调试)、minifyEnable(是否开启Proguard)等。
可以在buildTypes中配置支持的BuildType如下。即使不做任何配置,默认也会有Debug和Release两个BuildType,且分别包含了一套默认值,例如Debug的debuggable参数默认为true,而Release的debuggable参数默认为false。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 android { buildTypes { debug { } develop { debuggable false minifyEnabled false } release { debuggable false minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt' ), 'proguard-rules.pro' } } }
BuildVariant
ProductFlavor与BuildType组合,就成为BuildVariant。在前面多纬度Flavor的示例基础上,6个Flavor组合2个BuildType,总共就会有12种BuildVariant。
每个Build Variant包含了一个ProductFlavor和一个BuildType。BuildType和ProductFlavor中某些配置是重叠的,组合成BuildVariant后,通常会按优先级处理(一般BuildType优先级更高),或做合并处理。
例如两者都可以配置签名signingConfigs,合并成BuildVariant后,会优先取取BuildType中定义的;而对于配置项proguardFiles,则会采取合并处理。
具体的合并逻辑,可以参考这个类中的实现 com.android.builder.core.VariantConfiguration
BuildConfig
Android开发常用到BuildConfig类,这个类可在编译时生成,用于在代码中获取一些编译相关的参数,包括是否可以Debug、当前BuildType和Flavor名字、VersionCode和VersionName等,例如常会用BuildConfig.DEBUG
判断是否输出Log信息。
BuildType配置中提供了一个buildConfigField方法,可以往BuildConfig中添加自定义字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int xxLibraryVersion = 192 android { buildTypes { debug { buildConfigField "int" , "XX_LIBRARY_VERSION" , xxLibraryVersion } release { buildConfigField "int" , "XX_LIBRARY_VERSION" , xxLibraryVersion } } } dependencies { compile "com.xxx:xx:1.0.${xxLibraryVersion}" }
1 2 3 4 5 6 7 8 9 10 11 public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true" ); public static final String BUILD_TYPE = "debug" ; public static final int XX_LIBRARY_VERSION = 192 ; } private void showLibraryInfo () {String msg = "xx library version is " + BuildConfig.XX_LIBRARY_VERSION; Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); }
Configurations
在Gradle中,一个Project可以有多个Configuration,每个Configuration有不同的依赖配置。
例如在dependencies中,经常用到compile xxx
,这里的compile就是一个Configuration。一些常用的Configuration例如:
compile:最常用,参与编译并打包到APK中
testCompile:用于单元测试
androidTestCompile:用于Android自动化测试
provided:参与编译但不打包到APK中,类似eclipse中的external-libs
apk:打包到APK中但不参与编译,不能在代码中调用
考虑到BuildType和ProductFlavor,又会和上述Configuration组合成新的Configuration,例如:
compile
debugCompile
myflavorCompile
myflavorDebugCompile
testCompile
testDebugCompile
testMyflavorCompile
testMyflavorDebugCompile
在运行gradle dependencies
时,也会分别显示每个Configuration对应的依赖树,如下(省略了部分输出)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 ./gradlew :app:dependencies :app:dependencies ------------------------------------------------------------ Project :app ------------------------------------------------------------ androidJacocoAgent - The Jacoco agent to use to get coverage data. \--- org.jacoco:org.jacoco.agent:0.7.5.201505241946 androidJacocoAnt - The Jacoco ant tasks to use to get execute Gradle tasks. \--- org.jacoco:org.jacoco.ant:0.7.5.201505241946 +--- org.jacoco:org.jacoco.core:0.7.5.201505241946 \--- org.ow2.asm:asm-debug-all:5.0.1 +--- org.jacoco:org.jacoco.report:0.7.5.201505241946 +--- org.jacoco:org.jacoco.core:0.7.5.201505241946 (*) \--- org.ow2.asm:asm-debug-all:5.0.1 \--- org.jacoco:org.jacoco.agent:0.7.5.201505241946 androidTestAnnotationProcessor - Classpath for the annotation processor for 'androidTest'. No dependencies androidTestApk - Classpath packaged with the compiled 'androidTest' classes. No dependencies androidTestCompile - Classpath for compiling the androidTest sources. No dependencies androidTestJackPlugin - Classpath for the 'androidTest' Jack plugins. No dependencies androidTestProvided - Classpath for only compiling the androidTest sources. No dependencies androidTestWearApp - Link to a wear app to embed for object 'androidTest'. No dependencies annotationProcessor - Classpath for the annotation processor for 'main'. No dependencies apk - Classpath packaged with the compiled 'main' classes. No dependencies archives - Configuration for archive artifacts. No dependencies compile - Classpath for compiling the main sources. +--- com.android.support:appcompat-v7:24.0.0 +--- com.android.support:support-v4:24.0.0 \--- com.android.support:support-annotations:24.0.0 +--- com.android.support:support-vector-drawable:24.0.0 \--- com.android.support:support-v4:24.0.0 (*) \--- com.android.support:animated-vector-drawable:24.0.0 \--- com.android.support:support-vector-drawable:24.0.0 (*) \--- com.android.support.constraint:constraint-layout:1.0.2 \--- com.android.support.constraint:constraint-layout-solver:1.0.2 debugAnnotationProcessor - Classpath for the annotation processor for 'debug'. No dependencies debugApk - Classpath packaged with the compiled 'debug' classes. No dependencies debugCompile - Classpath for compiling the debug sources. No dependencies debugJackPlugin - Classpath for the 'debug' Jack plugins. No dependencies debugProvided - Classpath for only compiling the debug sources. No dependencies debugWearApp - Link to a wear app to embed for object 'debug'. No dependencies default - Configuration for default artifacts. No dependencies default-mapping - Configuration for default mapping artifacts. No dependencies default-metadata - Metadata for the produced APKs. No dependencies jackPlugin - Classpath for the 'main' Jack plugins. No dependencies provided - Classpath for only compiling the main sources. No dependencies ...
SourceSet
Gradle中使用SourceSet管理Java源码;Android定义了自己的SourceSet,其用法和Gradle类似。
Gradle的SourceSet官方文档: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html
Android的SourceSet官方文档: http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.api.AndroidSourceSet.html
默认的SourceSet即main,根目录位于src/main
,其中又包括多个子目录,例如:
src/main/java,Java源码
src/main/resources,Java资源文件
src/main/res,Android资源文件
src/main/assets,Android assets文件
ProductFlavor、BuildType、BuildVariant、test/androidTest,也会按照一定的形式组合产生SourceSet。
可以配置某个SourceSet的根目录,或者指定具体的子目录(支持多个目录),如下:
1 2 3 4 5 6 7 8 android { sourceSets { myflavor { res.srcDirs = ['src/main/res' , 'src/main/res2' ] } debug.setRoot('src/main' ) } }
使用Gradle的sourceSet可以查看项目中所有的SourceSet。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 $ ./gradlew : app: sourceSets :app: sourceSets ------------------------------------------------------------ Project : app------------------------------------------------------------ androidTest ----------- Compile configuration: androidTestCompile build.gradle name: android.sourceSets.androidTest Java sources: [app/src/ androidTest/java] Manifest file: app/src/ androidTest/AndroidManifest.xml Android resources: [app/src/ androidTest/res] Assets: [app/src/ androidTest/assets]AIDL sources: [app/src/ androidTest/aidl] RenderScript sources: [app/src/ androidTest/rs] JNI sources: [app/src/ androidTest/jni] JNI libraries: [app/src/ androidTest/jniLibs] Java-style resources: [app/src/ androidTest/resources] androidTestMyflavor ------------------- Compile configuration: androidTestMyflavorCompile build.gradle name: android.sourceSets.androidTestMyflavor Java sources: [app/src/ androidTestMyflavor/java] Manifest file: app/src/ androidTestMyflavor/AndroidManifest.xml Android resources: [app/src/ androidTestMyflavor/res] Assets: [app/src/ androidTestMyflavor/assets]AIDL sources: [app/src/ androidTestMyflavor/aidl] RenderScript sources: [app/src/ androidTestMyflavor/rs] JNI sources: [app/src/ androidTestMyflavor/jni] JNI libraries: [app/src/ androidTestMyflavor/jniLibs] Java-style resources: [app/src/ androidTestMyflavor/resources] debug ----- Compile configuration: debugCompile build.gradle name: android.sourceSets.debug Java sources: [app/src/ debug/java] Manifest file: app/src/ debug/AndroidManifest.xml Android resources: [app/src/ debug/res] Assets: [app/src/ debug/assets]AIDL sources: [app/src/ debug/aidl] RenderScript sources: [app/src/ debug/rs] JNI sources: [app/src/ debug/jni] JNI libraries: [app/src/ debug/jniLibs] Java-style resources: [app/src/ debug/resources] main ---- Compile configuration: compile build.gradle name: android.sourceSets.main Java sources: [app/src/ main/java] Manifest file: app/src/ main/AndroidManifest.xml Android resources: [app/src/ main/res] Assets: [app/src/ main/assets]AIDL sources: [app/src/ main/aidl] RenderScript sources: [app/src/ main/rs] JNI sources: [app/src/ main/jni] JNI libraries: [app/src/ main/jniLibs] Java-style resources: [app/src/ main/resources] myflavor -------- Compile configuration: myflavorCompile build.gradle name: android.sourceSets.myflavor Java sources: [app/src/ myflavor/java] Manifest file: app/src/ myflavor/AndroidManifest.xml Android resources: [app/src/ myflavor/res] Assets: [app/src/ myflavor/assets]AIDL sources: [app/src/ myflavor/aidl] RenderScript sources: [app/src/ myflavor/rs] JNI sources: [app/src/ myflavor/jni] JNI libraries: [app/src/ myflavor/jniLibs] Java-style resources: [app/src/ myflavor/resources] myflavorDebug ------------- Compile configuration: myflavorDebugCompile build.gradle name: android.sourceSets.myflavorDebug Java sources: [app/src/ myflavorDebug/java] Manifest file: app/src/ myflavorDebug/AndroidManifest.xml Android resources: [app/src/ myflavorDebug/res] Assets: [app/src/ myflavorDebug/assets]AIDL sources: [app/src/ myflavorDebug/aidl] RenderScript sources: [app/src/ myflavorDebug/rs] JNI sources: [app/src/ myflavorDebug/jni] JNI libraries: [app/src/ myflavorDebug/jniLibs] Java-style resources: [app/src/ myflavorDebug/resources] myflavorRelease --------------- Compile configuration: myflavorReleaseCompile build.gradle name: android.sourceSets.myflavorRelease Java sources: [app/src/ myflavorRelease/java] Manifest file: app/src/ myflavorRelease/AndroidManifest.xml Android resources: [app/src/ myflavorRelease/res] Assets: [app/src/ myflavorRelease/assets]AIDL sources: [app/src/ myflavorRelease/aidl] RenderScript sources: [app/src/ myflavorRelease/rs] JNI sources: [app/src/ myflavorRelease/jni] JNI libraries: [app/src/ myflavorRelease/jniLibs] Java-style resources: [app/src/ myflavorRelease/resources] release ------- Compile configuration: releaseCompile build.gradle name: android.sourceSets.release Java sources: [app/src/ release/java] Manifest file: app/src/ release/AndroidManifest.xml Android resources: [app/src/ release/res] Assets: [app/src/ release/assets]AIDL sources: [app/src/ release/aidl] RenderScript sources: [app/src/ release/rs] JNI sources: [app/src/ release/jni] JNI libraries: [app/src/ release/jniLibs] Java-style resources: [app/src/ release/resources] test ---- Compile configuration: testCompile build.gradle name: android.sourceSets.test Java sources: [app/src/ test/java] Java-style resources: [app/src/ test/resources] testDebug --------- Compile configuration: testDebugCompile build.gradle name: android.sourceSets.testDebug Java sources: [app/src/ testDebug/java] Java-style resources: [app/src/ testDebug/resources] testMyflavor ------------ Compile configuration: testMyflavorCompile build.gradle name: android.sourceSets.testMyflavor Java sources: [app/src/ testMyflavor/java] Java-style resources: [app/src/ testMyflavor/resources] testMyflavorDebug ----------------- Compile configuration: testMyflavorDebugCompile build.gradle name: android.sourceSets.testMyflavorDebug Java sources: [app/src/ testMyflavorDebug/java] Java-style resources: [app/src/ testMyflavorDebug/resources] testMyflavorRelease ------------------- Compile configuration: testMyflavorReleaseCompile build.gradle name: android.sourceSets.testMyflavorRelease Java sources: [app/src/ testMyflavorRelease/java] Java-style resources: [app/src/ testMyflavorRelease/resources] testRelease ----------- Compile configuration: testReleaseCompile build.gradle name: android.sourceSets.testRelease Java sources: [app/src/ testRelease/java] Java-style resources: [app/src/ testRelease/resources] BUILD SUCCESSFUL Total time: 1.041 secs
SourceSet的合并、Manifest合并
对于某个具体的编译任务中某一类源文件,会根据一定的优先级与合并规则,对多个SourceSet中的同类源文件进行合并得到。
一般情况下,优先级从高到底分别是:
BuildVariant(src/armFreeappDebug)
BuildType(src/debug)
MultiFlavor(src/armFreeapp)
每个单独的Flavor(src/arm, src/freeapp)
sourceSet.main(src/main)
依赖中的对应文件(JAR、AAR或SubProject中解析出来的源文件)
对于Java类、drawable目录下的图片资源文件、layout等,合并措施一般就是同名文件直接覆盖。
对于values目录下的标签类型资源,合并时会以每个标签为最小单元进行覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <style name ="txt_main" > <item name ="android:textSize" > 12sp</item > </style > <style name ="txt_main" > <item name ="android:textSize" > 14sp</item > <item name ="android:textColor" > @android:color/white</item > </style > <style name ="txt_main" > <item name ="android:textSize" > 12sp</item > </style >
而Manifest文件的合并规则更复杂一些。具体可参考官方文档:
https://developer.android.com/studio/build/manifest-merge.html
Android Gradle Task
Android Gradle环境下,有下面几个常见的Task。
assemble:Gradle内建的编译任务(生成APK / JAR / AAR)
test:Gradle内建的测试任务
lint:Android定义的Lint检查任务
check:Gradle内建的检查任务,依赖lint、test
build:Gradle内建的Build任务,依赖assemble、check
结合ProductFlavor、BuildType、BuildVariant,又会组合成很多Task。
这些Task的依赖关系示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 app:build app:check app:lint app:test app:testMyflavor2DebugUnitTest app:testMyflavor2ReleaseUnitTest app:testMyflavor1DebugUnitTest app:testMyflavor1ReleaseUnitTest app:assemble app:assembleDebug app:assembleMyflavor2Debug app:assembleMyflavor1Debug app:assembleRelease app:assembleMyflavor1Release app:assembleMyflavor2Release
Android Studio/IDEA相关
在Android Studio/IDEA中开发时,会在Build Variants窗口选择指定的Build参数,例如myflavor1Debug。
Sync Project With Gradle Files
当修改了gradle文件等情况,会提示:
Gradle files have changed since last project sync. A project sync may be necessary for the IDE to work properly.
此时点击提示栏中的Sync,会触发Gradle同步操作。刚打开工程,或者手动选择菜单Tools-Android-Sync Project With Gradle Files或工具栏中的同步按钮,也会触发同步操作。
在同步过程中,Gradle会执行很多任务,包括解析并下载所有依赖项,解压AAR、合并SourceSet、生成BuildConfig、R文件(结果会输出到build目录)等,这样Android Studio就能加载所有引用的class、jar文件,对源码进行语法解析,从而代码也可以正常的跳转了。
通过GradleConsole窗口,可以看到同步过程中Gradle所执行的Task,其中最主要的是generateMyflavor1DebugSources
,即生成源码的过程,依赖了prepareMyflavor1DebugDependencies、generateMyflavor1DebugBuildConfig、generateMyflavor1DebugResValues等Task。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 :app:preBuild UP-TO-DATE :app:preMyflavor1DebugBuild UP-TO-DATE :app:checkMyflavor1DebugManifest :app:preMyflavor1ReleaseBuild UP-TO-DATE :app:preMyflavor2DebugBuild UP-TO-DATE :app:preMyflavor2ReleaseBuild UP-TO-DATE :app:prepareComAndroidSupportAnimatedVectorDrawable2400Library :app:prepareComAndroidSupportAppcompatV72400Library :app:prepareComAndroidSupportConstraintConstraintLayout102Library :app:prepareComAndroidSupportSupportV42400Library :app:prepareComAndroidSupportSupportVectorDrawable2400Library :app:prepareComFacebookFrescoDrawee0100Library :app:prepareComFacebookFrescoFbcore0100Library :app:prepareComFacebookFrescoFresco0100Library :app:prepareComFacebookFrescoImagepipeline0100Library :app:prepareComFacebookFrescoImagepipelineBase0100Library :app:prepareMyflavor1DebugDependencies :app:compileMyflavor1DebugAidl :app:compileMyflavor1DebugRenderscript :app:generateMyflavor1DebugBuildConfig :app:generateMyflavor1DebugResValues :app:generateMyflavor1DebugResources :app:mergeMyflavor1DebugResources :app:processMyflavor1DebugManifest :app:processMyflavor1DebugResources :app:generateMyflavor1DebugSources :app:preMyflavor1DebugAndroidTestBuild UP-TO-DATE :app:prepareMyflavor1DebugAndroidTestDependencies :app:compileMyflavor1DebugAndroidTestAidl :app:processMyflavor1DebugAndroidTestManifest :app:compileMyflavor1DebugAndroidTestRenderscript :app:generateMyflavor1DebugAndroidTestBuildConfig :app:generateMyflavor1DebugAndroidTestResValues :app:generateMyflavor1DebugAndroidTestResources :app:mergeMyflavor1DebugAndroidTestResources :app:processMyflavor1DebugAndroidTestResources :app:generateMyflavor1DebugAndroidTestSources :app:mockableAndroidJar UP-TO-DATE :app:preMyflavor1DebugUnitTestBuild UP-TO-DATE :app:prepareMyflavor1DebugUnitTestDependencies BUILD SUCCESSFUL Total time: 6.425 secs
Run
点击三角形箭头时,除了执行assembleMyflavor1Debug,还会执行install(将APK安装至设备)等Task。
在Android Studio的Run/Debug Configuration窗口中,还可以指定点击三角箭头时要执行的Gradle Task。例如可以添加一个clean操作,每次编译前先用clean清理build目录。
AAR多版本发布
Android开发经常会用到AAR,有时候希望AAR能支持发布多个版本,并在不同的情况下依赖不同版本(包括不同的BuildType和ProductFlavor)。例如主工程依赖xxLibrary,希望Debug版本APK依赖Debug版本的AAR,而Release版本APK依赖Release版本的AAR。
在Android中依赖SubProject或AAR时,如果没有特殊配置,AAR的发布和模块依赖默认均为Release版本。
实际尝试主工程依赖子工程,子工程中读取BuildConfig.DEBUG
的值始终是false,修改Android Studio中子模块的BuildVariant配置也没有效果。
1 2 3 4 5 6 dependencies { compile project(':xxx_library' ) compile 'com.xxx:xxx:1.0.5@aar' { transitive = true } }
可用如下方式配置子模块或者独立AAR工程发布所有版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 apply plugin: 'com.android.library' android { publishNonDefault true defaultPublishConfig "falvorARelease" productFlavors { flavorA { } flavorB { } } }
用下面的方式依赖子模块或已经发布的AAR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 dependencies { debugCompile project(path: ':xxx' , configuration: 'flavorADebug' ) releaseCompile project(path: ':xxx' , configuration: 'flavorARelease' ) debugCompile('com.xxx:library:1.0.5:flavorADebug@aar' ) { transitive = true } releaseCompile('com.xxx:library:1.0.5:flavorARelease@aar' ) { transitive = true } }