gradle学习笔记

一、相关名词:ant、maven、ivy、Groovy、DSL

  • Groovy

    简单地说,Groovy 是下一代的java语言,跟java一样,它也运行在 JVM 中。作为跑在JVM中的另一种语言,groovy语法与 Java 语言的语法很相似。同时,Groovy 抛弃了java烦琐的文法。同样的语句,使用groovy能在最大限度上减少你的击键次数——这确实是“懒惰程序员们”的福音。


  • Ant

    Apache Ant是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。


  • maven

    Maven是一个采用纯Java编写的开 源项目管理工具。Maven采用了一种被称之为project object model (POM)概念来管理项目,所有的项目配置信息都被定义在一个叫做POM.xml的文件中,通过该文件,Maven可以管理项目的整个声明周期,包括编 译,构建,测试,发布,报告等等。目前Apache下绝大多数项目都已经采用Maven进行管理。


  • DSL

    所谓领域专用语言(domain specific language / DSL),其基本思想是“求专不求全”,不像通用目的语言那样目标范围涵盖一切软件问题,而是专门针对某一特定问题的计算机语言。在这个时期内,我们希望多种语言在同一个项目上的应用,人们就像现在选择框架一样,根据功能来选择相应的语言。

二、Gradle简介

Gradle是一个优秀的构建系统和构建工具,它允许通过插件创建自定义的构建逻辑。 我们基于Gradle以下的一些特点而选择了它:

  • 采用了Domain Specific Language(DSL语言)来描述和控制构建逻辑。
  • 构建文件基于Groovy,并且允许通过混合声明DSL元素和使用代码来控制DSL元素以控制自定义的构建逻辑。
  • 支持Maven或者Ivy的依赖管理。
  • 非常灵活。允许使用最好的实现,但是不会强制实现的方式。
  • 插件可以提供自己的DSL和API以供构建文件使用。
  • 良好的API工具供IDE集成。

三、Gradle使用

Android的任务比通用的四大任务多了“connectedCheck”和“deviceCheck”,这是想要让项目忽视设备是否连接,正常执行check任务。

  • assemble任务, 汇集所有项目输出
  • check任务,运行所有校验
  • connectedCheck任务,运行所有需要链接设备或模拟器的校验, 并行运行
  • deviceCheck任务,运行调用远程设备的校验,运用于CI Servers
  • build任务,既汇集又校验
  • clean任务,清除所有项目输出

四、Gradle项目结构

构建文件需要一个默认的文件夹结构。Gradle遵循约定优先于配置的概念,在可能的情况尽可能提供合理的默认配置参数。

基本的项目开始于两个名为“source sets”的组件,即main source code和test code。它们分别位于:

  • src/main/
  • src/androidTest/

里面每一个存在的文件夹对应相应的源组件。对于Android plugin来说,它还拥有以下特有的文件和文件夹结构:

  • AndroidManifest.xml
  • res/
  • assets/
  • aidl/
  • rs/
  • jni/

注意:src/androidTest/AndroidManifest.xml是不需要的,它会自动被创建。

当默认的项目结构不适用的时候,你可能需要去配置它。根据Gradle文档,重新为Java项目配置sourceSets可以使用以下方法:

sourceSets {
main {
java {
srcDir 'src/java'
}
 
resources {
srcDir 'src/resources'
}
}
}

注意:srcDir将会被添加到指定的已存在的源文件夹中(这在Gradle文档中没有提到,但是实际上确实会这样执行)。

以下是使用调用对象的另一种不同方法:

sourceSets {
main.java.srcDirs = ['src/java']
main.resources.srcDirs = ['src/resources']
}

Android Plugin使用的是类似的语法。但是由于它使用的是自己的sourceSets,这些配置将会被添加在android对象中。
以下是一个示例,它使用了旧项目结构中的main源码,并且将androidTest sourceSet组件重新映射到tests文件夹。

android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
 
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
 
renderscript.srcDirs = ['src']
 
res.srcDirs = ['res']
assets.srcDirs = ['assets']
 
}
 
androidTest.setRoot('tests')
}
}

注意:由于旧的项目结构将所有的源文件(java,aidl,renderscripthe和java资源文件)都放在同一个目录里面,所以我们需要将这些sourceSet组件重新映射到src目录下。
注意:setRoot()方法将移动整个组件(包括它的子文件夹)到一个新的文件夹。示例中将会移动scr/androidTest/到tests/下。
以上这些是Android特有的,如果配置在Java的sourceSets里面将不会有作用。

