很久没有关注Android的最新动态,最近帮朋友做一个Android项目,才又重操旧业。发现Google官方已经全面从ADT切到了Android Studio,自然的,Gradle成了官方的编译工具。其实很早就在用Gradle了,在这里做一个总结,祭奠我逝去的Android生涯(真的逝去了吗)。

Gradle的基本使用

当前推荐版本和安卓插件版本

如果是新建的项目,推荐使用最新版本;如果是接手已有的项目,那就保持现有的,升级可能会有一些小麻烦,这个不多说。

使用gradle模板创建安卓项目

下面是用android sdk创建一个用gradle模板的安卓项目(一般情况下用IDE就可以了):

1
android create project -n test -t android-19 -p test -k com.test -a Test -g -v 0.12.0

下图是建好的目录结构:build.gradle就是构建配置脚本;gradlew和gradle目录是一个wrapper,用来在没有gradle的环境下来build时,代替gradle命令,直接可以使用./gradlew

file_list

而其他目录结构也和用ant或者maven创建的略有不同,比如AndroidManifest的位置等等,在下面映射目录一节具体介绍。

最简单的build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
buildscript {
repositories {
//使用maven的默认repo,也可以加第三方的repo
mavenCentral()
}
dependencies {
//指定特定版本,也可以是0.12.+表示0.12.0以上的版本。
classpath 'com.android.tools.build:gradle:0.12.0'
}
}
apply plugin: 'android' //不需要再apply java的插件

android {
//这里的sdk和buildtools的版本号,需要精确指定,感觉比较弱,后面有可以自动配置的办法
compileSdkVersion 'android-19'
buildToolsVersion '20.0.0'

buildTypes {
release {
runProguard false
proguardFile getDefaultProguardFile('proguard-android.txt')
}
}
}

gradle build后就生成build目录,build/outputs/apk是生成的apk,而.gradle目录是一个cache目录,这2个目录都建议加入到gitignore里面。

映射目录 sourceSets

比较传统的目录结构是AndroidManifest.xml, src, res, libs, assets目录都是在项目根目录的,下面的配置就可以指定不同的目录的具体位置。可以使用安卓的目录结构,而不是更深的java风格的项目目录层级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
...

sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
aidl.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
...
}

自动检测SDK和build tools版本

gradle本身是可以编程的语言,所以很多事情可以通过编写函数来增强。上文提到手写sdk和buildtools的版本是很烦的一件事情,下面的函数就可以自动检测本地环境。具体的实现见下面链接的gist

1
2
3
4
5
6
android {
...
compileSdkVersion highestSdkAvailable(20)
buildToolsVersion latestBuildToolsAvailable("20.0.0")
...
}

配置签名 signingConfigs

配置release包所有的签名,里面的参数要用真正的值换掉

1
2
3
4
5
6
7
8
9
10
11
12
android {
...
signingConfigs {
release {
storeFile file("/your.keydir/your.keystore")
storePassword "your.password"
keyAlias "your.key.alias"
keyPassword "your.alias.passord"
}
}
...
}

配置 buildTypes

