I can believe fly.

Friday, August 14, 2015

Gradle构建Android应用

Gradle构建Android应用

Gradle 介绍

Gradle 基于DSL(领域特定语言)语法的自动化构建工具。其所有task可由Groovy代码控制,因此相对Maven那套标准性的流程或模板来说,Gradle会更加灵活,扩展性更强。

安装

  1. 下载 http://services.gradle.org/distributions/gradle-2.4-all.zip
  2. 解压到安装目录,例如/opt/tools/Gradle
  3. 添加GRADLE_HOME/bin to your PATH environment variable

简单build.gradle

buildscript {
    repositories {
    mavenCentral()
    }

    dependencies {
    classpath 'com.android.tools.build:gradle:1.1.3'
    }
}

apply plugin: 'android'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.0"
}

wrapper

安装完后,在项目的当前目录下执行 gradle init wrapper
如果是在已有maven配置的项目,gradle会自动识别转换,但部分配置还需要自行修改。
负责人使用wrapper初始化环境,开发团员的其它成员就可以不用自己安装了。
自动生成gradlew,build.gradle,settings.gradle等文件和相关目录
$ ./gradlew build

Downloading https://services.gradle.org/distributions/gradle-2.3-bin.zip
建议:在gradle项目里,尽量使用gradlew来编译,它会自动下载对应的版本,这样团队的其他成员就不需要手动安装了且可以保持大家使用的版本一致。

映射目录 sourceSets

如果你的工程是Eclipse 结构,那在build.gradle需要映射目录处理
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')
    }
}

依赖管理

设置从公司私服nexus下载依赖库

repositories {
      maven {
             url "http://127.0.0.1/nexus/content/groups/public"
    }
}

引用本地的aar库

repositories {
    flatDir {
    dirs 'libs'
    }
}

dependencies {
    compile(name:'cards', ext:'aar')
}

依赖jar

dependencies {
     compile group: 'com.duowan.mobile.uauth', name: 'yyauth', version:'1.7'
}
}

依赖so

dependencies {
         classpath 'com.nabilhachicha:android-native-dependencies:0.1+'
}

native_dependencies {
    artifact 'com.duowan.mobile.uauth:yyauth:1.7:armeabi'
    artifact 'com.duowan.mobile.uauth:yyauth:1.7:armeabi-v7a'
}

忽略lint的警告

lintOptions {
    abortOnError false
}

版本号设置

defaultConfig {
    versionCode System.getenv("BUILD_NUMBER") as Integer ?: 0     // 如果环境变量BUILD_NUMBER存在则读取,否则取0
    versionName version                                                                       //  version是gradle.propertise定义的属性
}

设置apk输出名称

方式1:设置archivesBaseName

defaultConfig {
    project.ext.set("archivesBaseName", "myAPP-" + versionName + "-" + versionCode);
}

方式2:获取输出文件名直接替换

