Android Gradle Study

648 阅读27分钟

@[toc](Android Gradle Study)

前言

大多数Android开发者是在接触Android Studio时开始接触Gradle的。很多人对它并不熟悉,遇到了构建问题,基本上都是百度里找答案,然后修改build.gradle并重试,直到问题解决。 通过反复试验学习Gradle是一个痛苦的过程。我们应该知其然,并知其所以然。所以对于Android开发者而言,系统的学习Gradle很重要。

Android Studio使用Gradle来构建,打包和部署代码。Gradle是从Java世界构建应用程序的最受欢迎的工具之一,并且正在扩展到其他语言,例如C ++。 Google的Android团队于2013年春季将Gradle用作Android的首选构建系统。Gradle来自Groovy生态系统,这是一种健壮简洁的脚本语言。 对于熟悉Java的开发人员而言,Groovy非常容易学习。

就Gradle而言,在IDE中执行任何的相关构建操作,绝大多数都可在命令行完成,这意味着我们可以进行自动化构建。

本文目的是避免最常见的构建问题。比如构建冲突,自动执行APK的签名过程,分版本打包,优化构建流程等等。

注:本文的大部分内容总结自Gradle_Recipes_for_Android.pdf,相关材料(代码示例,练习等)可从https://github.com/kousen/GradleRecipesForAndroid下载。

Android中的Gradle 基础

用Android Studio创建一个新工程时,会自动生成三个构建文件:settings.gradle, build.gradle, 和 app/build.gradle。settings.gradle展示了主工程的所有子工程目录。默认会显示:

include ':app'

说明app目录是唯一的子工程。当添加Android库工程后,它也会被添加到这里。

再来看工程根目录下的build.gradle。

// Top-level build file where you can add configuration options common to all subprojects/modules.
buildscript {//1.
    repositories {
        jcenter() 
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0'
               // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
	} 
}

allprojects {//2.
    repositories {
	jcenter() 
    }
}

task clean(type: Delete) {//3.
    delete rootProject.buildDir
}
  1. 顶层build中的buildscript块告诉Gradle在哪里下载gradle插件。 默认情况下,插件是从jcenter下载的,即Bintray JCenter Artifactory存储库。 支持其他存储库(尤其是mavenCentral(),默认的Maven存储库)。

  2. allprojects块指示主工程和所有子工程都默认使用jcenter()存储库来解析任何Java库依赖项。

  3. Gradle允许自定义任务并将其插入到有向图 (DAG),Gradle通过有向图来解析任务关系。在这里,一个清除任务被已添加到顶层的build构建中。 type : Delete表示新task是Gradle中内置的Delete任务的自定义实例。这里表示从根项目中删除build构建目录,该目录默认为最顶层的build文件夹。

再来看app/build.gradle。

apply plugin: 'com.android.application'
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.kousenit.myandroidapp"
        minSdkVersion 19
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    buildTypes {
        release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'),
        'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
}
  1. apply 方法:常规Java项目使用Java插件,而Android项目改为使用com.android.application插件。顶层的build文件通过buildscript块将Gradle的android插件添加到你的工程中, app/build.gradle则apply该插件,该插件将一个android块添加到Gradle DSL。

  2. android块是Android DSL的入口点。 您必须使用compileSdkVersion指定编译目标,并通过buildToolsVersion指定构建工具版本。这两个值都应分配给最新的可用版本,因为它们是向后兼容的,并且包括所有当前的错误修复程序。 android内部的defaultConfig块显示了几个属性: applicationId 应用程序的“程序包”名称,在Google Play商店中必须唯一。在您的应用程序使用期内,此值永远不会改变;对其进行更改将导致您的应用被视为全新的应用,而现有用户将看不到此更改。在迁移到Gradle之前,这是AndroidManifest根元素的package属性。现在可以将两者分离。 minSdkVersion 此应用程序支持的最低Android SDK版本。早于此版本的设备 则在访问Google Play商店时将看不到该应用程序。 targetSdkVersion 适用于此应用程序的Android版本。 如果不是最新版本, Android Studio将发出警告,但您可以随意使用任何喜欢的版本。 versionCode 此应用版本的整数值。应用升级会使用它。 versionName 此应用版本的的字符串。以"< major >.< minor >.< version > "形式的字符串出现。 注:在切换到Gradle之前,在Android Manifest中将minSdkVersion和buildToolsVersion属性指定为标签的属性。 现在不赞成使用该方法,因为Gradle构建文件中的值将覆盖那里的值。

    compileOptions部分显示此应用程序期望使用JDK版本1.7。

  3. dependencies块:依赖关系块由三行组成。第一个是fileTree依赖项,意味着将libs文件夹中所有以.jar结尾的文件添加到编译类路径中。 第二行告诉Gradle下载JUnit 4.12版本并将其添加到“test compile”阶段,这意味着JUnit类将在src/androidTest/java源码树以及src/test/java源码树中可用。 可以添加它用于纯单元测试(即那些不涉及Android API的测试)。第三行告诉Gradle,从Android支持库中添加23.0.0版本的appcompat-v7 jar文件。 请注意,-v7表示Android应用程序的支持回到Android的第7版,而不是支持库本身的第7版。

gradle命令行编译Android应用工程

Gradle包装器

Android Studio以插件的形式集成了Gradle发行版,并包含专用的功能。 术语"Gradle wrapper"是指Android应用程序根目录中的Unix的gradlew脚本和Windows的gradlew.bat脚本,其中结尾的"w"代表"包装器"。 Gradle包装器的目的是允许客户端运行Gradle,而不必先安装它。包装程序使用应用程序根目录下gradle / wrapper文件夹中的gradle-wrapper.jar和gradle-wrapper.properties文件启动gradle过程。

/*gradle-wrapper.properties*/
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

distributionUrl属性指明Gradle包装器将下载并安装Gradle.1的2.10版本。 第一次运行后,Gradle发行版将被缓存在zipStoreBase目录下的zipStorePath文件夹下,然后可用于所有的Gradle任务的后续执行。

只需在Unix上执行./gradlew命令或在Windows上执行gradlew.bat命令,即可在命令行中使用包装器。

可以在命令行上运行任何支持的Gradle任务,包括您自己的自定义任务。

命令行运行build task:

./gradlew build

编译后的结果可在app / build文件夹中找到。 生成的apk (Android软件包)文件位于app / build / outputs / apk目录中。

Welcome to Gradle 5.4.1.

To run a build, run gradlew < task> ...

To see a list of available tasks, run gradlew tasks

To see a list of command-line options, run gradlew --help

To see more detail about a task, run gradlew help --task < task>

Gradle中的task命令显示了构建中可用的任务:

./gradlew tasks

输出:

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for the base and test modules
sourceSets - Prints out all the source sets defined in this project.

