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

Gradle的基本使用

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

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

使用gradle模板创建安卓项目

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

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

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风格的项目目录层级。

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

android {
    ...
    compileSdkVersion highestSdkAvailable(20)
    buildToolsVersion latestBuildToolsAvailable("20.0.0")
    ...
}

配置签名 signingConfigs

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

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三种类型。

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里面。

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'
    }
} 

配置远程依赖和本地依赖

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文件,而其他模块项目,主项目,都是二级目录。

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

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

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时都包含那些模块

include ':facebook-sdk'
include ':app-lib'
include ':app'

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

dependencies {
    ...
    compile project(':facebook')
    compile project(':app-lib')
    ...
}

Gralde小技巧

集成ndk-build

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

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

android {
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }
}

重命名生成的apk

android {
    defaultConfig {
        applicationVariants.all {
            variant -> appendVersionNameVersionCode(variant)
        }
    }
}

productFlavors和渠道包

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

android {
    productFlavors {
        googleplay{
        }
        wandoujia{
        }
        ...
    }
}

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

发布组件

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

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
}

常用命令参数

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