依赖管理是Gradle的一个亮点。在最好的情况下,你只需要在构建文件中添加一行代码,Gradle就可以从远程仓库下载依赖并确保可用。当你所需的依赖还有它自己的依赖时,Gradle会自动接管这些事情。依赖的依赖称为传递依赖(transitive dependencies
)。
本章将介绍依赖管理的概念,并通过多种方式在Android工程中引入依赖。主要内容有:
- 仓库
- 本地依赖
- 依赖的几个概念
仓库
我们所说的依赖通常是指外部依赖,比如其他开发者提供的库。手动管理依赖是非常困难的事情。你需要找到这个库,然后下载JAR包,并拷贝到工程中,最后再引用它。一般这些JAR包的名称中并不包含版本信息,所以你需要自己添加,以便日后升级。你还需要确保这些库保存在一个源管理系统中,这样其他团队成员才可以直接引用基础代码,而不需要手动去下载依赖。
使用仓库可以解决这些问题。一个仓库可以看做是一个文件集。Gradle默认不会为你的工程定义任何仓库,所以你需要自己在repositories
块中添加。如果你使用的是Android Studio,它会帮你做这些。我们在前面的章节简单提到了repositories
块,如下:
repositories {
jcenter()
}
Gradle支持三种不同的仓库:Maven、Ivy和静态文件或者目录。在构建的execution期间,Gradle会从仓库中获取这些依赖。Gradle同样会保存一个本地的缓存,这样同一个版本的依赖只会下载一次。
一个依赖由三个元素定义:
- group指明了创建这个仓库的组织,通常是域名的翻转。
- name是仓库的唯一标识
- version指定仓库的版本
基于这三个元素,一个依赖可以在dependencies
块中这样声明:
dependencies {
compile 'com.google.code.gson:gson:2.3'
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
这是Groovy中map的一种简写,全写如下:
dependencies {
compile group: 'com.google.code.gson', name: 'gson', version:'2.3'
compile group: 'com.squareup.retrofit', name: 'retrofit', version: '1.9.0'
}
一个依赖的必需元素是name,group和version是可选的。尽管如此,我们仍建议添加上group以使依赖变得清晰,添加上version,这样可以确保依赖不会自动更新,否则会打断构建。
预置仓库
为了方便,Gradle预置了三个Maven仓库:JCenter,Maven Central和本地Maven仓库。可以添加如下代码来引入它们:
repositories {
mavenCentral()
jcenter()
mavenLocal()
}
Maven Central和JCenter是两个广泛使用的在线仓库。你不必同时使用它们,我们推荐使用JCenter,这也是Android Studio创建的Android工程的默认仓库。JCenter是Maven Central的父集,所以当你做开关的时候,你可原封不动的保留已定义的依赖项。在此之上,它还支持HTTPS,这和Maven Central不同。
本地Maven仓库是一个你所使用的所有依赖的本地缓存,你也可以添加你自己的依赖。默认情况下,这个仓库在home目录的.m2
文件夹下。在Linux或者Mac OS X中,路径为~/.m2
,Windows中为%UserProfile%\.m2
。
除了预置的仓库,你也可以添加其他公有的或者私有的仓库。
远程仓库
有些组织创建有趣的插件或者仓库,并且倾向于将它们部署在自己的Maven或者Ivy服务器上。为了将它们添加到你的构建中,你只需要在maven
块中添加URL地址:
repositories {
maven {
url "http://repo.acmecorp.com/maven2"
}
}
这同样适用于Ivy仓库。Apache Ivy在Ant中是一个流行的依赖管理者。Gradle以与Maven库相同的格式支持这些仓库。
repositories {
ivy {
url "http://repo.acmecorp.com/repo"
}
}
如果你的组织有自己的仓库,需要有证书才可以访问,可以这样写:
repositories {
maven {
url "http://repo.acmecorp.com/maven2"
credentials {
username 'user'
password 'secretpassword'
}
}
}
Maven和Ivy使用同样的方式。
保存证书
在构建文件中保存证书不是一个好主意。构建文件是纯文本的,并被检入源控制系统。更好的方式是使用单独的Gradle properties文件来保存,就像我们在第二章看到的那样。
本地仓库
在你的硬盘或者网盘上运行Maven或者Ivy仓库是可行的。为将其添加到你的构建中,你只需要配置一个相对或者绝对的URL地址:
repositories {
maven {
url "../repo"
}
}
新建的Android工程,默认依赖Android Support Library。在使用SDK Manager安装Google仓库时,会在你的硬盘上创建两个Maven仓库:ANDROID_SDK/extras/google/m2repository 和 ANDROID_SDK/extras/android/m2repository。这是Gradle获取Google提供的库的地址,比如:Android Spport Library和Google Play Services。
你也可以使用flatDirs
添加一个常规目录作为仓库。它允许你在dependency
块中从该目录添加文件。
repositories {
flatDir {
dirs 'aars'
}
}
本章的后续内容中,在讲解库工程时, 我们会看一个使用它的例子。
本地依赖
某种情况下,你可能仍需要手动下载Jar包或者native库。或许你想创建你自己的仓库,这样就可以在多个工程中重复使用它,而且不必将他发布到公有或者私有的仓库中。在这种情况下,不可能去使用任何在线的资源,你需要使用其他的方式来添加依赖。我们会讲述如何使用文件依赖,如何包含native库,以及如何在你的工程中引用库工程。
文件依赖
你可以使用files
方法来添加一个Jar文件依赖
dependencies {
compile files('libs/domoarigato.jar')
}
文件过多时,你可以添加文件夹:
dependencies {
compile fileTree('libs')
}
默认情况下,新建的Android工程有一个libs目录,并且被添加为依赖目录。你还可以添加过滤器,只添加Jar包为依赖:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
这意味着在Android Studio创建的任何工程中,你可以在libs
文件夹下放置Jar包,它会被自动包含在编译路径和最终的APK中。
Native库
用C或C++编写的库可以编译成特定平台的native代码。这些库由几个.so
文件组成,每个文件对应一个平台。Android 插件默认支持native库,你只需要在模块中创建jniLibs
目录,并且为每个平台创建相应的子目录就可以了。最后把.so
文件放入对应的子目录。
结构如下:
app
├── AndroidManifest.xml
└── jniLibs
├── armeabi
│ └── nativelib.so
├── armeabi-v7a
│ └── nativelib.so
├── mips
│ └── nativelib.so
└── x86
└── nativelib.so
如果这个规则对你不适用,你可以在构建文件中自定义目录:
android {
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
}
}
库工程
如果你想分享一个使用了Android API或者包含Android资源的库,你需要创建一个库工程。库工程和应用工程大体相同。你可以使用同样的任务来构建和测试库工程,它们同样可以有不同的构建变体。不同的是输出。应用工程会生成一个APK,它可以安装和运行在Android设备上;库工程会生成一个.aar
文件,该文件可以作为Android应用工程的库。
创建和使用库工程模块
在构建脚本中,使用Android库插件来代替Android应用插件:
apply plugin: 'com.android.library'
有两种方式可以在你的应用中包含一个库工程。
- 将其作为模块包含在工程中
- 创建一个
.aar
文件,该文件可以多个应用中重复使用。
第一种方式需要将模块添加到settings.gradle
文件中,并将其作为应用模块的依赖。settings文件如下:
include ':app', ':library'
在这种情况下,库模块被称为library
,相应的会有一个同名的文件夹。为了使用这个库,需要在应用模块的build.gradle
文件将其添加为依赖:
dependencies {
compile project(':library')
}
这样应用模块的类路径将会包含库的输出。我们将在第五章详细讨论这个方法。
使用.arr文件
如果你想创建一个可以在不同Android应用中重复使用的库,你可以构建一个.aar
文件,然后将其作为依赖添加到工程中。构建库时,.aar
文件会在模块目录的build/output/aar/
文件夹下生成。为了使用它,你需要在应用模块中创建一个文件夹,将.aar
文件拷贝其中,然后添加该目录为仓库。
repositories {
flatDir {
dirs 'aars'
}
}
这样每个被放置其中的文件都会被依赖。你可以如下引用这些依赖:
dependencies {
compile(name:'libraryname', ext:'aar')
}
这样Gradle就会添加libraryname.aar
作为依赖。
依赖的几个概念
这里有一些依赖相关的概念,值得了解一下。其中一个是configurations
,它解释了我们在本章用到的compile
关键字。
Configurations
有时我们需要使用一些特定设备提供的SDK,比如某些厂商的蓝牙SDK。为了编译代码,你需要将SDK添加到compile classpath中。但你并不需要将其包含在APK中,因为它已经存在于设备上了。这时候依赖的配置就起作用了。
Gradle将依赖分成各种配置,由文件集命名。下面是Android应用或者库的标准配置:
- compile
- apk
- provided
- testCompile
- androidTestCompile
compile
配置是默认的,包含编译main应用所需的全部依赖。该配置中的依赖不仅包含在类路径中,还包含在生成的APK中。
apk
配置只会包含在APK中,不会包含在类路径中。
provided
与apk
相反。这两种配置只接受JAR依赖项,试图将库工程加入其中会导致错误。
testCompile
和androidTestCompile
会添加测试所需的特定外部库。这两个配置可以添加测试框架,比如JUnit或者Espresso。它们只在运行测试相关任务的时候才会用到。你只想在测试APK中使用这些框架,而不是发布的APK中。
除了这些标准的配置,Android插件还会为每个构建变体创建配置,比如debugCompile,releaseProvided等。如果你只想在debug版本添加一个日志框架,这将会非常有用。你会在第四章学习到更多。
语义版本控制
版本控制是依赖管理很重要的一个方面。添加到仓库(如JCenter)的依赖被认为遵循一组版本控制规则,称为语义版本控制。在语义版本控制下,版本号遵循major.minor.patch
的规则,版本号的增长遵循如下规则:
- major(主版本号)在API有不兼容的修改时增长
- minor(小版本号)以向后兼容的方式添加功能时增长
- patch(补丁)修改bug时增长
动态版本
某些情况下,你希望每次构建应用或者库时都能获得最新的依赖。最好的方式就是使用动态版本。下面是使用动态版本的几种方式:
dependencies {
//获取最新的patch
compile 'com.android.support:support-v4:22.2.+'
//获取最新的小版本号至少是2的小版本
compile 'com.android.support:appcompat-v7:22.2+'
//获取最新版本
compile 'com.android.support:recyclerview-v7:+'
}
你需要慎重使用动态版本。使用动态版本,Gradle可能会选择不稳定的版本,导致构建失败。更严重的是,构建服务器和你的本地机器可能会使用不同版本的依赖,导致应用的行为不一致。
在你使用动态版本时,Android Studio会给出警告。
Inside Android Studio
本节略过
依赖顺序
配置文件中依赖的排列顺序决定了优先级的高低,排在前面的库优先级大于排在后面的库。这个顺序在资源合并和清单文件元素合并的时候非常重要。
比如,如果你的项目声明如下:
- 依赖于A和B,且A在B前面
- A依赖于C和D,C在D前面
- B依赖于C
那么,展开的依赖顺序如下:
- A
- D
- B
- C
这个顺序保证了A和B都可以覆写C;D的优先级高于B,因为A的优先级高于B。