Build tasks
-----------
assemble - Assemble main outputs for all the variants.
assembleAndroidTest - Assembles all the Test applications.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
bundle - Assemble bundles for all the variants.
clean - Deletes the build directory.
cleanBuildCache - Deletes the build cache directory.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Cleanup tasks
-------------
lintFix - Runs lint on all variants and applies any safe suggestions to the source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'GaussianBlurDemo'.
components - Displays the components produced by root project 'GaussianBlurDemo'. [incubating]
dependencies - Displays all dependencies declared in root project 'GaussianBlurDemo'.
dependencyInsight - Displays the insight into a specific dependency in root project 'GaussianBlurDemo'.
dependentComponents - Displays the dependent components of components in root project 'GaussianBlurDemo'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'GaussianBlurDemo'. [incubating]
projects - Displays the sub-projects of root project 'GaussianBlurDemo'.
properties - Displays the properties of root project 'GaussianBlurDemo'.
tasks - Displays the tasks runnable from root project 'GaussianBlurDemo' (some of the displayed tasks may belong to subprojects).

Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build.
uninstallRelease - Uninstalls the Release build.

Verification tasks
------------------
check - Runs all checks.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
lintVitalRelease - Runs lint on just the fatal issues in the release build.
test - Run unit tests for all variants.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.

以下示例演示获取assemble任务的帮助信息。 示例:

./gradlew help -- task assemble

输出:

> Task :help
Detailed task information for assemble

Path
     :app:assemble

Type
     Task (org.gradle.api.Task)

Description
     Assemble main outputs for all the variants.

Group
     build

通过用空格分隔它们来运行多个任务, 使用-x标志排除任务。

./gradlew lint assembleDebug

./gradlew assembleDebug -x lintDebug //不包括lintDebug任务

任务命令上的--all标志显示项目中的所有任务以及每个任务的依赖性。 注:gradle tasks --all的输出-全部可能非常长。

可以仅提供足够的字母来从命令行缩写任务名称以唯一地确定它

gradlew anDep //简写 Task :app:androidDependencies

Use the Gradle view to execute tasks

在Android Studio中打开Gradle view,如图:

在这里插入图片描述
双击任务,即可在Run窗口中执行该任务。

如何添加Java库依赖

在app的build.gradle的dependencies中添加group, name, 和 version 。 Gradle支持多种列出依赖项的方式。最常见的是使用引号引起来、带有冒号分隔的group, name, and version。Gradle文件使用Groovy,它支持单引号和双引号字符串。区别就是双引号允许插值或变量替换。

每个依赖项都与一个配置相关联。 Android工程包括compile, runtime, testCompile,和 testRuntime 等配置。可以添加其他配置,您也可以定义自己的配置。

依赖项语法:

testCompile group: 'junit', name: 'junit', version: '4.12' //完整语法
testCompile 'junit:junit:4.12' //简短语法,build.gradle默认
testCompile 'junit:junit:4.+'  //使用加号指定版本号,合法但不推荐,使构建的确定性降低,因此可复制性降低。

如果要在配置中添加文件集而不将其添加到存储库,可以在依赖关系块中使用files或fileTree语法:

dependencies {
    compile files('libs/a.jar', 'libs/b.jar')
    compile fileTree(dir: 'libs', include: '*.jar')
}

接下来,Gradle需要知道在哪里搜索解析依赖关系。这是通过顶层build.gradle中的repositories存储库块完成的。

Android项目使用Gradle命令 androidDependencies来查看传递性依赖关系。

示例:主工程app,库工程mgimlibs,下面分别为主工程app和库工程mgimlibs的build.gradle中的依赖块:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.+'
    implementation project(':mgimlibs')
}
dependencies {
    implementation 'com.android.support:support-v4:26.+'
}

我们通过androidDependencies命令来查看依赖关系: ==./gradlew androidDependencies==

输出:

> Task :app:androidDependencies
debug
debugCompileClasspath - Dependencies for compilation
+--- com.android.support:appcompat-v7:26.1.0@aar
+--- com.android.support:support-v4:26.1.0@aar
+--- com.android.support:animated-vector-drawable:26.1.0@aar
+--- com.android.support:support-vector-drawable:26.1.0@aar
+--- com.android.support:support-media-compat:26.1.0@aar
+--- com.android.support:support-fragment:26.1.0@aar
+--- com.android.support:support-core-utils:26.1.0@aar
+--- com.android.support:support-core-ui:26.1.0@aar
+--- com.android.support:support-compat:26.1.0@aar
+--- com.android.support:support-annotations:26.1.0@jar
+--- android.arch.lifecycle:runtime:1.0.0@aar
+--- android.arch.lifecycle:common:1.0.0@jar
+--- android.arch.core:common:1.0.0@jar
\--- :mgimlibs (variant: debug)
...

> Task :mgimlibs:androidDependencies
debug
debugCompileClasspath - Dependencies for compilation
+--- com.android.support:support-v4:26.1.0@aar
+--- com.android.support:support-media-compat:26.1.0@aar
+--- com.android.support:support-fragment:26.1.0@aar
+--- com.android.support:support-core-utils:26.1.0@aar
+--- com.android.support:support-core-ui:26.1.0@aar
+--- com.android.support:support-compat:26.1.0@aar
+--- com.android.support:support-annotations:26.1.0@jar
+--- android.arch.lifecycle:runtime:1.0.0@aar
+--- android.arch.lifecycle:common:1.0.0@jar
\--- android.arch.core:common:1.0.0@jar

这里使用Android支持库中的appcompat-v7库。 该库依赖于support-v4库,该库使用Android SDK中的内部jar。

Gradle提供了include包含和exclude排除单个库的语法。 ==Gradle默认遵循传递依赖项,即下载该依赖之前,会下载该依赖的递归依赖项。 如果您想将其关闭特定库的依赖,请使用transitive标志:==

dependencies {
    runtime group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.0.1',transitive: false 
}

将传递标记的值改为false会阻止下载相关依赖项,因此必须自己添加所需的内容。 如果只需要一个模块jar,而没有任何其他的依赖,则也可以指定它:

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.4.4@jar'//简短语法
    
    compile group: 'org.codehaus.groovy', name: 'groovy-all',
    version: '2.4.4', ext: 'jar' //完整version语法
}

简短语法使用@设置后缀,完整version语法使用ext设置后缀。

示例,引入了依赖项spock-core项,其排除了依赖项Groovy和JUnit库。

dependencies {
  androidTestCompile('org.spockframework:spock-core:1.0-groovy-2.4') {
    exclude group: 'org.codehaus.groovy'
    exclude group: 'junit'
  }
}

配置Repositories

配置Repositories存储库,准确解决库依赖关系 存储库块告诉Gradle在哪里可以找到依赖项。 默认情况下,Android使用jcenter()或mavenCentral(),分别表示默认的Binray JCenter存储库和公共Maven Central存储库。

repositories {
    jcenter()
}

这是指位于https://jcenter.bintray.com的JCenter存储库。

repositories {
    mavenLocal()
    mavenCentral()
}

Maven存储库有两个快捷方式。 mavenCentral()语法引用位于http://repo1.maven.org/maven2的center Maven 2存储库。 Maven Local()语法引用本地Maven缓存。

可以使用带有maven参数的任何Maven存储库添加到默认列表。

repositories {
    maven {
        url 'http://repo.spring.io/milestone'
    }
}

如果本地文件系统上有文件,则可以使用flatDir语法将目录用作存储库。

