1 ,Android 構建系統
構建 APK 的過程是個相當復雜的過程,Android 構建系統需要將應用的資源文件和源文件一同打包到最終的 APK 文件中。應用可能會依賴一些外部庫,構建工具要靈活地管理這些依賴的下載、編譯、打包(包括合并、解決沖突、資源優(yōu)化)等過程。
應用的源碼可能包括 Java 、RenderScript、AIDL 以及 Native 代碼,構建工具需要分別處理這些語言的編譯打包過程,而有些時候我們需要生成不同配置(如不同 CPU 架構、不同 SDK 版本、不同應用商店配置等)的 APK 文件,構建工具就需要根據不同情況編譯打包不同的 APK。
總之,構建工具需要完成從工程資源管理到最終編譯、測試、打包、發(fā)布的幾乎所有工作。而 Android Studio 選擇了使用 Gradle,一個高級、靈活、強大的自動構建工具構建 Android 應用,利用 Gradle 和 Android Gradle 插件可以非常靈活高效地構建和測試 Android 應用了:?
Gradle和其Android插件可以幫助你自定義以下幾方面的構建配置:
AppExtension的屬性
aaptOptions:aapt是一個可以將資源文件編譯成二進制文件的工具。aaptOptions表示aapt工具設置的可選項參數。
adbExecutable:adb從編譯sdk時執(zhí)行
adbOptions:adb的可選項參數
applicationVariants:應用變體列表
==buildToolsVersion==:構建工具版本(必要的)
buildTypes:構建類型(一般是release和debug,還可以自定義)
compileOptions:編譯可選項參數
==compileSdkVersion==:編譯sdk版本(必要的)
dataBinding:Data Binding可選項參數(關于DataBinding的使用)
defualtConfig:默認配置,對于所有的打包項目
defualtPublishConfig:默認是release。使用參考
dexOptions:Dex可選項參數。
externalNativeBuild:native編譯支持。參考
flavorDimensionList:
generatePureSplits:是否拆成多個APK
jacoco:JaCoCo可選項參數
lintOptions:Lint工具可選項參數
ndkDirectory:ndk目錄(一般在local.properties中)
packagingOptions:packaging的可選參數
productFlavors:項目所有flavor
publishNonDefualt:不僅僅使用默認的publish artifacts。可參考defualtPublishConfig。
resourcePrefix:創(chuàng)建新資源時使用的前綴。
sdkDirectory:sdk目錄(一般在local.properties中)
signingConfigs:簽名文件的可選項參數
sourceSets:資源文件目錄指定(Android中有自己的AndroidSourceSets,這個一般用于assets,jin等目錄)
splits:splits類型。
testBuildType:測試構建類型
testOptions:測試可選項參數
testVariants:測試變體
unitTestVariants:單元測試變體
variantFilter:變體過濾器
而這些構建配置要體現在不同的構建配置文件中,典型的Android應用結構為:?
1.1 Gradle Settings 文件
位于工程根目錄的?settings.gradle?文件用于告訴Gradle構建應用時需要包含哪些 module,如 :
include':app',':lib'
對于setting.gradle中也可以寫代碼的,可以參考:
https://kymjs.com/code/2018/02/25/01/
1.2 頂層 Build 文件
位于工程根目錄的?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{
compileSdkVersionrootProject.ext.compileSdkVersion
...
}
...
dependencies {
compile"com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
...
}
自定義BuildConfig
實際開發(fā)中服務器可能有正式環(huán)境和測試環(huán)境,gradle可以通過buildConfigField來配置。
defaultConfig {
? ? ? ? buildConfigField 'String','API_SERVER_URL','"http://url/"'? ? }
buildConfigField 一共有3個參數,第一個是數據類型,和Java的類型是對等的;第二個參數是常量名,這里是API_SERVER_URL;第三個參數就是你要配置的值。
defualtConfig{}
defaultConfig{}是所有flavor都共有的配置。英文解釋:Thedefaultconfiguration, inherited by all product flavors(ifanyaredefined).defaultConfig的使用:defaultConfig {? ? ? ? applicationId"com.example.zhang.demo"minSdkVersion15targetSdkVersion25versionCode1versionName"1.0"testInstrumentationRunner"android.support.test.runner.AndroidJUnitRunner"}
如果項目中包含多個Module,可以將共有的minSdkVersion和targetSdkVersion抽取到Project中的build.gradle文件中。具體細節(jié)下一章節(jié)。
1.3 Module 級 Build 文件
位于每個?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 特性
*/
compileSdkVersion26
/**
* buildToolsVersion 用來指定 SDK 所有構建工具、命令行工具、以及 Gradle 用來構建應用的編譯器版本
* 你需要使用 SDK Manager 下載好該版本的構建工具
* 在 3.0.0 或更高版本的插件中。該屬性是可選的,插件會使用推薦的版本
*/
buildToolsVersion"27.0.3"
/**
* defaultConfig 代碼塊包含所有構建變體(build variants)默認使用的配置,也可以重寫 main/AndroidManifest.xml 中的屬性
* 當然,你也可以在 product flavors(產品風味)中重寫其中一些屬性
*/
defaultConfig {
/**
* applicationId 是發(fā)布時的唯一指定包名,盡管如此,你還是需要在 main/AndroidManifest.xml 文件中
* 定義值是該包名的 package 屬性
*/
applicationId'com.example.myapp'
// 定義可以運行該應用的最小 API level
minSdkVersion15
// 指定測試該應用的 API level
targetSdkVersion26
// 定義應用的版本號
versionCode1
// 定義用戶友好型的版本號描述
versionName"1.0"
}
/**
* buildTypes 代碼塊用來配置多個構建類型,構建系統默認定義了兩個構建類型: debug 和 release
* debug 構建類型默認不顯式聲明,但它包含調試工具并使用 debug key 簽名
* release 構建類型默認應用了混淆配置
*/
buildTypes {
release {
minifyEnabledtrue
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
enablefalse
// 構建多個 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'
}
1.4 Gradle 屬性文件
位于工程根目錄的?gradle.properties?文件和?local.properties?用來指定 Gradle 構建工具自己的設置。?
gradle.properties?文件可以用來配置工程的 Gradle 設置,如 Gradle 守護進程的最大堆棧大小
local.properties?文件用來配置構建系統的本地環(huán)境屬性,如 SDK 安裝路徑,由于該文件內容是 Android Studio 自動生成的且與本地開發(fā)環(huán)境有關,所以你不要更改更不要上傳到版本控制系統中。
2
Gradle 概述
Gradle 是專注于靈活性和性能的開源自動構建工具。Gradle 的構建腳本使用?Groovy?或?Kotlin?語言。Gradle 構建工具的優(yōu)勢在于:
高度可定制?- Gradle 是以最基本的可定制和可擴展的方式模塊化的
更快?- Gradle 通過重用之前執(zhí)行的輸出、只處理更改的輸入以及并行執(zhí)行 task 的方式加快構建速度
更強大?- Gradle 支持跨多語言和平臺,是 Android 官方構建工具,支持很多主流 IDE,包括 Android Studio、Eclipse、IntelliJ IDEA、Visual Studio 2017 以及 XCode,將來會支持更多語言和平臺
2.1 Gradle 的依賴管理
依賴管理(Dependency management)是每個構建系統的關鍵特征,Gradle 提供了一個既容易理解又其他依賴方法兼容的一流依賴管理系統,如果你熟悉 Maven 或 Ivy 用法,那么你肯定樂于學習 Gradle,因為 Gradle 的依賴管理和兩者差不多但比兩者更加靈活。
Gradle 依賴管理的優(yōu)勢包括:
傳遞依賴管理 - Gradle 讓你可以完全控制工程的依賴樹
支持非托管依賴 - 如果你只依賴版本控制系統或共享磁盤中的單個文件,Gradle 提供了強大的功能支持這種依賴
支持個性化依賴定義 - Gradle 的 Module Dependencies 讓你可以在構建腳本中描述依賴層級
為依賴解析提供完全可定制的方法 - Gradle 讓你可以自定義依賴解析規(guī)則以便讓依賴可以方便地替換
完全兼容Maven和Ivy - 如果你已經定義了 Maven POM 或 Ivy 文件,Gradle 可以通過相應的構建工具無縫集成
可以與已存在的依賴管理系統集成 - Gradle 完全兼容 Maven 和 Ivy 倉庫,所以如果你使用 Archiva、Nexus 或 Artifactory,Gradle 可以100%兼容所有的倉庫格式
2.2 常用的依賴配置
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?依賴配置有幾個明顯的優(yōu)勢:
依賴不會泄漏到消費者的編譯 classpath 中,所以你也就不會無意中依賴一個傳遞依賴了
由于 classpath 大小的減少編譯也會更快
當?implementation?的依賴改變時,消費者不需要重新編譯,要重新編譯的很少
更清潔地發(fā)布,當結合新的 maven-publish 插件使用時,Java librariy 會生成一個 POM 文件來精確地區(qū)分編譯這個 librariy 需要的東西和運行這個 librariy 需要的東西
那到底什么時候使用 API 依賴什么時候使用 Implementation 依賴呢?
這里有幾個簡單的規(guī)則:?
一個 API 是 library binary 接口暴露的類型,通常被稱為 ABI (Application Binary Interface),這包括但不限于:
父類或接口用的類型
公共方法中參數用到的類型,包括泛型類型(公共指的是對編譯器可見的 public,protected 和 package private)
public 字段用到的類型
public 注解類型
相反,下面列表重要到的所有類型都與 ABI 無關,因此應該使用 implementation 依賴:
只用在方法體內的類型
只出現在 private 成員的類型
只出現在內部類中的類型
例如
// The following types can appear anywhere in the code
// but say nothing about API or implementation usage
importorg.apache.commons.httpclient.*;
importorg.apache.commons.httpclient.methods.*;
importorg.apache.commons.lang3.exception.ExceptionUtils;
importjava.io.IOException;
importjava.io.UnsupportedEncodingException;
publicclassHttpClientWrapper{
privatefinalHttpClient client;// private member: implementation details
// HttpClient is used as a parameter of a public method
// so "leaks" into the public API of this component
publicHttpClientWrapper(HttpClient client){
this.client = client;
}
// public methods belongs to your API
publicbyte[] doRawGet(String url) {
GetMethod method =newGetMethod(url);
try{
intstatusCode = doGet(method);
returnmethod.getResponseBody();
}catch(Exception e) {
ExceptionUtils.rethrow(e);// this dependency is internal only
}finally{
method.releaseConnection();
}
returnnull;
}
// GetMethod is used in a private method, so doesn't belong to the API
privateintdoGet(GetMethod method)throwsException{
intstatusCode = client.executeMethod(method);
if(statusCode != HttpStatus.SC_OK) {
System.err.println("Method failed: "+ method.getStatusLine());
}
returnstatusCode;
}
}
其中,public 構造器?HttpClientWrapper?使用了?HttpClient?參數暴露給了使用者,所以屬于 API 依賴。而 ExceptionUtils 只在方法體中出現了,所以屬于 implementation 依賴。
所以 build 文件這樣寫:
dependencies{
api'commons-httpclient:commons-httpclient:3.1'
implementation'org.apache.commons:commons-lang3:3.5'
}
因此,應該優(yōu)先選擇使用 implementation 依賴:缺少一些類型將會直接導致消費者的編譯錯誤,可以通過移除這些類型或改成 API 依賴解決。?
compileOnly?依賴配置會告訴 Gradle 將依賴只添加到編譯 classpath 中(不會添加到構建輸出中),在你創(chuàng)建一個 Android library module 且在編譯時需要這個依賴時使用?compileOnly?是個很好的選擇。但這并不能保證運行時良好,也就是說,如果你使用這個配置,那么你的 library module 必須包含一個運行時條件去檢查依賴是否可用,在不可用的時候仍然可以優(yōu)雅地改變他的行為來正常工作,這有助于減少最終 APK 的大小(通過不添加不重要的transient依賴)。?
runtimeOnly?依賴配置告訴 Gradle 將依賴只添加到構建輸出中,只在運行時使用,也就是說這個依賴不添加到編譯 classpath 中。?
此外,debugImplementation?會使依賴僅在 module 的 debug 變體中可用,而如?testImplementation、androidTestImplementation?等依賴配置可以更好地處理測試相關依賴。
2.3 聲明依賴
2.3.1 聲明 binary 依賴
現在的軟件工程很少單獨地構建代碼,因為現在的工程通常為了重用已存在且久經考驗的功能而引入外部庫,因此被稱為?binary dependencies。Gradle 會解析 binary 依賴然后從專門的遠程倉庫中下載并存到 cache 中以避免不必要的網絡請求:?
每個 artifact 在倉庫中的 coordinate 都會包含?groupId?、?artifactId?和?version?三個元素,如在一個使用 Spring 框架的 Java 工程中添加一個編譯時依賴:
apply plugin:'java-library'
repositories {
mavenCentral()
}
dependencies {
implementation'org.springframework:spring-web:5.0.2.RELEASE'
}
Gradle 會從?Maven中央倉庫https://search.maven.org/?解析并下載這個依賴(包括它的傳遞依賴),然后使用它去編譯 Java 源碼,其中的?version?屬性是指定了具體版本,表明總是使用這個具體的依賴不再更改。
當然,如果你總是想使用最新版本的 binary 依賴,你可以使用動態(tài)的?version,Gradle 默認會緩存 24 小時:
implementation'org.springframework:spring-web:5.+'
有些情況開發(fā)團隊在完全完成新版本的開發(fā)之前為了讓使用者能體驗最新的功能特色,會提供一個 changing version,在 Maven 倉庫中 changing version 通常被稱作 snapshot version,而 snapshot version會包含-SNAPSHOT后綴,如:
implementation'org.springframework:spring-web:5.0.3.BUILD-SNAPSHOT'
2.3.2 聲明文件依賴
工程有時候不會依賴 binary 倉庫中的庫,而是把依賴放在共享磁盤或者版本控制系統的工程源碼中(JFrog Artifactory 或 Sonatype Nexus 可以存儲解析這種外部依賴),這種依賴被稱為?file dependencies?,因為它們是以不涉及任何 metadata(如傳遞依賴、作者)的文件形式存在的。如我們添加來自?ant、libs?和?tools?目錄的文件依賴:
configurations{
antContrib
externalLibs
deploymentTools
}
dependencies {
antContribfiles('ant/antcontrib.jar')
externalLibs files('libs/commons-lang.jar','libs/log4j.jar')
deploymentTools fileTree(dir:'tools', include:'*.exe')
}
2.3.3 聲明工程依賴
現在的工程通常把組件獨立成 module 以提高可維護性及防止強耦合,這些 module 可以定義相互依賴以重用代碼,而 Gradle 可以管理這些 module 間的依賴。由于每個 module 都表現成一個 Gradle project,這種依賴被稱為?project dependencies?。在運行時,Gradle 構建會自動確保工程的依賴以正確的順序構建并添加到 classpath 中編譯。
project(':web-service') {
dependencies {
implementation project(':utils')
implementation project(':api')
}
}
3
Gradle 常用配置
強制所有的 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{
multiDexKeepFilefile('multidex-config.txt')
...
}
}
}
或者新建?multidex-config.pro?文件,使用 Proguard 語法指定放入主 dex 文件中的類,如:
-keepclasscom.example.** { *; }
android{
buildTypes{
release{
multiDexKeepProguardfile('multidex-config.pro')
...
}
}
}
參考: https://www.cnblogs.com/xinmengwuheng/p/5797048.html
https://blog.csdn.net/qq_33689414/article/details/53152212
文章來至:http://mp.weixin.qq.com/s/nDnw3fBtXSD8RHZXdvRsmA