系列目錄
1.【Gradle深入淺出】——初識(shí)Gradle
2.【Gradle深入淺出】——Gradle基礎(chǔ)概念
3.【Gradle深入淺出】——Android Gradle Plugin 基礎(chǔ)概念
4.【Gradle深入淺出】——Gradle配置(一)
5.【Gradle深入淺出】——Gralde配置(二)
一、前言
前一篇博客分析了Gralde配置的三個(gè)文件local.properties掖棉、gradle.properties、setting.gradle坎拐,本篇博客開(kāi)始分析gradle的工程配置,這些配置參數(shù)都是影響我們平時(shí)實(shí)際工程打包的參數(shù)狮崩,所以還是很有必要了解的其监。首先我們要知道gradle打包的核心配置在build.gradle文件,而關(guān)于build.gradle父工程和子工程的差異這里就不再說(shuō)了腻暮,前面的博客已有介紹,所以有了前面博客的介紹毯侦,我們知道我們工程的打包首先要看父項(xiàng)目的打包配置的build.gralde.
二哭靖、Root工程的Build.gradle配置
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
maven {
url('http://xxxxxx')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
我們初始化創(chuàng)建的工程的build.gradle還是比較清晰簡(jiǎn)單的,首先看到的是buildscript
,看到內(nèi)部的內(nèi)容我們一開(kāi)始可能會(huì)很困惑侈离,這里為什么有repositories
和dependencies
的定義试幽,我們每個(gè)子工程的build.gradle中也有關(guān)于repositories
和dependencies
的定義,這兩個(gè)有什么區(qū)別呢卦碾?
這里buildScript正如名字的含義那樣铺坞,表示的是gradle腳本編譯過(guò)程中需要的資源,比如我們?cè)趃radle打包時(shí)用到了一些外部依賴項(xiàng)或者第三方插件洲胖,那么這時(shí)我們就需要在buildScript中聲明相關(guān)的repo倉(cāng)庫(kù)地址济榨,并且在dependencies中聲明相應(yīng)的依賴項(xiàng)。而在其他工程中的build.gradle中聲明的repo和denpencies表示的是打包的項(xiàng)目所依賴的資源宾濒,也就是我們工程用到的依賴的資源腿短。
repositories表示的是dependencies的repo地址,我們看下配置的源碼绘梦,我們一般會(huì)配置google()
,jcenter()
,maven
這幾種,簡(jiǎn)單看下源碼赴魁。
/**
* Adds a repository which looks in Google's Maven repository for dependencies.
* <p>
* The URL used to access this repository is {@literal "https://dl.google.com/dl/android/maven2/"}.
* <p>
* Examples:
* <pre autoTested="">
* repositories {
* google()
* }
* </pre>
*
* @return the added resolver
* @since 4.0
*/
@Incubating
MavenArtifactRepository google();
/**
* Adds a repository which looks in Bintray's JCenter repository for dependencies.
* <p>
* The URL used to access this repository is {@literal "https://jcenter.bintray.com/"}.
* The behavior of this repository is otherwise the same as those added by {@link #maven(org.gradle.api.Action)}.
* <p>
* Examples:
* <pre autoTested="">
* repositories {
* jcenter()
* }
* </pre>
*
* @return the added resolver
* @see #jcenter(Action)
*/
MavenArtifactRepository jcenter();
/**
* Adds a repository which looks in the local Maven cache for dependencies. The name of the repository is
* {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}.
*
* <p>Examples:</p>
* <pre autoTested="">
* repositories {
* mavenLocal()
* }
* </pre>
* <p>
* The location for the repository is determined as follows (in order of precedence):
* </p>
* <ol>
* <li>The value of system property 'maven.repo.local' if set;</li>
* <li>The value of element <localRepository> of <code>~/.m2/settings.xml</code> if this file exists and element is set;</li>
* <li>The value of element <localRepository> of <code>$M2_HOME/conf/settings.xml</code> (where <code>$M2_HOME</code> is the value of the environment variable with that name) if this file exists and element is set;</li>
* <li>The path <code>~/.m2/repository</code>.</li>
* </ol>
*
* @return the added resolver
*/
MavenArtifactRepository mavenLocal();
所以可以看到google(),jcenter(),mavenLocal()就表示我們引入了三個(gè)地址的倉(cāng)庫(kù)卸奉,下載denpendencies庫(kù)的時(shí)候就會(huì)按照順序從這幾個(gè)url中找是否有對(duì)應(yīng)的庫(kù),如果有則會(huì)下載下來(lái)颖御。
那么如何指定本地路徑作為repo地址呢榄棵?我們?cè)陂_(kāi)發(fā)gradle插件的時(shí)候可能會(huì)遇到這個(gè)問(wèn)題。
maven{
url 'file://E:/libs/localMaven/'
}
接著往下看潘拱,會(huì)看到脫離了buildScript的代碼塊疹鳄,也就是說(shuō)明現(xiàn)在的配置不是用于gradle編譯使用了,而且真正打包使用的配置了芦岂。
allprojects {
repositories {
google()
jcenter()
}
}
正如名字allprojects
一樣瘪弓,這個(gè)就是我們統(tǒng)一為所有的子project配置的地方,具體為什么語(yǔ)法糖是這樣的禽最,后面會(huì)專門開(kāi)一篇博客講一下關(guān)于這里的語(yǔ)法糖腺怯,這里如果有困惑的我們其實(shí)可以把代碼改成這樣袱饭。
allprojects {
println "this.name:" + name
repositories {
google()
jcenter()
}
}
這樣我們就會(huì)發(fā)現(xiàn)這其實(shí)是一個(gè)for循環(huán),會(huì)將我們依賴的子project都打印出來(lái)呛占,所以這里配置的所有配置都會(huì)在全局的project生效虑乖,而如果子project在build.gralde的配置會(huì)覆蓋這里的配置,所以我們?nèi)绻鋈峙渲昧缆牵涂梢栽谶@里進(jìn)行統(tǒng)一配置疹味。
接下來(lái)就是一個(gè)統(tǒng)一的配置,可以看到定義了一個(gè)task帜篇,關(guān)于task的定義前面已經(jīng)講到過(guò)佛猛,這里可以看到定義了一個(gè)clean
的task,可以看到clean這個(gè)task繼承了系統(tǒng)的delete的task坠狡,所以我們可以在task內(nèi)部用到delete的功能继找,而clean的作用也很簡(jiǎn)單,就是把/projectDir/build
目錄給清理了逃沿,所以我們有時(shí)候在編譯打包時(shí)有時(shí)候會(huì)發(fā)現(xiàn)自己的一個(gè)改動(dòng)沒(méi)有生效婴渡,很有可能就是緩存的問(wèn)題,先clean一下再build就好了凯亮。
這樣根目錄的build.gradle就分析完了边臼,總體來(lái)看下根目錄的build.gradle會(huì)做哪些事情:
- 構(gòu)建腳本的配置,例如gradle插件的版本假消,構(gòu)建依賴的repo柠并,注意這里都是和構(gòu)建有關(guān)的
- 定義全局配置,可以定義所有子project的配置
- 定義了一個(gè)clean的task富拗,用于清理緩存目錄
接下來(lái)來(lái)看下主project的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.xuan.studydemo"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
def lifecycle_version = "2.2.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
}
首先看到的是apply plugin
臼予,這個(gè)就是gradle中的插件,關(guān)于插件的自定義我們后面再詳細(xì)講解啃沪,但對(duì)于插件的定義我們要在這里先了解下粘拾,不然后面對(duì)于流程會(huì)有些不理解。前面一篇博客有講解關(guān)于插件的基礎(chǔ)概念创千,這里再結(jié)合這里講一下缰雇。
gradle的本質(zhì)是一個(gè)流程控制框架,提供了基礎(chǔ)的task和方便的流程控制的語(yǔ)法糖追驴,而AGP是Google以Gradle為基礎(chǔ)械哟,并且結(jié)合Android的打包流程,專門開(kāi)發(fā)的一個(gè)插件殿雪。
所以簡(jiǎn)單的理解暇咆,就是Google將Android的打包流程,例如編譯冠摄、資源合并糯崎、混淆几缭、簽名等一系列的打包流程開(kāi)發(fā)成一個(gè)個(gè)Gradle中的Task,而AGP再將這些Task整合成流程沃呢,利用Gradle方便的流程控制和配置打包年栓,整合成一個(gè)AGP插件。
所以可以看到我們經(jīng)常會(huì)看到apply plugin: 'com.android.application'
,apply plugin: 'com.android.library'
,這其實(shí)就是AGP中定義的插件薄霜,因?yàn)橐蕾嚵诉@些插件某抓,所以有了下面的android
的配置,我們可以試下將apply plugin: 'com.android.application'
注釋掉惰瓜,這時(shí)候再build就會(huì)發(fā)現(xiàn)這樣的錯(cuò)誤的信息否副。
Could not find method android() for arguments [build_kmftzroc9bwua5m7q41tme6p$_run_closure1@47c045f] on project ':app' of type org.gradle.api.Project.
這也就是說(shuō)明android()
這個(gè)配置其實(shí)不是gradle自帶的,而是AGP中新增的崎坊。接下來(lái)來(lái)看看android的配置备禀。
compileSdkVersion
表示的Gradle用哪個(gè)版本的AndroidSDK編譯項(xiàng)目,所以當(dāng)我們使用新版本的API時(shí)就需要使用對(duì)應(yīng)版本的AndroidSDK奈揍,而compileSdkVersion只影響編譯時(shí)行為曲尸,不影響運(yùn)行時(shí)的行為,如果我們想要使用最新版本的的API,就可以將compileSdkVersion升級(jí)到對(duì)應(yīng)的版本男翰,并且如果有老的API過(guò)期另患,在編譯時(shí)就會(huì)警告或者報(bào)錯(cuò)。
buildToolsVersion
表示的編譯工具集的版本號(hào)蛾绎,例如在打包時(shí)使用的aapt,dx等工具版本昆箕,都是根據(jù)這里的版本制定的版本來(lái)尋找的。也就是我們構(gòu)建項(xiàng)目需要的工具集的版本號(hào)租冠,一般是在sdk/build-tools/目錄中鹏倘,如果說(shuō)剛才說(shuō)到的compileSdkVersion
表示的是我們使用的AndroidSDK版本,那么buildToolsVersion
就是我們的編譯工具的版本肺稀,然后使用這個(gè)編譯工具配合AndroidSDK來(lái)編譯工程項(xiàng)目第股,所以一般buildToolsVersion
的版本會(huì)和compileSdkVersion
大版本相同。
minSdkVersion
表示的是我們的應(yīng)用程序運(yùn)行所需要的最低版本號(hào)话原,如果手機(jī)系統(tǒng)版本大于這個(gè)版本,那么Android系統(tǒng)會(huì)阻止應(yīng)用安裝應(yīng)用诲锹,而如果我們沒(méi)有顯示的聲明minSdkVersion
,那么默認(rèn)值就是1繁仁,也就是可以運(yùn)行在所有的Android機(jī)器上。
targetSdkVersion
正如字面意思归园,表示的是我們應(yīng)用的目標(biāo)版本黄虱,所以也是最重要的和我們應(yīng)用適配有關(guān)的字段,在targetSdkVersion
設(shè)置的版本表示我們已經(jīng)對(duì)當(dāng)前版本及以下的版本充分適配測(cè)試庸诱,所以能夠兼容小于等于targetSdkVersion
所有的手機(jī)捻浦,所以這個(gè)也是Android系統(tǒng)向前兼容的判斷依據(jù)晤揣。比如我們?cè)O(shè)置的targetSdkVersion
版本是26,而如果我們運(yùn)行項(xiàng)目在一個(gè)28版本系統(tǒng)的手機(jī)上朱灿,Android系統(tǒng)判定我們應(yīng)用的targetSdkVerison
是26昧识,就會(huì)向前兼容,使用API時(shí)就會(huì)使用26及26版本以下的API,如果某個(gè)API在28上有變更盗扒,我們的應(yīng)用就不會(huì)體現(xiàn)出新特性跪楞。
所以綜上所述,我們應(yīng)用的版本關(guān)系應(yīng)該是:
minSdkVersion<=targetSdkVersion<=compileSdkVersion(buildToolsVersion)
defaultConfig
接下來(lái)來(lái)看下defaultConfig的配置侣灶,首先看下這個(gè)是用來(lái)配置什么東西的甸祭,當(dāng)然最便捷的就是先看AGP的源碼。
/**
* Specifies defaults for variant properties that the Android plugin applies to all build
* variants.
*
* <p>You can override any <code>defaultConfig</code> property when <a
* >
* configuring product flavors</a>.
*
* <p>For more information about the properties you can configure in this block, see {@link
* ProductFlavor}.
*/
public void defaultConfig(Action<DefaultConfig> action) {
checkWritability();
action.execute(defaultConfig);
}
這里我們先忽略下為什么這里是一個(gè)方法褥影,這個(gè)其實(shí)涉及到Gradle的一個(gè)特性池户,后續(xù)講Gradle源碼的時(shí)候會(huì)專門分析,這里先看注釋來(lái)看下是做什么作用的凡怎,在這個(gè)系列的第三篇博客我們有提到過(guò)關(guān)于AGP中buildType,Flavor,defaultConfig的區(qū)別和概念校焦,這里再來(lái)看應(yīng)該就清晰很多,其實(shí)defaultConfig就是對(duì)于不同的產(chǎn)物類型的默認(rèn)配置栅贴,而如果我們?cè)赽uildType或者Flavor中配置斟湃,那么就會(huì)覆蓋defaultConfig里的配置。具體的順序之前也有提到檐薯,這里在說(shuō)明一下:
按優(yōu)先級(jí)從高到低: buildType->Flavor->defaultConfig
首先來(lái)看下defaultConfig的配置方式凝赛,defaultConfig和Gradle之前的配置一樣,支持語(yǔ)法糖和方法調(diào)用兩種形式
defaultConfig {
minSdkVersion 15
}
//等價(jià)于
defaultConfig {
minSdkVersion(15)
}
接下來(lái)來(lái)看下核心屬性坛缕,這里只列舉幾個(gè)我認(rèn)為比較常見(jiàn)墓猎,其他的這里有可以查看Google的官方文檔,或者這里有一篇博客講的也比較詳細(xì)赚楚。
- applicationId
應(yīng)用的id毙沾,也就是我們常說(shuō)的包名,Android中的應(yīng)用包名是唯一的宠页,所以我們是用包名來(lái)區(qū)分一個(gè)唯一的應(yīng)用的左胞。 - comsumerProguardFiles\consumerProguardFile\proguardFiles
defaultConfig {
consumerProguardFiles 'consumer-rules.pro'
}
// 因?yàn)樵搶傩允且粋€(gè) List<File> 類型,如果需要多個(gè)文件配置举户,則如下所示
defaultConfig {
consumerProguardFiles 'consumer-rules.pro','consumer-test-rules.pro'
}
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
首先說(shuō)下comsumerProguardFiles烤宙,很多博客有說(shuō)到一個(gè)概念
這個(gè)屬性只作用于我們創(chuàng)建的library中,也就是不是在我們的主項(xiàng)目中配置使用的俭嘁,它是負(fù)責(zé)配置library被編譯打包時(shí)使用的混淆規(guī)則的躺枕。
但其實(shí)我感覺(jué)這里這樣描述是有錯(cuò)誤或者說(shuō)有歧義的,接下來(lái)我們來(lái)分析下這個(gè)問(wèn)題。
首先看下proguardFiles這個(gè)屬性拐云,這個(gè)是用于配置混淆屬性的罢猪,也就是我們平時(shí)打包時(shí)當(dāng)開(kāi)啟了minifyEnabled屬性時(shí),這時(shí)候Android打包的過(guò)程中叉瘩,在混淆時(shí)就會(huì)讀取我們通過(guò)proguardFiles配置的文件列表膳帕,來(lái)進(jìn)行相應(yīng)的混淆配置和防混淆配置。這個(gè)屬性適用于App也同樣適用于library房揭,也就是我們?nèi)绻蛞粋€(gè)aar包時(shí)备闲,也可以通過(guò)配置proguardFiles屬性來(lái)進(jìn)行混淆。哪可能有人要問(wèn)來(lái)捅暴,哪comsumerProguardFiles是用于干什么的呢恬砂?
我們?cè)谑褂靡恍┤介_(kāi)源庫(kù),可能會(huì)發(fā)現(xiàn)一種情景蓬痒,庫(kù)的Uage要求不僅僅需要通過(guò)maven依賴泻骤,還需要手動(dòng)寫入一些keep文件,這時(shí)就會(huì)發(fā)現(xiàn)很麻煩梧奢,為什么三方庫(kù)的混淆配置需要我們手動(dòng)加到我們自己工程里的狱掂,不能在三方庫(kù)內(nèi)部配置好嗎?
comsumerProguardFiles就是用于這種場(chǎng)景的亲轨,我們?cè)谕ㄟ^(guò)comsumerProguardFiles配置了混淆配置后趋惨,我們打出的release的aar,我們解壓后就會(huì)發(fā)現(xiàn)里面包含一個(gè)proguard.txt
文件惦蚊,而這個(gè)文件內(nèi)容其實(shí)就是我們的comsumerProguardFiles配置器虾,也就是我們的混淆配置會(huì)被打入aar文件中,而proguardFiles就不會(huì)有這樣的效果蹦锋。
然后在App引入aar后兆沙,Android在進(jìn)行apk打包的時(shí)候,就會(huì)對(duì)proguard.txt
進(jìn)行合并莉掂,然后最終在apk打包混淆時(shí)對(duì)全部文件生效葛圃。
這時(shí)候我們就可以得出幾個(gè)結(jié)論:
comsumerProguardFiles用于用于當(dāng)三方庫(kù)被App依賴時(shí),不僅僅會(huì)對(duì)三方庫(kù)生效憎妙,也會(huì)對(duì)全局的Java代碼生效
proguardFiles僅用于當(dāng)次編譯混淆時(shí)使用库正,并不會(huì)被持久化到aar中,所以我們?cè)赼ar配置的proguardFiles只會(huì)當(dāng)前aar打包的混淆時(shí)有效厘唾,并不會(huì)被寫入proguard.txt
文件中诀诊,也就是aar中的proguardFiles配置不會(huì)影響接入的Apk的混淆。
這里大家可以測(cè)試一下阅嘶,如果在一個(gè)aar的comsumerProguardFiles配置,增加一個(gè)-dontobfuscate
表示不進(jìn)行混淆,然后打出一個(gè)aar包讯柔,用App項(xiàng)目依賴這個(gè)aar抡蛙,就會(huì)發(fā)現(xiàn)我們?cè)赼pp中就算打開(kāi)了混淆,也會(huì)失效魂迄,就是因?yàn)槲覀冊(cè)赼ar的comsumerProguardFiles配置了-dontobfuscate
這個(gè)屬性粗截,最終被合并到apk打包的proguard.txt
文件中,對(duì)全部的Java文件進(jìn)行生效了捣炬,所以就導(dǎo)致全局的混淆都被關(guān)閉熊昌,這樣看這個(gè)配置還是要慎重的。
- javaCompileOptions
配置編譯時(shí) java 的一些參數(shù)湿酸,例如我們使用 annotationProcessor 時(shí)所需要的參數(shù)婿屹。
defaultConfig {
javaCompileOptions {
annotationProcessorOptions{
arguments = []
classNames ''
....
}
}
......省略其他配置
}
- ndk
用于配置abi過(guò)濾,配置我們打出來(lái)的apk支持什么架構(gòu)類型的CPU
defaultConfig {
// ndk中推溃,目前只有 abiFilter 一個(gè)屬性昂利,所以 ndk 目前來(lái)說(shuō)只用于 abi 的過(guò)濾
ndk {
abiFilter 'armeabi-v7a'
}
...
}
- versionCode/versionName
這里兩個(gè)的區(qū)別網(wǎng)上其實(shí)挺多講解的,但其實(shí)可以簡(jiǎn)單理解就是:versionName就是一個(gè)名字铁坎,是一個(gè)字符串蜂奸,沒(méi)任何作用,僅給用戶展示區(qū)分使用硬萍,versionCode是最重要的參數(shù)扩所,是一個(gè)int值,應(yīng)用市場(chǎng)的更新/判斷新舊包都是用這個(gè)朴乖,對(duì)開(kāi)發(fā)者透明祖屏,不對(duì)用戶透露『可以出現(xiàn)一個(gè)versionName對(duì)應(yīng)多個(gè)versionCode赐劣,但是最好不要這樣使用,因?yàn)檫@樣很容易導(dǎo)致版本亂了哩都,后續(xù)的運(yùn)營(yíng)數(shù)據(jù)分析魁兼,線上問(wèn)題修復(fù),版本升級(jí)漠嵌,渠道管理都可能會(huì)有問(wèn)題咐汞,所以最好的就是一對(duì)一的關(guān)系。
最后介紹一個(gè)defaultConfig的兩個(gè)特性儒鹿,用于動(dòng)態(tài)生成變量使用化撕。
首先是我們?cè)赿efaultConfig配置的屬性,在構(gòu)建時(shí)都會(huì)在對(duì)應(yīng)的產(chǎn)物變體目錄下生成對(duì)應(yīng)的BuildConfig.java文件约炎,此文件會(huì)講我們之前配置的屬性變成對(duì)應(yīng)的Java常量植阴,這樣我們就可以在Java代碼中進(jìn)行使用蟹瘾。
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.xuan.studydemo";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}
而如果我們想在編譯時(shí)自定義一些相應(yīng)的屬性變量,這時(shí)候就可以用buildConfigField(type,name,value)
方法掠手,用于向構(gòu)建時(shí)生成BuildConfig.java類中新增屬性憾朴。例如:
defaultConfig {
...
// 是否是Monkey包
isMonkey = project.hasProperty('isMonkey') ? isMonkey : 'false'
buildConfigField "boolean", "IS_MONKEY", isMonkey
// 添加模塊對(duì)應(yīng)的模塊名
buildConfigField "String", "MODULE_NAME", "\"${project.name}\""
}
對(duì)應(yīng)我們?nèi)绻幾g后,就會(huì)在產(chǎn)物目錄中發(fā)現(xiàn)BuildConfig.java類中就有我們相應(yīng)的變量定義喷鸽。
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.xuan.studydemo";
public static final String BUILD_TYPE = "debug";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
// Fields from default config.
public static final boolean IS_MONKEY = true;
public static final String MODULE_NAME = "app";
}
這樣我們就可以在我們的代碼邏輯中加入相關(guān)的判斷众雷,例如Monkey包可以不顯示某些入口,直接登錄等等做祝。
除了buildConfigField(type,name,value)
方法外砾省,還有一個(gè)方法也是用于在編譯時(shí)新增資源的。resValue(String type, String name, String value)
,這個(gè)方法相當(dāng)于在res/values中新增一個(gè)資源混槐。例如:
buildTypes {
debug {
resValue("String", "app_name_monkey", "Monkey for App")
}
}
這樣我們?cè)诰幾g后编兄,就會(huì)生成一個(gè)資源文件。
而我們?nèi)绻诖a中使用這個(gè)資源纵隔,就可以通過(guò)下面的方式來(lái)獲取翻诉。
getResources().getString(R.string.app_name_monkey);
最后是關(guān)于buildTypes
的介紹,其實(shí)關(guān)于buildTypes
和productFlavors
的區(qū)別捌刮,前面已經(jīng)有一篇博客比較詳細(xì)的介紹碰煌,這里再簡(jiǎn)單總結(jié)下,buildType
就是我們的構(gòu)建類型绅作,比如debug包芦圾,release包,monkey包俄认,不同的構(gòu)建類型有不同的構(gòu)建方式个少,比如debug包一般不會(huì)混淆,monkey包一般會(huì)加入一些apm的操作和庫(kù)眯杏,release一般會(huì)開(kāi)啟混淆和簽名夜焦。而productFlavors
就是指的產(chǎn)物類型,比如我們打的不同的渠道包岂贩,比如內(nèi)部包茫经,GooglePlay包,國(guó)內(nèi)市場(chǎng)包萎津,而buildTypes
和productFlavors
會(huì)以排列的方式進(jìn)行匯總卸伞,所以對(duì)應(yīng)的就是n*m
的關(guān)系,比如GooglePlay版的Debug包/release包锉屈,等等荤傲。對(duì)應(yīng)的這個(gè)最終的產(chǎn)物就是我們的BuildVariant
,所以對(duì)應(yīng)到一個(gè)公示:
BuildVariant = ProductFlavor x BuildType
就很好理解這些了颈渊,而這些對(duì)應(yīng)的配置優(yōu)先級(jí)順序就是:
按優(yōu)先級(jí)從高到低: buildType->Flavor->defaultConfig
接下來(lái)來(lái)看下里面常用的一些屬性遂黍。
- shrinkResources
shrinkResources
是google官方提供的優(yōu)化無(wú)用資源的配置终佛,shrinkResources和minifyEnabled必須同時(shí)開(kāi)啟才有效,這里具體shrinkResources
的實(shí)現(xiàn)細(xì)節(jié)就不展開(kāi)了妓湘,后續(xù)有時(shí)間可以專門寫篇博客學(xué)習(xí)分析下查蓉,但是這里說(shuō)幾個(gè)這個(gè)屬性特殊的地方。
shrinkResources中被移除的資源是真正被刪除了嗎榜贴?
這里先說(shuō)下結(jié)論,開(kāi)啟后無(wú)用的資源和圖片并沒(méi)有被真正的移除掉妹田,而是用了一個(gè)同名的占位符號(hào)唬党,具體我們可以自己試下。
shrinkResources會(huì)將所有無(wú)用的資源都移除嗎鬼佣?
這個(gè)也是不會(huì)的驶拱,shrinkResources在嚴(yán)格模式下,其實(shí)是會(huì)檢測(cè)我們代碼中靜態(tài)聲明的一些字符串晶衷,如果圖片中有命中我們定義的字符串蓝纲,就算資源沒(méi)有被使用,但是也不會(huì)被移除晌纫,具體這里有篇博客比較詳細(xì)的講解了税迷。
- minifyEnabled
當(dāng)設(shè)置為true的時(shí)候,就會(huì)開(kāi)啟代碼混淆锹漱,壓縮apk箭养,還會(huì)對(duì)資源進(jìn)行壓縮,對(duì)無(wú)用的代碼和多余的代碼在編譯打包的時(shí)候就會(huì)被移除掉哥牍,而具體的混淆規(guī)則配置前面也有提到毕泌,就是我們關(guān)于proguard.pro
的配置。
如何方式資源被混淆移除
我們可以在res/raw/keep.xml(避免被誤刪除)配置嗅辣。例如:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/activity_main,@drawable/comfirm_bg"/>
代碼混淆的結(jié)果和細(xì)節(jié)如何追溯撼泛?
代碼混淆生成apk之后,項(xiàng)目下面會(huì)多出來(lái)一個(gè)proguard文件夾澡谭,proguard文件夾中四個(gè)文件的作用愿题。
dump.txt : 描述了apk中所有類文件中內(nèi)部的結(jié)構(gòu)體。
mapping.txt : 列出了原始的類译暂、方法和名稱與混淆代碼間的映射抠忘。
seeds.txt : 列出了沒(méi)有混淆的類和方法。
usage.txt : 列出congapk中刪除的代碼外永。
- signingConfig
從字面知道有 “簽署配置” 的意思崎脉。該配置的作用,就是為編譯出來(lái)的apk簽上我們的“名字”伯顶,這樣才能將apk發(fā)布安裝到用戶的設(shè)備上囚灼。設(shè)備(手機(jī)骆膝、TV等)對(duì) apk 的唯一認(rèn)定,并不只是包名灶体,而是 包名和簽名阅签,其中一項(xiàng)不同,都會(huì)認(rèn)為這個(gè) apk 包是不同的蝎抽。
包名的不同政钟,表現(xiàn)為多個(gè)應(yīng)用。簽名的不同樟结,在應(yīng)用升級(jí)時(shí)表現(xiàn)為無(wú)法安裝养交,如果是第一次安裝,則不會(huì)有問(wèn)題瓢宦。
具體可以看下這篇博客
- resConfigs
作用是指定打包時(shí)編譯的語(yǔ)言包類型碎连,未指定的其他語(yǔ)言包,將不會(huì)打包到apk文件中驮履,從而減少apk體積的大小鱼辙。
最后介紹下關(guān)于依賴的內(nèi)容,這里但從使用層面介紹下幾種常用依賴的關(guān)鍵字的區(qū)別:
首先介紹下gralde不同版本下的幾個(gè)關(guān)鍵字的區(qū)別:
compile依賴關(guān)系已被棄用玫镐,被implementation和api替代;provided被compileOnly替代;
- implementation
與compile對(duì)應(yīng)倒戏,會(huì)添加依賴到編譯路徑,并且會(huì)將依賴打包到輸出(aar或apk)摘悴,但是在編譯時(shí)不會(huì)將依賴的實(shí)現(xiàn)暴露給其他module峭梳,也就是只有在運(yùn)行時(shí)其他module才能訪問(wèn)這個(gè)依賴中的實(shí)現(xiàn)。使用這個(gè)配置蹂喻,可以顯著提升構(gòu)建時(shí)間葱椭,因?yàn)樗梢詼p少重新編譯的module的數(shù)量。建議口四,盡量使用這個(gè)依賴配置孵运。 - api
與compile對(duì)應(yīng),功能完全一樣蔓彩,會(huì)添加依賴到編譯路徑治笨,并且會(huì)將依賴打包到輸出(aar或apk),與implementation不同赤嚼,這個(gè)依賴可以傳遞旷赖,其他module無(wú)論在編譯時(shí)和運(yùn)行時(shí)都可以訪問(wèn)這個(gè)依賴的實(shí)現(xiàn),也就是會(huì)泄漏一些不應(yīng)該不使用的實(shí)現(xiàn)更卒。舉個(gè)例子等孵,A依賴B,B依賴C蹂空,如果都是使用api配置的話俯萌,A可以直接使用C中的類(編譯時(shí)和運(yùn)行時(shí))果录,而如果是使用implementation配置的話,在編譯時(shí)咐熙,A是無(wú)法訪問(wèn)C中的類的弱恒。 - compileOnly
與provided對(duì)應(yīng),Gradle把依賴加到編譯路徑棋恼,編譯時(shí)使用返弹,不會(huì)打包到輸出(aar或apk)。這可以減少輸出的體積蘸泻,在只在編譯時(shí)需要琉苇,在運(yùn)行時(shí)可選的情況,很有用悦施。 - annotationProcessor
與compile對(duì)應(yīng),用于注解處理器的依賴配置去团。
三抡诞、總結(jié)
本篇博客介紹了gradle的常用配置,至此關(guān)于Gradle的基礎(chǔ)知識(shí)準(zhǔn)備告于段落了土陪,雖然沒(méi)有很全面的把Gradle基礎(chǔ)配置都包含進(jìn)來(lái)昼汗,但5篇博客下來(lái)基本上對(duì)于Gradle的基礎(chǔ)概念有了一個(gè)大體的認(rèn)識(shí)和思路,所以接下來(lái)準(zhǔn)備從源碼角度來(lái)學(xué)習(xí)Gradle鬼雀,為什么Gradle支持這樣的語(yǔ)法糖顷窒,AGP的打包流程是什么樣的,依賴關(guān)系是如何打入APK中的源哩,等等問(wèn)題都需要我們從Gradle和AGP的源碼角度來(lái)學(xué)習(xí)鞋吉,所以接下來(lái)的博客就結(jié)合源碼來(lái)繼續(xù)學(xué)習(xí)Gradle螟炫。