以上也是将旧构建系统项目迁移到新构建系统需要做的迁移工作。

关于Android Studio与Eclipse的Gradle兼容性

由于eclipse使用ant工具编译,导致eclipse与Android Studio目录结构的不同,因此从eclipse中Generate Gradle build files导出时,为了兼容 Eclipse 的文件结构, Gradle 对资源文件目录、代码文件目录等目录进行了设置,与Android Studio生成的项目有一些不同。

默认的,导出项目中没有 {@projectName}/settings.gradle 文件,而且 {@projectName}/build.gradle 和 {@moduleName}/build.gradle 文件进行了合并。合并后的文件内容如下:

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.5.+'
}
}
apply plugin: 'android-library'
 
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
}
 
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
 
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
 
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('tests')
 
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
}

与默认的Android Studio的build.grade相比,sourseSets算是最大的区别了,sourseSets目录用于设置文件目录。

main 元素表示默认的主干,除了 main 之外,默认的会有 release 和 debug 分支。如果 release 和 debug 分支中有些文件所在的目录不在默认目录同时也不再 main 所设置的目录,你可以在对应的分支中进行设置。

五、Gradle项目文件详解

新建project后,会生成一系列gradle相关的文件。主要涉及到的:/build、/gradle、build.gradle、gradlew、gradlew.bat、settings.gradle等,另外每个Module也会生成各自的相关文件。其中/gradle、gradlew、gradlew.bat不需要处理。

后面部分的讲解主要围绕build.gradle文件进行。

准备知识
Gradle脚本中的参数是DSL规范的,即使用的是类似于Java包名的方式:

a.b.c = 1
 
a {
b {
c 1
}
}

上面两种方式表示的表达的效果相同。

1.build.gradle

最基本的java程序,它的build.gradle文件就一句话:

apply plugin: 'java'

最基本的Android项目,它的build.gradle如下:

buildscript {
repositories {
jcenter()
}
 
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
}
}
 
apply plugin: 'android'
 
allprojects {
repositories {
jcenter()
}
}

我们一步步来分析一下上面三部分的内容。

默认的 project 目录下的 build.gradle 文件内容如上。

  • buildscript :用于设置驱动构建过程的代码。
  • jcenter():声明使用 maven 仓库。在老版本中,此处为 mavenCentral()。
    • mavenCentral() :表示依赖从 Central Maven 2 仓库中获取。
    • jcenter() :表示依赖从 Bintary's JCenter Maven 仓库中获取。
    • mavenLocal() :表示依赖从本地的Maven仓库中获取。
  • dependencies :声明了使用 Android Studio gradle 插件版本。一般升级AS或者导入从Eclipse中生成的项目时需要修改下面gradle版本。具体的版本对应关系,请点击
  • allprojects:设置每一个 module 的构建过程。在此例中,设置了每一个 module 使用 maven 仓库依赖。

2.settings.gradle

默认内容为:

include ':app'

可有可能默认情况下, project 目录下的 settings.gradle 文件不存在,你可以自己创建。
如果你的一个 module 并不是 project 根目录下,你可以这么设置:

include ':app2'
project(':app2').projectDir = new File('path/to/app2')

3./gradle/wrapper/gradle-wrapper.properties

这个配置文件中配置了 Gradle 构建当前项目时使用的 Gradle 版本。我们需要注意的就是 Android Studio 、Gradle 、 Gradle Plugin 这3个的版本需要对应起来,具体的对应关系请点此

我们可以使用/gradlew.bat 在命令行中进行 Gradle 任务。但是,建议使用 Android Studio 的 Gradle 功能面板进行 Gradle 任务

4.(ModuleName)/build.gradle

默认内容如下:

apply plugin: 'com.android.application'
 
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
 
defaultConfig {
applicationId "cc.bb.aa.myapplication"
minSdkVersion 10
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
 
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
}

