稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

理解Android Gradle构建系统

Gradle是一个新型的强大的构建系统。Android很早就开始支持Gradle了,到现在已经完全切换到Gradle构建。它的优势也是比较明显的,更强大的配置,方便的依赖管理,简洁的语法(Groovy DSL),跟啰嗦的XML相对,这是很大的进步。

基本概念

为了方便讨论和理解,需要对一些概念进行定义:

  • 项目:可以理解为一个安卓应用。或者说一个可构建的所有配置,代码和资源的集合。
  • 模块:最小的可构建单元 项目可以只有一个模块,或者说一个模块也可以作为一个项目。项目通常都由一个或者几个,甚至多个模块组成。每一个模块都要有相应的build.gradle来描述它是如何构建的。项目根目录下面也会有一个build.gradle,用于对应用于整个项目的配置,比如依赖库的repositories,Android Gradle Plugin的版本指定。

项目结构

典型的项目结构是酱紫的:

1
2
3
4
5
6
7
8
9
10
|-project
| |-app
| | |-build.gradle
| |-library1
| | |-build.gradle
| |-build.gradle
| |-settings.gradle
| |-gradle/wrapper/gradle-wrapper.properties
| |-local.properties
| |-gradle.properties
  • gradle.properties 是用来配置gradle本身的运行的参数,比如是否并行,Gradle的JVM参数,因为Gradle也是基于JVM的。
  • local.properties是用于配置本机操作环境的安卓参数,这里就二个,一个是sdk.dir就是配置安卓SDK的本机路径;另一个就是ndk.dir是配置NDK的本机路径,当然,如果不需要在构建时编译JNI代码,是不用配置ndk.dir的
  • gradle/wrapper/gradle-wrapper.properties是用于配置Gradle本身的版本信息,比如使用哪个版本的Gradle
  • build.gradle是整个构建系统的核心文件,类似make的Makefile以及maven的pom.xml,里面描述如何构建一个目标(应用,或者库)。这个文件 是我们修改最多的
  • settings.gradle是项目结构的配置文件,它指定了项目中包含了哪些模块,比如上面这个例子,settings.gradle应该这样写:
 include ':app', ':library1'

当然,这是典型的项目结构,但是没有完全这样,因为Gradle的配置是相当灵活且强大的,也就是说真实的文件夹结构,跟Gradle中的项目结构是没有关系的,只不过默认的情况下(也即没有特别的指定和定制)模块的名字就是子目录的名字,项目的结构就按文件夹结构来确定的。

但是可以不用这样,下面详细说

配置非文件夹结构的项目

意思就是每一个模块,不一定非在项目所在文件夹里,换句话说,你可以在项目中引用随便在哪里的模块,举个例子:

你的项目project是在,Documents/project/,想引用一个库是在Downloads/library/,不必非把library拷贝到project下面,只需要在project/settings.gradle中配置一下就可以了:

1
2
3
include ':app', ':library'

project(':library').projectDir = new File(settingsDir, '../../../Downloads/library')

这样就可能在项目project中引入了模块library。

分清楚二个版本

  • gradle的版本,在./gradle/wrapper/gradle-wrapper.properties中通过distributionUrl来指定

Gradle是一个构建系统,像make和cmake,ant和maven一样。安卓的项目里推荐使用gradle wrapper,实际上就是在项目中指定gradle的版本,这样有一个好处就是,这个项目换了操作系统环境,也不受影响。我们都知道软件的不同的版本对软件的使用是有影响的。所以如果像make或者cmake一样,使用操作系统环境中的gradle,那么同一个项目会因为不同的环境而带来一些诡异的配置相关的问题。

Gradle跟Android或者Google没有关系,它是由Gradle Inc.在开发和维护的。所以Gradle的升级要到gradle.org

  • Android Gradle Plugin的版本,通过buildScript DSL中的dependencies DSL来指定
1
2
3
4
5
6
7
8
9
10
buildscript {
  repositories {
    mavenLocal()
    mavenCentral()
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.2.2'
  }
}

Gradle是一个构建系统,有它自身的规则,可以用来构建任何语言的任何项目。但每一个语言和平台都有自己的特性,如果直接使用Gradle也不是不可以,但是要做很多重复性的工作。Gradle是有插件机制的,也就是每个语言或者平台实现一些Gradle插件,以让开发者更加方便来构建,而不从头一条指令一条的写。Android Gradle Plugin就是专门用来构建安卓应用开发相关的插件。这个是由Google开发并维护,与SDK一起发布的。

常见命令

除了在Android Studio中直接操作以外,gradle是支持命令行的。而且命令行有时候会更方便。执行的方式是./gradlew 。gradlew实际上就是一个Shell脚本,它的作用是运行gradle/wrapper/中的Gradle二进制文件。task(任务)是Gradle构建中的可执行单元,与makefile中的target有些类似,可以理解为一个应用的构建,就是执行一些定义好的task。常见的task有:

  • assemble - 构建项目的所有目标输出
  • assembleDebug - 只构建调试环境下的输出
  • assembRelease - 构建发布环境下的输出
  • uninstallDebug/uninstallRelease - 从设备上卸载调试的应用,或者卸载发布环境的应用
  • clean - 清理编译过程中产生的输出,包括最终输出。通常就是把build目录删除

可以用gradle tasks来查看支持的所有任务。

Dependency managament

在每个模块的build.gradle中dependencies {} DSL中定义该模块的依赖树。