repositories {
    flatDir {
	dirs 'lib' 
    }
}

这是使用file或fileTree将文件显式添加到依赖项块的替代方法。

通常会在构建中添加多个存储库。 Gradle将自上而下依次搜索每个对象,直到解析出所有依赖项为止。

用gradle设置工程属性

使用ext块为项目添加额外属性

要从构建文件中删除它们,请将属性放在gradle.properties文件中,或使用-P标志在命令行上进行设置。

Gradle构建文件使用简单的ext语法支持属性定义,“ext”代表“ extra”,通过ext语法来定义变量值并在整个文件中使用它。 看一下app/build.gradle中的一段代码:

ext {
	def AAVersion = '4.0-SNAPSHOT' // change this to your desired version
}
dependencies {
    apt "org.androidannotations:androidannotations:$AAVersion"
    compile "org.androidannotations:androidannotations-api:$AAVersion"
}

此处使用def关键字来定义当前构建文件中的局部变量。 定义不带def(或任何其他类型)的变量会将变量添加为项目对象的属性,从而使其在该项目及其子项目中均可用。 ==定义变量后,可以通过变量名直接使用它;在字符串中通过$后跟变量名来引用该变量。==

==ext块中的无类型变量将属性添加到与build构建关联的Project实例中。== 可以用来移除build.gradle中的实际值。

示例:移除'user'和'password'

repositories {
    maven {
        url 'http://repo.mycompany.com/maven2'
        credentials {
            username 'user'
            password 'password'
        }
    } 
}

在 gradle.properties中添加变量:

ext{
	login='user'
	pass='my_long_and_highly_complex_password'
}

替换后的代码:

repositories {
    maven {
        url 'http://repo.mycompany.com/maven2'
        credentials {
            username login
            password pass
        } 
    }
}

动态设置属性 前面提到==ext块中的无类型变量将属性添加到与build构建关联的Project实例中。== 看一下动态设置属性的demo:

ext {
	if (!project.hasProperty('user')) {
	    user = 'user_from_build_file'
	}
	if (!project.hasProperty('pass')) { 
		pass = 'pass_from_build_file'
	}
}
task printProperties() { //打印变量
    doLast {
        println "username=$user"
        println "password=$pass"
    }
}

自定义任务 printProperties()将打印user和pass变量值:

>./gradlew printProperties
:app:printProperties
username=user_from_build_file
password=pass_from_build_file 

更新Gradle

Gradle会定期发布新版本。 出于性能原因(每个新版本都更快)或由于向项目添加了新功能,您可能希望更新项目中使用的Gradle版本。 为此,有两个主要选择: 1.将wrapper(包装器)任务添加到build.gradle文件并生成新的wrapper脚本 2.直接在gradle-wrapper.properties中编辑distributionUrl值 如果您的项目已使用当前版本的Gradle加载,则第一个选项效果最佳。 默认情况下,Gradle构建已经包含一个wrapper任务,可以通过运行gradle tasks命令来看到它。

~ apple$ gradle tasks
> Task :tasks
------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
wrapper - Generates Gradle wrapper files.
...
BUILD SUCCESSFUL

内建wrapper 任务 gradle wrapper命令支持--gradle-version参数。如下代码在命令行中使用所需版本重新生成wrapper包装器:

> ./gradlew wrapper --gradle-version 2.12
:wrapper
BUILD SUCCESSFUL
Total time: ... sec

另一个选择是将wrapper任务显式添加到顶层的build构建文件,并为gradleVersion指定一个值:

task wrapper(type: Wrapper) {
    gradleVersion = 2.12
}

进行此更改后,运行./gradlew wrapper 将生成新的wrapper文件。

然而,每隔一段时间,现有的wrapper文件就变得旧了,以至于Android Studio拒绝与现有的构建文件进行同步,从而无法运行任何任务。在这种情况下,总是可以直接转到控制wrapper的文件,这些文件是在wrapper首次运行时生成的。 除了生成的脚本gradlew和gradlew.bat之外,包装程序还依赖于名为gradle / wrapper的文件夹,并且其中包含两个文件gradle-wrapper.jar和gradle-wrapper.properties,gradle-wrapper.properties文件包含distributiontionUrl属性,该属性告诉包装程序在哪里下载所需的Gradle版本。直接编辑此文件,将distributiontionUrl属性中的版本号更改为您喜欢的任何版本。这样可以毫无问题地运行现有的包装脚本。 gradle-wrapper.properties:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip

Sharing Settings Among Projects

如果想移除多模块中的重复设置,可以在顶层的build文件中使用 allprojects 或 subprojects方法。 示例2.5

allprojects {
    repositories {
	jcenter() 
    }
}

此块来自Gradle DSL,因此适用于所有基于Gradle的项目,而不仅限于Android项目。 allprojects属性来自Gradle中的Project API,它是org.gradle.api.Project类的属性。该属性由一个包含当前项目及其所有子项目的集合组成。还有一个同名的方法,它允许您配置当前项目及其所有子项目。

Project中allprojects方法 该方法将org.gradle.api.Action类型的引用作为参数。

void allprojects(Action<? super Project> action)

此方法针对此项目及其每个子项目执行给定的Action。 Action 是具有单个方法(称为execute)的接口,该接口带有单个通用参数,因此必须创建一个实现Action接口的类,将其实例化,并将结果提供为参数。在Java(Java SE 8之前的版本)中,通常将其作为匿名内部类完成。

project.allprojects(new Action<Project>() { 
	void execute(Project p) {
	        // do whatever you like with the project
	} 
});

在Groovy中,您只需提供一个closure闭包作为参数即可实现单方法接口。然后,闭包将成为该方法的实现。 allprojects和subprojects方法的Gradle实现是在集合中的每个项目上调用closure参数。

在这里allprojects方法的行为是将closure(闭包)参数应用于allprojects集合返回的每个项目,默认为顶层项目和app模块。这里表示将主工程和库工程的存储库块配置为jcenter()。

Project中subprojects方法 再来看subprojects块。假设主工程包含了多个Android库工程,而每个Android库工程都需要在各自的构建文件中应用android库插件。此时可以在顶层build文件中使用subprojects方法启用该插件,进而可以删除各个库工程的build.gradle中的重复项:

subprojects {
    apply plugin: 'com.android.library'
}

注: subprojects属性和subprojects方法的区别: subprojects属性返回子项目集 subprojects方法将提供的闭包应用于每个子项目。

Signing a Release APK

在部署之前,所有Android软件包(APK)文件都需要进行数字签名。 默认情况下,Android使用一个已知密钥为您签名debug版的APK。debug版的密钥库位于主目录中名为.android的子目录中。 密钥库的默认名称为debug.keystore,其密钥库密码为android。

此外,也可使用Java的keytool命令创建certificate(证书),并在Gradle构建文件的signingConfigs块中配置其使用。

默认debug.keystore的信息如下:

Keystore name: “debug.keystore”
Keystore password: “android”
Key alias: “androiddebugkey”
Key password: “android”
CN: “CN=Android Debug,O=Android,C=US”