buildTypes其实就是不同的任务类型,对应安卓来说,最常见的是下面的release,beta,debug三种类型。

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
android {
...
buildTypes {

release {
proguardFile 'proguard-project.txt'
signingConfig signingConfigs.release //使用release签名
jniDebugBuild false
debuggable false //关闭debug模式
runProguard true //混淆
zipAlign true
}

beta {
proguardFile 'proguard-project.txt'
signingConfig signingConfigs.release //同样用release签名
jniDebugBuild false
debuggable true
runProguard false
zipAlign true
}

debug {
applicationIdSuffix ".debug" //包名加debug后缀
jniDebugBuild true
debuggable true
zipAlign true
}
...
}

上面release包和beta包,同样使用release签名,是方便测试,具体可以根据实际情况来调整混淆及其他开关。debug包加后缀的目的,是可以同时安装debug和release的包,也是为了方便调试的。

通过上面的配置,就有了一个基本的安卓项目的构建脚本了。运行gradle build就可以打出来全部的包了。

Gralde依赖管理

gradle真正强大的地方不在于比较人性化简明的语法,而是在方便灵活的依赖管理。

配置repo和flatDir

下面的repositories需要在build.gradle的顶级节点配置,不能写在android或buildscript里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
repositories {
//maven的repo,也就是http://repo1.maven.org/maven2
mavenCentral() //所以可以直接使用maven的众多jar,aar包
//本地的maven repo,比较少用
mavenLocal()
//第三方的maven repo
maven {
url "http://mente.github.io/facebook-api-android-aar"
}
//使用本地文件夹作为repo
flatDir {
dirs 'aars'
}
}

配置远程依赖和本地依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dependencies {
//support包
compile 'com.android.support:support-v4:20.0.0'
//aar
compile ('fr.avianey:facebook-android-api:3.16.0@aar') {
transitive = true
}
//jar
compile 'com.jakewharton:butterknife:5.1.2'
//本地libs目录里的jar
compile files('libs/umeng_sdk.jar')
//暴力引入lib目录里的所有jar包,不推荐
compile fileTree(dir: 'libs', include: ['*.jar'])
}

模块项目依赖 settings.gradle

因为现在的安卓项目越来越复杂,所以模块化是趋势,而且依赖的库有时候是开源的,也涉及到二次开发的需求。settings.gradle的模式就能很好的支撑模块化的复杂依赖构建的场景。

首先,在项目根目录下编写build.gradle, settings.gradle文件,而其他模块项目,主项目,都是二级目录。

1
2
3
4
5
6
android-demo:
|__________ app //应用主项目
|__________ app-lib //依赖的库项目
|__________ facebook-sdk //第三方sdk代码,比如facebook
|__________ build.gradle
|__________ setings.gradle

根目录的build.gradle可以配置所有模块都有的基本配置,如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.+'
}
}

allprojects {
repositories {
mavenCentral()
}
tasks.withType(Compile) {
options.encoding = 'UTF-8'
}
}

根目录的settings.gradle是配置build时都包含那些模块

1
2
3
include ':facebook-sdk'
include ':app-lib'
include ':app'

然后就是在每个模块的子目录里面去配置各自的build.gradle就好了,但是要添加依赖的模块。比如上面app目录的build.gradle就得添加相应的依赖。

1
2
3
4
5
6
dependencies {
...
compile project(':facebook')
compile project(':app-lib')
...
}

Gralde小技巧

集成ndk-build

在目录映射那里设置了jniLibs.srcDirs = [‘libs’]的话,默认里面的so会自动打包到apk里。但是如果想在gradle build时,重新编译ndk的代码的话,就需要加入下面的配置

1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.tools.ant.taskdefs.condition.Os

task ndkBuild(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine 'ndk-build.cmd', '-j'
} else {
commandLine 'ndk-build', '-j'
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}

忽略lint的警告 lintOptions

1
2
3
4
5
6
android {
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}

重命名生成的apk

1
2
3
4
5
6
7
android {
defaultConfig {
applicationVariants.all {
variant -> appendVersionNameVersionCode(variant)
}
}
}

productFlavors和渠道包

productFlavors其实是另外一个维度,和buildTypes是相乘的关系。我们可以利用这点来打安卓的渠道包。

1
2
3
4
5
6
7
8
9
android {
productFlavors {
googleplay{
}
wandoujia{
}
...
}
}

但是其实无论是umeng还是GA,flurry等渠道包,有更加节能高效的打渠道包方案: 利用aapt add的方式,注入assets文件,达到改变渠道的目的。(比flavors和基于apktool的方案都要完美,当然还有直接用apk末尾增加信息的方式,也是不错的选择,这里不做展开了。)

发布组件

很多公司都自己利用Sonatype Nexus项目搭建了自己的maven库,那么一些模块库,也是有打包后上传到maven企业源的需求的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apply plugin: 'maven'

ext.pomCfg = {
name 'app-lib'
description 'project_desc'
...
}

uploadArchives.repositories.mavenDeployer {
repository(url: 'repo_url') {
authentication(
userName: repo_user,
password: repo_passwd)
...
}
pom.project pomCfg
}

常用命令参数

1
2
3
4
5
6
7
8
gradle --help
gradle tasks //列出task列表
gradle asD (gradle assembleDebug) //编译debug打包
gradle asR (gradle assembleRelease) //编译release打包
gradle asD --refresh-dependencies //强制刷新依赖
gradle asD --parallel //并行编译
gradle asD --parallel-threads 3
gradle clean