1
2
3
4
5
6
7
8
9
10
11
12
dependencies {
  // 本地lib的依赖
  compile fileTree(dir: 'libs', include: ['*.jar'])

  // 对其他模块的依赖
  compile (project(':library'))

  // 从远程的repo中下载依赖
  compile "com.android.support:support-v4:23.1.0"
  compile 'com.android.support:appcompat-v7:23.0.0'
  compile 'com.squareup.picasso:picasso:2.5.2'
}

有几种不的指定编译方式:

  • compile - 把所需要的依赖会编译进目标,类似于C++中的静态库的概念
  • provided - 只在编译时依赖一下,不会打于目标中,运行时环境应该提供相同的库,否则会找不到依赖,类似于动态库的概念
  • compileProvided

常见的tricks

  • gradle.org访问缓慢或者根本无法访问

./gradle/wrapper/gradle-wrapper.properties中的distributionUrl=https://services.gradle.org/distributions/gradle-2.14.1-all.zip,这个地址下载十分缓慢,或者根本无法下载,导致gradle sync花费很长时间,甚至卡死无法完成。改成国内的镜像会好很多distributionUrl=http://mirrors.taobao.net/mirror/gradle/gradle-2.14.1-all.zip

如果没有可用的镜像网站,还有一个解决办法就是,去其他网站下载Gradle的二进制文件,然后解压放到~/.gradle/wrapper/dists/。(如果是windows的话,应该是在C:\Users\.gradle\wrapper\dists)。

  • transitive false
1
2
3
4
5
dependencies {
  compile ('com.squareup.picasso:picasso:2.5.2') {
    transitive false
  }
}

依赖是一个树状结构,比如项目中依赖了rxlifecircle,但rxlifecircle本身也有依赖,解析依赖时,会把所有依赖以及依赖的依赖都下载下来,这就构成了依赖树,当然,也会涉及冲突的处理,比如二个库都依赖了另外一个库,但是不同的版本,这个Gradle本身是有策略的。

可以用./gradlew -q dependencies –configuration compile命令来查看依赖树 * 指定依赖的具体类型@aar or @jar

1
2
3
4
5
dependencies {
  compile "com.android.support:support-v4:23.1.0"
  compile 'com.android.support:appcompat-v7:23.0.0'
  compile 'com.squareup.picasso:picasso:2.5.2@jar'
}

默认情况,Gradle认为依赖库都是Java 的jar类型。也就说当其在repo中寻找依赖时会去找jar。所以,如果是aar的依赖库,就需要指定其具体类型,通过在版本号后面加上@aar或者@jar来指定其具体的类型。

  • 在Android Studio中没问题,但是运行命令行时,会报错误:

FAILURE: Build failed with an exception.

* What went wrong: A problem occurred configuring root project ‘pailitao-sdk’. Could not resolve all dependencies for configuration ‘:classpath’.

> Could not find com.android.tools.build:gradle:2.2.2.

Searched in the following locations: file:/Users/alexhilton/.m2/repository/com/android/tools/build/gradle/2.2.2/gradle-2.2.2.pom file:/Users/alexhilton/.m2/repository/com/android/tools/build/gradle/2.2.2/gradle-2.2.2.jar https://repo1.maven.org/maven2/com/android/tools/build/gradle/2.2.2/gradle-2.2.2.pom https://repo1.maven.org/maven2/com/android/tools/build/gradle/2.2.2/gradle-2.2.2.jar Required by: :pailitao-sdk:unspecified

* Try:

Run with –stacktrace option to get the stack trace. Run with –info or –debug option to get more log output.

这个问题的根本原因是Android Gradle Plugin没有找到。查看要目录的build.gradle,在repositories里面加上jcenter(),或者把mavenCenteral()改成jcenter()就可以解决了。

  • 如何直接构建子项目,比如project下面有app和library二个子项目:
 |-project
 | |-app
 | |-library

在根目录执行命令./gradlew assembleDebug或者assembleRelease,会构建整个应用。假如只想构建library呢?可以执行这样的命令:./gradlew :library:assembleRelease也即,前面是子项目,后面跟命令。

  • Gradle refresh failed: com.android.build.gradle.internal.model.defaultandroidproject unsupported major.minor version 51.0

这个错误的原因是Android Studio运行的JVM版本太低了,看一下Android Studio的关于,里面会有JVM的版本信息,如果是1.6就会上面的错误。如果是Mac,到app/info.list里面把JVM的版本改成1.7+

  • Gradle sync failed: “java.lang.OutOfMemoryError: GC overhead limit exceeded”

明显是Gradle运行时,内存爆了。解决方法就是加大它的内存配额。在相应的build.gradle中加入:

1
2
3
4
dexOptions {
  incremental true
  javaMaxHeapSize 4g
}
  • Error:(2, 0) No service of type Factory available in ProjectScopeServices.

查看根目录的build.gradle通常都指定有github的插件,把1.3升级到1.4.1就可以解决,参考这篇文章

  • 解决默认情况下只编译子模块的release模式

默认情况下,当有compile project(‘:library’)形式的依赖时,只会编译library的release模式,这对调试是非常不方便的,因为release模式通常会开启混淆,这时可以在子模块中加入默认的打包配置来强制默认打debug包:

1
2
3
android {
  defaultPublishConig 'debug'
}

进阶之Gradle

对于日常的开发,了解基本的就够用了,但若想做一些额外的事情,比如自定义一些task等就需要深入研究Gradle本身了。这个建议直接看Gradle的文档比较好。

进阶之Groovy

Gradle的文件是用Groovy语言来描述的,所以如果想要实现高级功能,也必须熟悉Groovy语言。同样还是要参考相应的文档。

参考资料

Comments