以下代码列出默认证书:

$ cd ~/.android
$ keytool -list -keystore debug.keystore
输入密钥库口令:  ("android")
密钥库类型: JKS
密钥库提供方: SUN

您的密钥库包含 1 个条目

androiddebugkey, 2018-6-19, PrivateKeyEntry, 
证书指纹 (SHA1): 07:2A:0D:02:DB:07:29:D9:1A:87:C0:33:C9:2F:91:D6:05:39:90:ED

密钥库类型是JKS,表示Java密钥库,用于公共和私有密钥。 密钥库具有一个alias(别名)为androiddebugkey的自签名证书,用于在将debug版APK部署到已连接的设备或仿真器时对APK进行签名。

同样,您必须先签名才能发布应用程序的发行版,这需要一个Release key。

demo of Generating a release key :

keytool -genkey -v -keystore myapp.keystore -alias my_alias
    -keyalg RSA -keysize 2048 -validity 10000  (all on one line)
Enter keystore password:  (probably shouldn't use use "password")
Re-enter new password:    (but if you did, type it again)
What is your first and last name?
  [Unknown]:  Ken Kousen
What is the name of your organizational unit?
  [Unknown]:
What is the name of your organization?
  [Unknown]:  Kousen IT, Inc.
What is the name of your City or Locality?
  [Unknown]:  Marlborough
What is the name of your State or Province?
[Unknown]:  CT
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=Ken Kousen, OU=Unknown, O="Kousen IT, Inc.", L=Marlborough,
  ST=CT, C=US correct?
  [no]:  yes
Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA)
  with a validity of 10,000 days for: CN=Ken Kousen, OU=Unknown,
  O="Kousen IT, Inc.", L=Marlborough, ST=CT, C=US
Enter key password for <my_alias>
        (RETURN if same as keystore password):
[Storing myapp.keystore]

RSA算法用于生成2K大小的公/私密钥对,并使用SHA256算法签名,有效期为10,000天(超过27年)。 有了release key后,就可以用Gradle进行签名。 作为android闭包的子类,向android块中添加一个signingConfigs块:

android {
// ... other sections ...
    signingConfigs {
        release {//这里的release为自定义属性
            keyAlias 'my_alias'
            keyPassword 'password'
            storeFile file('/user/keystores/myapp.keystore')
            storePassword 'password'
        } 
    }
}

从DSL文档中可以看到,signingConfigs块是委托给SigningConfig类的实例,该实例具有列出的四个常用属性: keyAlias 签署特定密钥时在keytool中使用的值 keyPassword 签名过程中使用的特定密钥的密码 storeFile 由keytool生成的包含密钥和证书的磁盘文件 storePassword 密钥库本身使用的密码 还有一个storeType属性,但很少使用。

要使用新配置,请在发行版本类型中添加一个signingConfig属性:

android {
// ... other sections ...
    buildTypes {
        release {
            // ... other settings ...
            signingConfig signingConfigs.release //与上面的自定义属性release对应
        }
    } 
}

当从命令行Gradle调用assembleRelease任务时,该构建将在app / build / outputs / apk文件夹中生成一个发行版APK。

> ./gradlew assembleRelease
:app:preBuild UP-TO-DATE
:app:preReleaseBuild UP-TO-DATE
// ... lots of tasks ...
:app:zipalignRelease UP-TO-DATE
:app:assembleRelease UP-TO-DATE
BUILD SUCCESSFUL

注意: 不要丢失密钥库。 如果丢失,您将无法对您的应用发布任何更新,因为所有版本都必须使用相同的密钥签名。否则,新版本将被视为全新应用。 应将密钥库放在安全的地方。 您使用的是自签名证书,但这并不是出于加密目的。 它被用于完整性(保证APK未被修改)和不可否认性(保证您是唯一可以签名的人)。 如果其他人可以访问您的密钥库,则他们可以用您的名字签署其他应用程序。

也可以使用Android Studio生成signing configurations (签名配置),并将其分配给build types(构建类型)。“Build”菜单具有用于生成签名配置的选项,“Project Structure”对话框具有用于将其分配给build types和 flavors的选项卡。

Build → Generate Signed Bundle/APK

在这里插入图片描述

在这里插入图片描述
这里选择Choose existing,选择.android目录中的debug.keystore。
在这里插入图片描述
即可用默认的debug.keystore签名release版的apk。

再看一下Project Structure中如何配置签名。

在这里插入图片描述

在这里插入图片描述

看下生成的代码:

