构建 APK 的过程是个至关复杂的过程,Android 构建系统须要将应用的资源文件和源文件一同打包到最终的 APK 文件中。应用可能会依赖一些外部库,构建工具要灵活地管理这些依赖的下载、编译、打包(包括合并、解决冲突、资源优化)等过程。应用的源码可能包括 Java 、RenderScript、AIDL 以及 Native 代码,构建工具须要分别处理这些语言的编译打包过程,而有些时候咱们须要生成不一样配置(如不一样 CPU 架构、不一样 SDK 版本、不一样应用商店配置等)的 APK 文件,构建工具就须要根据不一样状况编译打包不一样的 APK。总之,构建工具须要完成从工程资源管理到最终编译、测试、打包、发布的几乎全部工做。而 Android Studio 选择了使用 Gradle,一个高级、灵活、强大的自动构建工具构建 Android 应用,利用 Gradle 和 Android Gradle 插件能够很是灵活高效地构建和测试 Android 应用了:
html
Build Types : Build types(构建类型)定义了一些构建、打包应用时 Gradle 要用到的属性,主要用于不一样开发阶段的配置,如 debug 构建类型要启用 debug options 并用 debug key 签名,release 构建类型要删除无效资源、混淆源码以及用 release key 签名java
Product Flavors : Product flavors(产品风味)定义了你要发布给用户的不一样版本,好比免费版和付费版。你能够在共享重用通用版本功能的时候自定义 product flavors 使用不一样的代码和资源,Product flavors 是可选的因此你必须手动建立android
Build Variants : build variant(构建变体)是 build type 和 product flavor 的交叉输出(如free-debug、free-release、paid-debug、paid-release),Gradle 构建应用要用到这个配置。也就是说添加 build types 或 product flavors 会相应的添加 build variantsgit
Manifest Entries : 你能够在 build variant 配置中指定 manifest 文件中的某个属性值(如应用名、最小 SDK 版本、target SDK 版本),这个值会覆盖 manifest 文件中原来的属性值web
Dependencies : 构建系统会管理工程用要用到的本地文件系统和远程仓库的依赖。spring
Signing : 构建系统会让你指定签名设置以便在构建时自动给你的 APK 签名,构建工具默认会使用自动生成的 debug key 给 debug 版本签名,你也能够生成本身的 debug key 或 release key 使用。apache
ProGuard : 构建系统让你能够为每一个构建变体指定不一样的混淆规则文件api
Multiple APK Support : 构建系统让你能够为不一样屏幕密度或 Application Binary Interface (ABI)的设备生成包含其所须要的资源的 APK 文件,如为 x86 CPU 架构的设备生成只包含该 x86 架构 so 库的 APK 文件。缓存
而这些构建配置要体如今不一样的构建配置文件中,典型的Android应用结构为:
bash
位于工程根目录的 settings.gradle
文件用于告诉Gradle构建应用时须要包含哪些 module,如 :
include ':app', ':lib'
复制代码
位于工程根目录的 build.gradle
文件用于定义工程全部 module 的构建配置,通常顶层 build 文件使用 buildscript
代码块定义 Gradle 的 repositories 和 dependencies,如自动生成的顶层 build 文件:
/** * buildscript代码块用来配置Gradle本身的repositories和dependencies,因此不能包含modules使用的dependencies */
buildscript {
/** * repositories 代码块用来配置 Gradle 用来搜索和下载依赖的仓库 * Gradle 默认是支持像 JCenter,Maven Central,和 Ivy 远程仓库的,你也可使用本地仓库或定义你本身的远程仓库 * 下面的代码定义了 Gradle 用于搜索下载依赖的 JCenter 仓库和 Google 的 Maven 仓库 */
repositories {
google()
jcenter()
}
/** * dependencies 代码块用来配置 Gradle 用来构建工程的依赖,下面的代码表示添加一个 * Gradle 的 Android 插件做为 classpath 依赖 */
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
/** * allprojects 代码块用来配置工程中全部 modules 都要使用的仓库和依赖 * 可是你应该在每一个 module 级的 build 文件中配置 module 独有的依赖。 * 对于一个新工程,Android Studio 默认会让全部 modules 使用 JCenter 仓库和 Google 的 Maven 仓库 */
allprojects {
repositories {
google()
jcenter()
}
}
复制代码
除了这些,你还可使用 ext
代码块在这个顶层 build 文件中定义工程级(工程中全部 modules 共享)的属性:
buildscript {...}
allprojects {...}
ext {
// 如让全部 modules 都使用相同的版本以免冲突
compileSdkVersion = 26
supportLibVersion = "27.0.2"
...
}
...
复制代码
每一个 module 的 build 文件使用 rootProject.ext.property_name
语法使用这些属性便可:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
...
}
...
dependencies {
compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
...
}
复制代码
位于每一个 project/module/
目录的 build.gradle
文件用于定义该 module 本身的构建配置,同时你也能够重写顶层 build 文件或 main app manifest 的配置:
/** * 为这个构建应用 Gradle 的 Android 插件,以便 android 代码块中 Android 特定的构建配置可用 */
apply plugin: 'com.android.application'
/** * android 代码块用来配置 Android 特定的构建配置 */
android {
/** * compileSdkVersion 用来指定 Gradle 用来编译应用的 Android API level,也就是说 * 你的应用可使用这个 API level 及更低 API level 的 API 特性 */
compileSdkVersion 26
/** * buildToolsVersion 用来指定 SDK 全部构建工具、命令行工具、以及 Gradle 用来构建应用的编译器版本 * 你须要使用 SDK Manager 下载好该版本的构建工具 * 在 3.0.0 或更高版本的插件中。该属性是可选的,插件会使用推荐的版本 */
buildToolsVersion "27.0.3"
/** * defaultConfig 代码块包含全部构建变体(build variants)默认使用的配置,也能够重写 main/AndroidManifest.xml 中的属性 * 固然,你也能够在 product flavors(产品风味)中重写其中一些属性 */
defaultConfig {
/** * applicationId 是发布时的惟一指定包名,尽管如此,你仍是须要在 main/AndroidManifest.xml 文件中 * 定义值是该包名的 package 属性 */
applicationId 'com.example.myapp'
// 定义能够运行该应用的最小 API level
minSdkVersion 15
// 指定测试该应用的 API level
targetSdkVersion 26
// 定义应用的版本号
versionCode 1
// 定义用户友好型的版本号描述
versionName "1.0"
}
/** * buildTypes 代码块用来配置多个构建类型,构建系统默认定义了两个构建类型: debug 和 release * debug 构建类型默认不显式声明,但它包含调试工具并使用 debug key 签名 * release 构建类型默认应用了混淆配置 */
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
/** * 因为 product flavors 必须属于一个已命名的 flavor dimension,因此你至少须要定义一个 flavor dimension * 如定义一个等级和最小 api 的 flavor dimension */
flavorDimensions "tier", "minApi"
productFlavors {
free {
// 这个 product flavor 属于 "tier" flavor dimension
// 若是只有一个 dimension 那么这个属性就是可选的
dimension "tier"
...
}
paid {
dimension "tier"
...
}
minApi23 {
dimension "minApi"
...
}
minApi18 {
dimension "minApi"
...
}
}
/** * 你可使用 splits 代码块配置为不一样屏幕分辨率或 ABI 的设备生成仅包含其支持的代码和资源的 APK * 同时你须要配置 build 文件以便每一个 APK 使用不一样的 versionCode */
splits {
density {
// 启用或禁用构建多个 APK
enable false
// 构建多个 APK 时排除这些分辨率
exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
}
}
}
/** * 该 module 级 build 文件的 dependencies 代码块仅用来指定该 module 本身的依赖 */
dependencies {
implementation project(":lib")
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.0.2'
}
复制代码
位于工程根目录的 gradle.properties
文件和 local.properties
用来指定 Gradle 构建工具本身的设置。
gradle.properties
文件能够用来配置工程的 Gradle 设置,如 Gradle 守护进程的最大堆栈大小
local.properties
文件用来配置构建系统的本地环境属性,如 SDK 安装路径,因为该文件内容是 Android Studio 自动生成的且与本地开发环境有关,因此你不要更改更不要上传到版本控制系统中。
Gradle 是专一于灵活性和性能的开源自动构建工具。Gradle 的构建脚本使用 Groovy 或 Kotlin 语言。Gradle 构建工具的优点在于:
学习 Gradle 的途径有不少:
依赖管理(Dependency management)是每一个构建系统的关键特征,Gradle 提供了一个既容易理解又其余依赖方法兼容的一流依赖管理系统,若是你熟悉 Maven 或 Ivy 用法,那么你确定乐于学习 Gradle,由于 Gradle 的依赖管理和二者差很少但比二者更加灵活。Gradle 依赖管理的优点包括:
Java Library插件 继承自 Java插件,但 Java Library 插件与 Java 插件最主要的不一样是 Java Library 插件引入了将 API 暴露给消费者(使用者)的概念,一个 library 就是一个用来供其余组件(component)消费的 Java 组件。 Java Library 插件暴露了两个用于声明依赖的 Configuration(依赖配置): api
和 implementation
。出如今 api
依赖配置中的依赖将会传递性地暴露给该 library 的消费者,并会出如今其消费者的编译 classpath 中。而出如今 implementation
依赖配置中的依赖将不会暴露给消费者,也就不会泄漏到消费者的编译 classpath 中。所以,api
依赖配置应该用来声明library API 使用的依赖,而 implementation
依赖配置应该用来声明组件内部的依赖。implementation
依赖配置有几个明显的优点:
implementation
的依赖改变时,消费者不须要从新编译,要从新编译的不多那到底何时使用 API 依赖何时使用 Implementation 依赖呢?这里有几个简单的规则: 一个 API 是 library binary 接口暴露的类型,一般被称为 ABI (Application Binary Interface),这包括但不限于:
相反,下面列表重要到的全部类型都与 ABI 无关,所以应该使用 implementation 依赖:
例如
// The following types can appear anywhere in the code
// but say nothing about API or implementation usage
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class HttpClientWrapper {
private final HttpClient client; // private member: implementation details
// HttpClient is used as a parameter of a public method
// so "leaks" into the public API of this component
public HttpClientWrapper(HttpClient client) {
this.client = client;
}
// public methods belongs to your API
public byte[] doRawGet(String url) {
GetMethod method = new GetMethod(url);
try {
int statusCode = doGet(method);
return method.getResponseBody();
} catch (Exception e) {
ExceptionUtils.rethrow(e); // this dependency is internal only
} finally {
method.releaseConnection();
}
return null;
}
// GetMethod is used in a private method, so doesn't belong to the API
private int doGet(GetMethod method) throws Exception {
int statusCode = client.executeMethod(method);
if (statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: " + method.getStatusLine());
}
return statusCode;
}
}
复制代码
其中,public 构造器 HttpClientWrapper
使用了 HttpClient
参数暴露给了使用者,因此属于 API 依赖。而 ExceptionUtils 只在方法体中出现了,因此属于 implementation 依赖。因此 build 文件这样写:
dependencies {
api 'commons-httpclient:commons-httpclient:3.1'
implementation 'org.apache.commons:commons-lang3:3.5'
}
复制代码
所以,应该优先选择使用 implementation 依赖:缺乏一些类型将会直接致使消费者的编译错误,能够经过移除这些类型或改为 API 依赖解决。 compileOnly
依赖配置会告诉 Gradle 将依赖只添加到编译 classpath 中(不会添加到构建输出中),在你建立一个 Android library module 且在编译时须要这个依赖时使用 compileOnly
是个很好的选择。但这并不能保证运行时良好,也就是说,若是你使用这个配置,那么你的 library module 必须包含一个运行时条件去检查依赖是否可用,在不可用的时候仍然能够优雅地改变他的行为来正常工做,这有助于减小最终 APK 的大小(经过不添加不重要的transient依赖)。 runtimeOnly
依赖配置告诉 Gradle 将依赖只添加到构建输出中,只在运行时使用,也就是说这个依赖不添加到编译 classpath 中。 此外,debugImplementation
会使依赖仅在 module 的 debug 变体中可用,而如 testImplementation
、androidTestImplementation
等依赖配置能够更好地处理测试相关依赖。
如今的软件工程不多单独地构建代码,由于如今的工程一般为了重用已存在且久经考验的功能而引入外部库,所以被称为 binary dependencies。Gradle 会解析 binary 依赖而后从专门的远程仓库中下载并存到 cache 中以免没必要要的网络请求:
apply plugin: 'java-library'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework:spring-web:5.0.2.RELEASE'
}
复制代码
Gradle 会从 Maven中央仓库 解析并下载这个依赖(包括它的传递依赖),而后使用它去编译 Java 源码,其中的 version 属性是指定了具体版本,代表老是使用这个具体的依赖再也不更改。 固然,若是你老是想使用最新版本的 binary 依赖,你可使用动态的 version,Gradle 默认会缓存 24 小时:
implementation 'org.springframework:spring-web:5.+'
复制代码
有些状况开发团队在彻底完成新版本的开发以前为了让使用者能体验最新的功能特点,会提供一个 changing version,在 Maven 仓库中 changing version 一般被称做 snapshot version,而 snapshot version会包含-SNAPSHOT
后缀,如:
implementation 'org.springframework:spring-web:5.0.3.BUILD-SNAPSHOT'
复制代码
工程有时候不会依赖 binary 仓库中的库,而是把依赖放在共享磁盘或者版本控制系统的工程源码中(JFrog Artifactory 或 Sonatype Nexus 能够存储解析这种外部依赖),这种依赖被称为 file dependencies ,由于它们是以不涉及任何 metadata(如传递依赖、做者)的文件形式存在的。如咱们添加来自 ant
、libs
和 tools
目录的文件依赖:
configurations {
antContrib
externalLibs
deploymentTools
}
dependencies {
antContrib files('ant/antcontrib.jar')
externalLibs files('libs/commons-lang.jar', 'libs/log4j.jar')
deploymentTools fileTree(dir: 'tools', include: '*.exe')
}
复制代码
如今的工程一般把组件独立成 module 以提升可维护性及防止强耦合,这些 module 能够定义相互依赖以重用代码,而 Gradle 能够管理这些 module 间的依赖。因为每一个 module 都表现成一个 Gradle project,这种依赖被称为 project dependencies 。在运行时,Gradle 构建会自动确保工程的依赖以正确的顺序构建并添加到 classpath 中编译。
project(':web-service') {
dependencies {
implementation project(':utils')
implementation project(':api')
}
}
复制代码
强制全部的 android support libraries 使用相同的版本:
configurations.all {
resolutionStrategy {
eachDependency { details ->
// Force all of the primary support libraries to use the same version.
if (details.requested.group == 'com.android.support' &&
details.requested.name != 'multidex' &&
details.requested.name != 'multidex-instrumentation') {
details.useVersion supportLibVersion
}
}
}
}
复制代码
更改生成的 APK 文件名:
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "${variant.name}-${variant.versionName}.apk"
}
}
复制代码
若是开启了 Multidex 后在 Android 5.0 如下设备上出现了 java.lang.NoClassDefFoundError
异常,多是因为构建工具没能把某些依赖库代码放进主 dex 文件中,这时就须要手动指定还有哪些要放入主 dex 文件中的类。在构建类型中指定 multiDexKeepFile
或 multiDexKeepProguard
属性便可:
在 build 文件同级目录新建 multidex-config.txt
文件,文件的每一行为类的全限定名,如:
com/example/MyClass.class
com/example/MyOtherClass.class
复制代码
android {
buildTypes {
release {
multiDexKeepFile file('multidex-config.txt')
...
}
}
}
复制代码
或者新建 multidex-config.pro
文件,使用 Proguard 语法指定放入主 dex 文件中的类,如:
-keep class com.example.** { *; }
复制代码
android {
buildTypes {
release {
multiDexKeepProguard file('multidex-config.pro')
...
}
}
}
复制代码