内容解析:

  • apply plugin: 'com.android.application':
    表示使用 com.android.application 插件。也就是表示,这是一个 android application module 。
    注:如果Module本身是一个依赖库,那么此时的 apply plugin 为 'com.android.library'
    相应的,若是一个Java project,apply plugin 为 'java'。

  • android
    配置所有 android 构建过程需要的参数。

  • defaultConfig
    Android 项目默认设置。

  • buildTypes:
    编译类型。默认有两个: release 和 debug 。我们可以在此处添加自己的 buildTypes ,可在 Build Variants 面板看到。

    • minifyEnabled
      是否使用混淆。在老版本中为 runProguard ,新版本之所换名称,是因为新版本支持去掉没使用到的资源文件,而 runProguard 这个名称已不合适了。

    • proguardFiles
      使用的混淆文件,可以使用多个混淆文件。此例中,使用了 SDK 中的 proguard-android.txt 文件以及当前 module目录下的 proguard-rules.pro 文件。

  • dependencies
    用于配制引用的依赖。

    • compile fileTree(dir: 'libs', include: ['*.jar']):
      引用当前 module 目录下的 libs 文件夹中的所有 .jar 文件。

    • compile 'com.android.support:appcompat-v7:21.0.3'
      引用 21.0.3版本的 appcompat-v7 (也就是常用的 v7 library 项目)。

      在 Eclipse 中,使用 android support ,需要在 SDK 中下载 Android Support Library 。在 Android Studio中,使用 android support ,需要在 SDK 中下载 Android Support Repository ,且项目中使用的版本不能大于 SDK 中的版本。

六、项目构建

Build Task
Gradle中用Task来代表一系列操作,有点类似于Makefile中的Target。常见的Tasks有:

  • assemble
  • build
  • check
  • clean

根据名字基本就能知道Task作用,其中build = assemble + check。
执行task直接就是在命令行执行gradle [task],例如: gradle assemble

(待续。)

七、Gradle依赖(含:.jar,Library project和Maven依赖)

以如下app/build.gradle为例:

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
compile project(':library')
compile 'com.nineoldandroids:library:2.4.0'
}
1.Local packages(本地包)
compile fileTree(dir: 'libs', include: ['*.jar'])

引用libs目录下的.jar文件。若要指定jar包,可写成这样:

compile files('libs/xx.jar')
2.Remote artifacts(远程文件)
compile 'com.android.support:appcompat-v7:21.0.3'

引用 21.0.3 版本的 appcompat-v7 。

在 Android Studio中,使用 android support ,需要在 SDK 中下载 Android Support Repository ,且项目中使用的版本不能大于 SDK 中的版本。当你的 SDK 中已经下载指定版本的 Android Support Repository ,即使没有联网,你也是可以在 Android Studio 中依赖对应的文件。

compile 'com.nineoldandroids:library:2.4.0'

引用 2.4.0 版本的 NineOldAndroids 。需要联网下载。

3.Library Project(库项目)
compile project(':library')

引用名称为 library 的 module 。需要注意的是,被引用的 module 需要在 {@projectName}/settings.gradle 文件中进行注册

3.1.建立库项目

一个库项目与通常的Android项目非常类似,只是有一点小区别。尽管构建库项目与普通Android项目相比使用了不同的plugin,但是在内部这些plugin共享了大部分相同的代码,并且它们都由相同的com.android.tools.build.gradle.jar提供。

apply plugin: 'com.android.library'
 
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
 
defaultConfig {
minSdkVersion 15
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
 
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
}

可以发现,库项目与普通项目仅仅是app plugin不同而已,其他完全相同。

3.2.库项目与普通项目区别

一个库项目的main输出是一个.aar包(即Android的归档文件)。它组合了编译代码(如.jar包或.so文件)和资源(manifest,res,assets)。

一个库项目同样也可以独立于应用程序生成一个测试用的apk来测试。

标识Task同样适用于库项目(assembleDebug,assembleRelease),因此在命令行上与构建一个项目没有什么不同。

其余的部分,库项目与应用程序项目一样。它们都拥有build type和product flavor,也可以生成多个aar版本。
记住大部分Build Type的配置不适用于库项目。但是你可以根据库项目是否被其它项目使用或者是否用来测试来使用自定义的sourceSet改变库项目的内容。

3.3引用一个库项目
dependencies {
compile project(':libraries:lib1')
compile project(':libraries:lib2')
}

注意:如果你要引用多个库,那么排序将非常重要。这类似于旧构建系统里面的project.properties文件中的依赖排序。