android {
    signingConfigs {
        release {
            storeFile file('/Users/apple/.android/debug.keystore')
            storePassword 'android'
            keyAlias = 'androiddebugkey'
            keyPassword 'android'
        }
    }
    compileSdkVersion 26
    buildToolsVersion '28.0.3'

    defaultConfig {
        ...
        signingConfig signingConfigs.release
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
	...
}

Build Types

android块中的buildTypes块用于配置构建类型。构建类型决定如何打包应用程序。 默认情况下,适用于Gradle的Android插件支持两种类型的构建:debug 和 release。 下列代码显示了构建文件中的buildTypes块。

android {
    buildTypes {
	release {
    	    minifyEnabled true //开启混淆
    	    proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'//混淆规则文件
	}
    }
}

可以在com.android.build.gradle.inter nal.dsl.BuildType类的DSL参考中找到完整的属性和方法集。

另一个构建类型中可用的属性是debuggable。 debug版本会自动将debuggable设置为true,而其他所有版本都默认设置为false。 为了在单个设备上安装多种构建类型,Android必须能够区分其应用程序ID。 applicationIDsuffix属性允许Gradle生成多个APK,每个APK都有自己的ID。

例:在application ID和version name上添加后缀:

android {
// ... other properties ... 
	buildTypes {
            debug {
                applicationIDsuffix '.debug'
                versionNameSuffix '-debug'
            }
            // .. other build types ...
        }
 }

现在,该应用程序的发行版和调试版都可以部署到同一设备上。 如果访问设备上的“设置”并转到“应用程序”,则可以看到调试版本和发行版都在同一应用程序中。

Product Flavors and Variants

Product flavors 允许创建相同应用的多个不同版本。当您需要为不同的客户自定义应用程序的外观或功能,或者需要推出同一应用程序的免费和付费版本时,可能会发生这种情况。 通过在android块中使用productFlavors块来实现。

下列代码将为applicationId为 'com.oreilly.helloworld'的应用产生两个flavor:arrogant和friendly,它们都具有唯一的applicationId。这样在同一个设备上就可以安装这两个不同风格的版本。

android {
    productFlavors {
	  arrogant {
	     applicationId 'com.oreilly.helloworld.arrg'
	  }
	  friendly {
		 applicationId 'com.oreilly.helloworld.frnd'
	  }
   }
}

须注意:flavor的名字不能与已存在的buildtype和androidTest一样。 每个productFlavors中可以自定义的属性有:

• applicationId
• minSdkVersion
• targetSdkVersion 
• versionCode
• versionName
• signingConfig

基本上是defaultConfig块中的属性。

每个flavor都可定义自己的源码目录和资源目录,它们是main的同级目录,除了app/src/main/java外,还可以在以下位置添加源文件: app/src/arrogant/java •app/src/friendly/java

可以在以下位置添加其他资源文件: •app / src / arrogant / res / layout •app / src / arrogant / res / values •app / src / friendly / res / layout •app / src / friendly / res / values

Merging Resources

每种flavor都有自己的资源目录,位于app / src / / res下。 都添加了名为values、layout的子文件夹。并将app / src / main / res / values的strings.xml文件的副本复制到app / src / / res / values目录下。

gradle将的res文件夹中的资源与buildtype和主目录树中的相同文件夹进行合并来整合资源。 优先级是:buildtype优先于product flavor,后者优先于主目录树中的资源。非Java资源会相互覆盖,其中buildtype构建类型具有最高优先级,然后是flavor,然后是主目录。 所以flavor下的资源目录会覆盖main中的资源目录。

variant变体

buildType构建类型和flavor风格的组合称为variant变体。对于两种默认的构建类型(debug和release)和此处显示的两种风格(arrogant,friendly),可以生成四个不同的变体APK:

arrogantDebug
arrogantRelease
friendlyDebug
friendlyRelease

可以从Android Studio的“Build Variants”视图下拉列表中选择变体来部署。

Flavor Dimensions

假设几个flavor,它们的源代码相同, 只有几个次要资源不同。为避免每个目录下的资源重复过多,可以在productFlavors中使用flavorDimensions将相同的资源保留一份放在一起。

flavorDimensions 'attitude', 'client'
productFlavors {
    arrogant {
        dimension 'attitude'
        applicationId 'com.oreilly.helloworld.arrg'
    }
    friendly {
        dimension 'attitude'
        applicationId 'com.oreilly.helloworld.frnd'
    }
    stark {
        dimension 'client'
    } 
}

现在,flavor有两个维度:attitude和client。 arrogant和friendly的flavor都在attitude维度上,而stark的flavor则是在client维度上。 具有两种attitude和一个client的两种buildtype的组合给出2 * 1

  • 2 = 4个不同的variant变体:
•arrogantStarkDebug
•arrogantStarkRelease
•friendlyWayneDebug
•friendlyWayneRelease

注意:Gradle构建文件中的flavorDimensions定义顺序为:

flavorDimensions 'attitude', 'client'

这意味着attitude维度中的资源将比client维度中的具有更高的优先级。 因此,将所有attitude都相同的资源放在stark目录下,每种attitude中的同名资源应删除。 这样就达到了删除多个风格版本中重复资源的目的。

Merging Java Sources Across Flavors

尽管flavor和buildtype中的字符串和布局资源会覆盖main中的相应资源,但Java类却有所不同。 如果您在main目录下的代码引用了一个特定的类,那么只要main目录中没有该类的实现,每种flavor和buildtype中都可以自己实现该类。 注:被main目录的代码引用的任意类必须在每个flavor下都存在。每个flavor下的实现类都是独立的。

改进Gradle build性能

本章节讨论可以添加到Android应用程序根目录中的gradle.properties文件中的设置,比如添加全局设置。

==并行编译== 可以并行编译主工程和库工程。如启用,在gradle.properties中添加一行:

org.gradle.parallel = true

注意:这可能无济于事。 Android项目中的大多数模块都是相关联的,这抵消了并行编译的任何优点。

==排除不必要的任务== -x标志可用于排除特定任务,例如lint(代码优化工具),这对于排除每次构建过程中都耗时又不必要的任务很必要。

==更改JVM设置== Gradle构建最终在Java进程中运行,因此影响JVM的标志会影响Gradle的性能。下例显示了Java虚拟机的一些设置。

org.gradle.jvmargs = -Xmx2048m -XX:MaxPermSize = 512m
    -XX:+ HeapDumpOnOutOfMemoryError

-Xmx标志指定Java进程中要使用的最大内存量。 -Xms标志指定要分配给进程的初始内存量。 MaxPermSize = 512m:设置“永久生成”空间的大小 HeapDumpOnOutOfMemoryError:在引发java.lang.OutOfMemoryError时将堆转储到文件中。

==使用dex选项== Android块允许您指定用于控制将Java字节码(即.class文件)转换为Dalvik可执行文件(.dex文件)的“ dex”过程的选项。 android中的dexOptions块选项:

dexOptions {
	incremental true
	javaMaxHeapSize '2g' 
	jumboMode = true 
	preDexLibraries = true
}

incremental:增量选项指定是否为dx处理器启用增量模式。如文档所述,“这有很多局限性,可能不起作用。小心使用。” javaMaxHeapSize:使用javaMaxHeapSize作为在dx运行期间指定Xmx值(Java进程中要使用的最大内存量)的另一种方法,以1024m为增量, 此处将其设置为2g。 jumboMode:启用jumboMode可以在dex文件中使用大量字符串。 preDexLibraries:启用preDexLibraries将提前在库上运行dx进程。如文档所述,“这可以改善增量构建,但是干净的构建可能会更慢。” 所有这些设置都可能改进或损害性能,在采用它们之前,请务必先试验一下。

Groovy study

Gradle构建文件主要由用Groovy编写的DSL(领域特定语言)组成。 除DSL外,任何合法的Groovy代码都可以添加到构建中。 Groovy是基于Java的通用编程语言,可编译为Java字节码。 它具有功能性,但它是一种面向对象的语言,可以说是从C++到Java的下一代语言。

基本语法

Example A-1. "Hello, World!" in Groovy

println 'Hello, World!'

注意事项: •分号不是必需的。 不添加它们,也可以工作。 •括号是可选的,除非没有。 如果编译器能正确猜测它们消失的地方,则一切正常。 否则,将它们重新添加回来。println方法接受一个String参数。 此处省略了括号。 •Groovy中有两种类型的字符串:单引号字符串(例如'Hello')是java.lang.String的实例。 双引号字符串是Groovy字符串,并允许插值,如示例A-2所示。 Groovy中没有“基本类型”。 所有变量都使用包装器类,例如java.lang.Integer,java.lang.Character和java.lang.Double。 整型的本地数据类型(例如3)是Integer。 浮点型的本地数据类型(例如3.5)是java.math.BigDecimal。

Example A-2. Some basic data types in Groovy

assert 3.class == Integer 
assert (3.5).class == BigDecimal 
assert 'abc' instanceof String。 //1.
assert "abc" instanceof String。//2. 

String name = 'Dolly'
assert "Hello, ${name}!" == 'Hello, Dolly!' //3.
assert "Hello, $name!" == 'Hello, Dolly!' //4.
assert "Hello, $name!" instanceof GString
  1. 单引号字符串是Java字符串
  2. 除非有字符串插值,否则双引号字符串也是Java字符串。
  3. 字符串插值,完整格式
  4. 字符串插值,无歧义时的短格式 ==请注意,可以在数值上调用方法,因为它们是包装类的实例。== Groovy允许您使用实际类型(例如String,Date或Employee)声明变量,也可以使用def。 请参见示例A-3

Example A-3. Static versus dynamic data types

Integer n = 3
Date now = new Date()
def x=3
assert x.class == Integer 
x = 'abc'
assert x.class == String 
x = new Date()
assert x.class == Date

Java自动导入java.lang包。 在Groovy中,以下软件包都将自动导入: • java.lang • java.util • java.io • java.net • groovy.lang • groovy.util

也可以在没有导入语句的情况下使用java.math.BigInteger和java.math.BigDecimal类。

断言方法 和Groovy中的真假规则

Groovy中的assert方法根据“ Groovy的真假规则”评估其参数。 这意味着: •非零数(正数和负数)为真 •非空集合(包括字符串)为真 •非空引用为真 •布尔值true为true

Example A-4. The Groovy Truth

assert 3; assert -1; assert !0 
assert 'abc'; assert !''; assert !""
assert [3, 1, 4, 1, 5, 9] 
assert ![]

通过的断言什么也不返回。 失败的声明将引发异常。 Example A-5. Failing assertions

int x=5;int y=7
assert 12 == x + y // passes
assert 12 == 3 * x + 4.5 * y / (2/x + y**3) //Assertion fails,Exception thrown

运算符重载

在Groovy中,每个运算符都对应一个方法调用。 例如,+号在Number上调用plus方法。 Groovy库中广泛使用了它。 Example A-7. Operator overloading

assert 3 + 4 == 3.plus(4) 
assert 3 * 4 == 3.multiply(4)
assert 2**6 == 64 //幂运算符
assert 2**6 == 2.power(6)
assert 'abc' * 3 == 'abcabcabc' // String.multiply(Number) 
try {
3 * 'abc'
} catch (MissingMethodException e) {
    // no Number.multiply(String) method
}

String s = 'this is a string'
assert s + ' and more' == 'this is a string and more' 
assert s - 'is' == 'th is a string'
assert s - 'is' - 'is' == 'th a string'
Date now = new Date()
Date tomorrow = now + 1 // Date.plus(Integer) 
assert tomorrow - 1 == now // Date.minus(Integer)

Groovy具有一个幂运算符**。 在Java中,==运算符检查两个引用是否指向同一对象。 而在Groovy中,==调用equals方法,因此它检查等效性而不是相等性。 如果要检查引用,请使用is方法。

Collections

Groovy具有集合的本地语法。 使用方括号并用逗号分隔值以创建ArrayList。 您可以使用as运算符将一种集合类型转换为另一种集合类型。 集合还具有运算符重载,实现加,减和乘之类的方法。

Example A-8. Collection examples and methods

def nums=[3,1,4,1,5,9,2,6,5] 
assert nums instanceof ArrayList

Set uniques = nums as Set
assert uniques == [3, 1, 4, 5, 9, 2, 6] as Set

def sorted = nums as SortedSet
assert sorted == [1, 2, 3, 4, 5, 6, 9] as SortedSet 
assert sorted instanceof TreeSet

assert nums[0] == 3
assert nums[1] == 1
assert nums[-1] == 5 // end of list 
assert nums[-2] == 6

assert nums[0..3] == [3, 1, 4, 1] // two dots is a Range assert nums[-3..-1] == [2, 6, 5]
assert nums[-1..-3] == [5, 6, 2]

String hello = 'hello'
assert 'olleh' == hello[-1..0] // Strings are collections too

Groovy中的范围由两个值组成,两个值之间用一对点分隔,例如from..to。 范围从起始位置开始扩展,接着在每个元素上调用,直到到达z终止位置(包括终止位置值)。

Map使用冒号表示法将键与值分开。 Map上的方括号运算符是getAt或putAt方法,具体取决于您是访问还是添加值。 点运算符类似地被重载。 请参见Example A-9。

Example A-9. Map instances and methods

def map = [a:1, b:2, c:2]
assert map.getClass() == LinkedHashMap 
assert map.a == 1. //1.
assert map['b'] == 2 //2.
assert map.get('c') == 2 //3.
  1. 重载点号运算符
  2. 使用getAt方法
  3. java语法同样有效

Closures(闭包)

Groovy有一个称为Closure的类,它表示可以像对象一样被使用的代码块。 可以将其视为匿名方法的主体,这是一个过分的简化,但是一个好的开始。 闭包接受实参并执行代码块。 ==Groovy闭包可以修改在其外部定义的变量。== Groovy中的许多方法都将闭包作为参数。 例如,集合上的每个方法将每个元素提供给闭包,闭包将对其进行运算。 Example A-10中有一个示例。

Example A-10. Using Groovy’s each method with a closure argument

def nums=[3,1,4,1,5,9]
def doubles = [] //1. 
nums.each { n -> //2.
	doubles<< n*2    //3.
}
assert doubles == [6, 2, 8, 2, 10, 18]
  1. 空集合
  2. 集合nums中每个元素n在箭头前,接受一个闭包类型的参数,通过左移运算符将n运算的结果追加到集合doubles
  3. 注:修改在闭包外部定义的变量并不是好的做法。

这是将list中的值加倍的自然方法,但是还有一个更好的替代方法,称为collect。 通过对每个元素应用闭包,collect方法将一个集合转换为一个新集合。

Example A-11. Using Groovy’s collect method to transform a collection

def nums=[3,1,4,1,5,9]
def doubles == nums.collect { it * 2 } 
assert doubles == [6, 2, 8, 2, 10, 18]

==当闭包具有单个参数(默认值),并且您没有使用箭头运算符为该参数指定名称时,默认名称为it,这时箭头省略。== 在这种情况下,collect方法通过在每个元素的闭包中应用it * 2来创建doubles集合。

POGOs

仅具有属性以及getter和setter的Java类通常被称为Plain Old Java Object或POJO。 Groovy具有类似的类,称为POGO。 ExampleA-12中有一个示例。 Example A-12. A simple POGO

import groovy.transform.Canonical 
@Canonical
class Event {
	String name 
	Date when 
	int priority
}

这个小类实际上具有很大的能量。 对于POGO: •默认情况下,该类是public的 •默认情况下,属性是private的 •默认情况下方法是public的 •为每个未标记为public或private的属性生成Getter和setter方法 •提供了默认构造函数和“基于map的”构造函数(使用“ attribute:value”形式的参数)

另外,此POGO包含@Canonical批注,该批注触发抽象语法树(AST)转换。 AST转换以特定方式修改在编译过程中由编译器创建的语法树。 @Canonical批注实际上是其他三个AST转换的快捷方式:@ ToString,@ EqualsAndHashCode和@TupleConstructor。在这种情况下,@Canonical注释会添加到此类: •toString重写,以自上而下的顺序显示类的全限定名称,后跟属性的值 •equals重写,对每个属性进行null-safe检查是否等效 •hashCode重写,基于属性的值生成整数 •一个按顺序将属性作为参数的附加构造函数 Example A-13显示了如何使用它。

Example A-13. Using the Event POGO

Event e1 = new Event(name: 'Android Studio 1.0', 
when: Date.parse('MMM dd, yyyy', 'Dec 8, 2014'), priority: 1)

Event e2 = new Event(name: 'Android Studio 1.0', 
when: Date.parse('MMM dd, yyyy', 'Dec 8, 2014'), priority: 1)

assert e1.toString() ==
'Event(Android Studio 1.0, Mon Dec 08 00:00:00 EST 2014, 1)'
assert e1 == e2

Set events = [e1, e2]
assert events.size() == 1

Groovy in Gradle Build Files

Gradle构建文件支持所有Groovy语法。 这里有一些具体示例说明了Gradle中的Groovy。 在Example A-14中,单词apply是Project实例的方法。 方法上的括号是可选的,此处省略。 该参数将Project实例上的名为plugin的属性设置为提供的字符串值。

Example A-14. Applying the Android plugin for Gradle

apply plugin: 'com.android.application'

在Example A-15中,术语android是插件DSL的一部分,它以闭包作为参数。 闭包内部的属性(例如compileSdkVersion)是带有可选括号的方法调用。 在某些Gradle构建文件中,使用=设置属性,这将调用相应的setter方法。 除了设置器setCompileSdkVersion(23)外,Android插件的开发人员还经常添加常规方法,如compileSdkVersion(23)。

Example A-15. Setting properties in the android block

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
}