defaultConfig {
    applicationVariants.all {
    variant -> changeApkName(variant)
}

def changeApkName(variant) {
    def apk = variant.outputs[0].outputFile
    def newName = ""
    newName = apk.name.replace(project.name, project.name + "-" + android.defaultConfig.versionName + "-" + android.defaultConfig.versionCode)
    if (variant.buildType.name == "release") {
        newName = newName.replace("-" + variant.buildType.name, "")
    }
    variant.outputs[0].outputFile = new File(apk.parentFile, newName)
    if (variant.outputs[0].zipAlign) {
        variant.outputs[0].zipAlign.outputFile = new File(apk.parentFile, newName.replace("-unaligned", ""))
    }
}

签名配置

android {
    signingConfigs {
        release
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
}

File signFile = file(System.getenv('HOME') + "/.android/sign.properties")
if( signFile.canRead() ) {
    Properties p = new Properties()
    p.load(new FileInputStream(signFile))

    if( p!=null
    && p.containsKey('key.store')
    && p.containsKey('key.store.password')
    && p.containsKey('key.alias')
    && p.containsKey('key.alias.password')
    ) {
        println "RELEASE_BUILD: Signing..."

        android.signingConfigs.release.storeFile = file( p['key.store'] )
        android.signingConfigs.release.storePassword = p['key.store.password']
        android.signingConfigs.release.keyAlias = p['key.alias']
        android.signingConfigs.release.keyPassword = p['key.alias.password']

    } else {
        println "RELEASE_BUILD: Required properties in signing.properties are missing"
        android.buildTypes.release.signingConfig = null
    }
    } else {
        println "RELEASE_BUILD: signing.properties not found"
        android.buildTypes.release.signingConfig = null
}

ndk-build

自动生成mk

defaultConfig {
    sourceSets {
        main {
            jni.srcDirs = []
        }
    }

    ndk {
        moduleName "singalsdk"
        abiFilter "armeabi-v7a,x86,armeabi"
        stl "stlport_shared"
    }
}

使用定制mk

task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
    def androidMKfile = "$projectDir/jni/Android.mk"
    def applicationMKfile = "$projectDir/jni/Application.mk"
    def ndkDir = System.env.ANDROID_NDK_HOME
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        def ndkbuildcmd = $ndkDir/ndk-build.cmd
    } else {
        def ndkbuildcmd = $ndkDir/ndk-build
    }
    def execmd = ["$ndkbuildcmd","-j16","NDK_PROJECT_PATH=$buildDir",
    "APP_BUILD_SCRIPT=$androidMKfile", "NDK_APPLICATION_MK=$applicationMKfile"]
    println(execmd)
    commandLine execmd
}

tasks.withType(JavaCompile) {
    compileTask -> compileTask.dependsOn ndkBuild
}

利用productFlavor实现渠道包

初始化渠道包列表./../markets.list
productFlavors {
    def path="./../markets.list"
    file(path).eachLine { line->
        def channel = line
        if (!channel.trim().equals("")) {
            "$channel" {
                manifestPlaceholders=[channelname: channel]
            }
        }
    }
}

发布产物到Maven仓库(upload/publish)

Publishing artifacts(old)

Maven Publishing (new)

apply plugin: 'maven-publish'
apply plugin: 'signing'

def isReleaseBuild() {
    return version.contains("SNAPSHOT") == false
}

def getReleaseRepositoryUrl() {
    return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
    : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}

def getSnapshotRepositoryUrl() {
    return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
    : "https://oss.sonatype.org/content/repositories/snapshots/"
}

def getRepositoryUsername() {
    return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}

def getRepositoryPassword() {
    return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}

group = GROUP 

task androidJavadocs(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
    exclude '**/*.so'
}

task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
    classifier = 'javadoc'
    from androidJavadocs.destinationDir
}

task androidSourcesJar(type: Jar) {
    classifier = 'sources'
    from android.sourceSets.main.java.sourceFiles
}

task androidNativeJar(type: Jar) {
    classifier = 'so'
    from(new File(buildDir, 'libs'))
    include("**/*.so")
}

task androidNativeZip(type: Zip) {
    classifier = 'so'
    from(new File(buildDir, 'libs'))
    include("**/*.so")
}


android.libraryVariants

publishing {
    publications {
        maven(MavenPublication) {
            artifact bundleRelease
            artifact androidJavadocsJar
        }
    }
}

publishing {
    repositories {
        maven {
            credentials {
                username = getRepositoryUsername()
                password = getRepositoryPassword()
            }

            if(isReleaseBuild()) {
                url getReleaseRepositoryUrl()
            } else {
                url getSnapshotRepositoryUrl()
            }
        }
    }
}

生成jar文件

task clearJar(type: Delete) {
    delete 'build/libs/' + POM_ARTIFACT_ID + '_' + VERSION_NAME + '.jar'
}

task makeJar(type: Copy) {
    from('build/intermediates/bundles/release/')
    into('release/')
    include('classes.jar')
    rename ('classes.jar', POM_ARTIFACT_ID + '_' + VERSION_NAME + '.jar')
}

makeJar.dependsOn(clearJar, build)

收集apk到target目录

task collectApks(type:Copy) {
description = "Copies APKs and Proguard mappings to the target directory"
from 'build/outputs/apk'
exclude '**/*-unaligned.apk'
into 'target'
}
assembleRelease.finalizedBy collectApks

常用命令

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

参考资料