5.更多用法
dependencies {
// 引入 jar 包。
// 引用某一个特定的jar。
compile files('libs/xx.jar')
// 引用libs文件夹下除xx.jar以外所有的jar。
compile fileTree(dir: 'libs', include: ['*.jar'], exclude: ['xx.jar'])
 
// so包在0.8版本的Android Studio中的目录更改为@{ModuleName}/src/main/jniLibs。且可以不用在此处配置so了。
 
// 从 maven 库中引入。
//compile 'com.github.chrisbanes.actionbarpulltorefresh:extra-abc:0.9.2'
 
// 引用 lib 工程。
compile project(':moduleName')
 
// 引用users-library。users-library作用是,在编译时使用,但是jar不会打包到apk中,由Android或Android上安装的服务提供需要的内容。
// 使用场景:
// 1. 使用Android的framework-classes.jar中的一些隐藏的API。
// 2. Google的服务框架或者其他服务框架。需要在AndroidMainFest.xml中配合uses-library使用。
provided files('libs/xx.jar')
provided 'aaa:bbb:x.x.x'
 
// 在测试环境下引用依赖。
// 引用jar文件。
androidTestCompile files('libs/xx.jar')
// 引用Maven。
androidTestCompile 'junit:junit:4.11'
 
// 在baidu productFlavors分支下引用依赖。
// 引用jar文件。
baiduCompile files('libs/xx.jar')
// 引用Maven。
baiduCompile 'aaa:bbb:x.x.x'
 
// 在release buildTypes分支下引用依赖。
// 引用jar文件。
releaseCompile files('libs/xx.jar')
// 引用Maven。
releaseCompile 'aaa:bbb:x.x.x'
}

七.APK打包签名、混淆与Product Favor

1.签名:

对一个应用程序签名需要以下:

  • 一个Keystory
  • 一个keystory密码
  • 一个key的别名
  • 一个key的密码
  • 存储类型

位置,键名,两个密码,还有存储类型一起形成了签名配置。

如下是一个签名的示例:

android {
signingConfigs {
release {
storeFile file('keystore')
storePassword 'helloworld'
keyAlias 'Android Release Key'
keyPassword 'helloworld'
}
}
 
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
 
signingConfig signingConfigs.release
}
debug {
applicationIdSuffix ".debug"
}
}
}

关于签名的配置项都在signingConfigs中,在buildTypes中引用即可。

debug构建类型会自动使用debug签名配置。

默认情况下,debug被配置成使用一个debug keystory。debug keystory使用了默认的密码和默认key及默认的key密码。
debug keystory的位置在$HOME/.android/debug.keystrore,如果对应位置不存在这个文件将会自动创建一个。

注意:只有默认路径下的debug keystory不存在时会被自动创建。更改debug keystory的路径并不会自动在新路径下创建debug keystory。如果创建一个新的不同名字的SignConfig,但是使用默认的debug keystore路径来创建一个非默认的名字的SigningConing,那么还是会在默认路径下创建debug keystory。换句话说,会不会自动创建是根据keystory的路径来判断,而不是配置的名称。

注意:如果你将这些文件添加到版本控制工具中,你可能不希望将密码直接写到这些文件中。下面Stack Overflow链接提供从控制台或者环境变量中获取密码的方法:how-to-create-a-release-signed-apk-file-using-gradle

2.混淆

我们回头再看默认的app/build.gradle文件中的buildType的内容:

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

这里release的内容中:

  • minifyEnabled
    是否使用混淆。在老版本中为 runProguard ,新版本之所换名称,是因为新版本支持去掉没使用到的资源文件,而 runProguard 这个名称已不合适了。

  • proguardFiles
    使用的混淆文件,可以使用多个混淆文件。此例中,使用了 SDK 中的 proguard-android.txt 文件以及当前 module目录下的 proguard-rules.pro 文件。目录为sdk\tools\proguard

这里的两个文件可用getDefaultProguardFile()可以获取这些文件的完整路径。它们除了是否要进行优化之外,其它都是相同的。

3.Product flavors

如果你一个项目想要生成不同的APK包,有不同的包名,或者不同的资源,那么这就是使用Product flavors的时候了。

android {
....
 
productFlavors {
flavor1 {
packageName "com.example.flavor1"
versionCode 20
}
 
flavor2 {
packageName "com.example.flavor2"
minSdkVersion 14
}
}
}

上面的示例会生成两个不同的APK包,有着不同的包名以及属性。

没有更多推荐了,返回首页