同样,“嵌套”属性(如此处的compileSdkVersion)也可以使用点表示法设置: android.compileSdkVersion = 23 两者是等效的。 插件的最新版本将清理任务添加到Gradle构建文件中。 该任务的名称为clean,是Delete类(作为Task的子类)的实例,并以闭包作为参数。 与标准的Groovy做法保持一致,在括号后面显示了闭包(Example A-16)。 Example A-16. The default clean task

task clean(type: Delete) {
    delete rootProject.buildDir
}

如果Groovy方法将Closure作为其最后一个参数,则通常在括号后添加闭包。

此处的实现在rootProject.buildDir上调用delete方法(同样带有可选的括号)。 rootProject属性的值是顶层项目,而buildDir的默认值是“ build”,因此此任务将删除顶层项目中的“ build”目录。 请注意,在顶层项目中调用clean也会在app子项目上调用它,这也会在该子项目中删除构建目录。

在Example A-17中,编译项是DSL的一部分,这意味着它的自变量在编译阶段应用。 尽管可以省略,但用括号显示了fileTree方法。 dir参数采用代表本地目录的字符串。 include参数采用文件模式的Groovy列表(方括号)。此处将会筛选出libs目录下,以“.jar”结尾的文件。

Example A-17. A file tree dependency

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

