構建 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')
...
}
}
}
複製代碼