Custom Tasks

在Gradle构建文件中添加task块来实现自定义任务。 Gradle DSL支持一个任务块,用于定义您自己的自定义任务。 该API包含各种现成任务(例如,Copy,Delete, Wrapper和Exec),您只需设置属性即可使用它们。 例如,复制任务包括from和into属性,并且from块可以配置为排除指定的文件名模式。 要将所有APK复制到新文件夹中(不包括未签名或未对齐的APK),请将示例4-1中的任务添加到模块内部。

Example 4-1. Copy APKs to another folder

task copyApks(type: Copy) {
    from("$buildDir/outputs/apk") {
        exclude '**/*unsigned.apk', '**/*unaligned.apk'
    }
    into '../apks'
}

buildDir属性引用默认的构建目录(app / build),并且使用美元符号将其注入Groovy字符串(带双引号)。 复制任务的文档显示,from中的exclude块支持Ant样式的目录名,这意味着**匹配所有子目录。 如果您不想简单地配置现成的Gradle任务,则需要了解Gradle的配置和执行阶段之间的区别。 在配置阶段,Gradle根据其依赖关系构建DAG。 然后执行所需的任务。 在执行任何任务之前,必须先配置所有任务。

Gradle更喜欢声明性任务,例如Example 4-1任务,您可以在其中指定要执行的操作,但不指定如何执行。 但是,如果需要执行命令,请在您的Gradle任务中添加一个doLast块。

Example 4-2. A custom task to print available variants

task printVariantNames() {
    doLast {
        android.applicationVariants.all { variant ->
            println variant.name
        } 
    }
}

在任务中的在doLast块之前或之后的所有操作都将在配置期间运行。 doLast块中的代码本身在执行时运行。 Android插件添加了一个android属性,该属性又具有一个applicationVariants属性,该属性返回所有buildType / flavor组合。 在这种情况下,它们都将被打印到控制台。 applicationVariants属性仅可用于com.android.application插件。 Android库中提供了libraryVariants属性。 两者都可以使用testVariants属性。 要将所有调试版本安装到单个设备上(假设它们都具有唯一的applicationId值),请使用示例4-3中的任务。

Example 4-3. Install all the debug flavors on a single device

task installDebugFlavors() {
    android.applicationVariants.all { v ->
		if (v.name.endsWith('Debug')) { 
			String name = v.name.capitalize() 
			dependsOn "install$name"
		} 
    }
}

在这种情况下,dependsOn方法是配置过程的一部分,而不是执行的一部分。 每个变量名称(如friendlyDebug)都被大写(FriendlyDebug),然后将相应的安装任务(install FriendlyDebug)作为对依赖项添加到installDebugFlavors任务中。 结果是在配置过程中,将installArrogantDebug,installFriendlyDebug作为依赖项添加到installDebugFlavors。 因此,在命令行执行installDebugFlavors要求全部的三种版本都安装。

延长ADP超时时间 顺便说一句,尽管构建过程相对较快,但部署过程可能并非如此。 android标签支持adbOptions标签,以增加进程的部署超时时间。 示例4-5 更改部署超时时间 android { adbOptions { timeOutInMs = 30 * 1000 } } 这会将超时限制扩展到30秒。 如果遇到ShellCommandUnsensitiveException故障,请调整此值。

Adding Custom Tasks to the Build Process

如何在整个构建过程中调用自定义任务: 使用DependOn属性将您的任务插入到有向无环图(DAG)中。 在初始化阶段,Gradle根据任务的依赖性将任务组装为一个序列, 即DAG。例如,Gradle文档形成了Java插件的DAG,如图4-1所示。

在这里插入图片描述

“定向”术语是指每个依赖性箭头都朝一个方向移动。 “非循环”表示图中没有循环。 在流程中添加您自己的自定义任务意味着将您的任务插入到图形中的适当位置。 前面有例子将copyApks任务定义为将所有生成的APK复制到单独的目录中。为方便起见,示例4-6中再现了该任务。 Example 4-6. Copy APKs to another folder

task copyApks(type: Copy) {
    from("$buildDir/outputs/apk") {
        exclude '**/*unsigned.apk', '**/*unaligned.apk'
    }
    into '../apks'
}

但是,如果尚未生成APK,则该任务不是很有用。assemble task(组装任务)将构建APK,因此使其成为copyApks任务的依赖项,如示例4-7所示。 Example 4-7. Updated copy task to generate them first

task copyApks(type: Copy, dependsOn: assembleDebug) {
    from("$buildDir/outputs/apk") {
        exclude '**/*unsigned.apk', '**/*unaligned.apk'
    }
    into '../apks'
}

首先运行assembleDebug 对assembleDebug的依赖意味着所有调试APK将在复制任务运行之前生成。如果您还希望发布APK,则可以改用assemble。 如果希望每次执行构建时都运行copyApks任务,请使其成为构建任务的依赖项,如示例4-8所示。 Example 4-8. Making copyApks a part of the build

build.dependsOn copyApks

现在,运行构建任务还将把APK复制到单独的文件夹中。 您已使用正确的依赖项信息将copyApks任务插入到DAG中。 删除包含所有APK的生成的apks文件夹的操作可以类似的方式进行,但顶级Gradle构建文件已经有一个可以编辑的clean任务,如示例4-9所示。 Example 4-9. clean task generated by Android Studio

task clean(type: Delete) {
    delete rootProject.buildDir
}

Gradle中的delete任务接受文件或文件夹的列表,因此与其执行特殊任务来删除apks文件夹,不如修改该任务很容易,如示例4-10所示。 Example 4-10. Modified clean task to remove the apks directory

task clean(type: Delete) {
    delete rootProject.buildDir, 'apks'
}

可以使用此机制将任何自定义任务插入构建过程。

Excluding Tasks

使用-x标志排除单个任务。 通过修改任务图排除多个任务。 Gradle构建过程涉及许多按顺序执行的任务。 它们中的大多数依赖于流程中较早执行的任务,但是如果时间紧迫,可以将某些任务排除在外。 例如,lint任务对于确定项目与Google针对Android应用程序推荐的做法的紧密程度很有用,但是不必每次都运行它。 因此在运行构建时,请使用该标志跳过lint任务(或其他不想要的任务),如示例4-11所示。

Example 4-11. Excluding the lint task

> ./gradlew build -x lint

这会排除lint任务及其任何依赖项。任何需要其结果的任务也将不会 运行,因此请确保后面不需要您排除的任何任务。 唯一的问题是,如果项目涉及多个变体,则每个变体都有一个lint任务。原则上,可以手动将它们全部排除,但可能更希望将整个集合排除在构建之外。 当Gradle运行时,它会组装一个有向无环图,称为taskGraph(任务图)。可以通过gradle对象在构建文件中获取对它的引用。任务图形成后才能执行任何操作,可以使用whenReady属性判断任务图的状态。 可以在构建文件中按如下编写代码,如示例4-12所示。

Example 4-12. Disabling all tasks that start with the word lint

gradle.taskGraph.whenReady { graph ->
	graph.allTasks.findAll { it.name ==~ /lint.*/ }*.enabled = false
}

任务图的allTask​​s属性调用getAllTask​​s方法。这将返回任务类型的一个java.util.List。 Groovy向List添加了findAll方法,该方法仅返回满足提供的闭包的任务。在这种情况下,闭包表示访问每个任务的name属性,并检查它是否与正则表达式完全匹配。 结果是,所有名称以字母lint开头的任务都将其enabled属性设置为false,因此它们都不会运行。

可能不希望总是排除所有lint任务,可以在执行此操作之前检查是否已设置项目属性,如示例4-13所示。

Example 4-13. Only disable the lint tasks if the noLint property is set

gradle.taskGraph.whenReady { graph -> 
	if (project.hasProperty('noLint')) {
		graph.allTasks.findAll { it.name ==~ /lint.*/ }*.enabled = false 
	}
}

可以使用-P标志从命令行设置项目属性,如示例4-14所示。 Example 4-14. Setting a project property

> ./gradlew build -PnoLint | grep lint
:app:lintVitalArrogantRelease SKIPPED
:app:lintVitalFriendlyRelease SKIPPED
:app:lint SKIPPED

Custom Source Sets

在Gradle构建中,使用sourceSets属性来设置使用非标准目录的代码。 例4-15显示了Application子目录中的Gradle构建文件(请注意,示例对于主项目通常使用Application而不是app)

Example 4-15. Gradle build file with source sets

// The sample build uses multiple directories to 
// keep boilerplate and common code separate from 
// the main sample code.
List<String> dirs = [
'main', // main sample code; look here for the interesting stuff.
'common', // components that are reused by multiple samples
'template'] // boilerplate code that is generated by the sample template process

android {
// ... code omitted ...
    sourceSets {
        main {
            dirs.each { dir ->
                java.srcDirs "src/${dir}/java"
                res.srcDirs "src/${dir}/res"
            } 
        }
	    androidTest.setRoot('tests')
            androidTest.java.srcDirs = ['tests/src']
    } 
}

构建文件定义了一个名为dirs的List 来表示源目录。 Groovy支持列表的本地语法,使用方括号,其值之间用逗号分隔。在这种情况下,值是main,common和template。 在android块中,sourceSets属性用于将相关的源目录添加到classpath中。 着重于main块部分,Groovy的迭代器将列表中的每个条目提供给示例4-16中的闭包参数。

Example 4-16. Groovy each with a closure

dirs.each { dir ->
    java.srcDirs "src/${dir}/java"
    res.srcDirs "src/${dir}/res"
}

每个方法都来自Groovy。它遍历集合的每个元素,并将其传递给闭包参数。这里的闭包将每个元素标记为dir并将其替换为Groovy字符串。 标准项目定义了默认的源码树src / main / java和资源树src / main / res。这里使用srcDirs属性将其他目录添加到这些集合中。最终的结果是文件夹src / main / java,src / common / java和src / template / java都添加到了编译类路径中,并且文件夹src / main / res,src / common / res,和src / template / res都被视为资源目录。

示例4-17展示了Gradle构建文件不会将所有测试放在预定义的src / androidTest / java文件夹下,而是会更改该位置。

Example 4-17. Changing the root directory for tests

androidTest.setRoot('tests')
androidTest.java.srcDirs = ['tests/src']

现在,测试根目录是tests文件夹,并且测试本身放置在tests / src文件夹中。每个示例项目在Application目录下都有两个文件夹:src和tests,而tests文件夹包含一个名为src的子目录。 ActivityInstrumentation示例的基本项目布局包含一个Application目录,其目录的结构类似于示例4-18。

在这里插入图片描述
如您所见,Java代码位于src / main / java下,资源位于src / main / res下,测试位于所有位置的tests / src下。

Using Android Libraries

•Android库工程是需要Android依赖项的Java项目,例如Android API中的类或资源,或两者都有 •Gradle使用子目录进行多项目构建,其中每个子项目都添加到顶级settings.gradle文件中 •在Android Studio中,使用“New Module”向导中的“ Android Library”选项创建一个Android库工程 •库工程使用"com.android.library"插件 •应用程序构建文件使用project(“:library”)依赖项来访问 应用中的库类

看一个库工程的gradle文件:

apply plugin: 'com.android.library'
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    packagingOptions {
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/license.txt'
        exclude 'LICENSE.txt'
    }
    defaultConfig {
        minSdkVersion 16
	    targetSdkVersion 23
	    versionCode 1
	    versionName "1.0"
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "com.android.support:support-annotations:${supportLibVersion}"
    implementation project(':libuvccamera')
}

请注意packagingOptions块的使用。 可以排除出现在多个依赖项中的同名的冲突文件。

可以参考如何添加Java库依赖

参考文档

附两个参考文档供大家参考。

Android Plugin DSL Reference

Gradle User Manual