【Gradle深入淺出】——Gralde配置(二)

系列目錄

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ì)很困惑侈离,這里為什么有repositoriesdependencies的定義试幽,我們每個(gè)子工程的build.gradle中也有關(guān)于repositoriesdependencies的定義,這兩個(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 &lt;localRepository&gt; of <code>~/.m2/settings.xml</code> if this file exists and element is set;</li>
     * <li>The value of element &lt;localRepository&gt; 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)于buildTypesproductFlavors的區(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)包萎津,而buildTypesproductFlavors會(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螟炫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泻红,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锐秦,更是在濱河造成了極大的恐慌坛掠,老刑警劉巖赊锚,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異屉栓,居然都是意外死亡舷蒲,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門友多,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)牲平,“玉大人,你說(shuō)我怎么就攤上這事夷陋∏肥埃” “怎么了胰锌?”我有些...
    開(kāi)封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)藐窄。 經(jīng)常有香客問(wèn)我资昧,道長(zhǎng),這世上最難降的妖魔是什么荆忍? 我笑而不...
    開(kāi)封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任格带,我火速辦了婚禮,結(jié)果婚禮上刹枉,老公的妹妹穿的比我還像新娘叽唱。我一直安慰自己,他們只是感情好微宝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布棺亭。 她就那樣靜靜地躺著,像睡著了一般蟋软。 火紅的嫁衣襯著肌膚如雪镶摘。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天岳守,我揣著相機(jī)與錄音凄敢,去河邊找鬼。 笑死湿痢,一個(gè)胖子當(dāng)著我的面吹牛涝缝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播譬重,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拒逮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了害幅?” 一聲冷哼從身側(cè)響起消恍,我...
    開(kāi)封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎以现,沒(méi)想到半個(gè)月后狠怨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邑遏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年佣赖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片记盒。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡憎蛤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俩檬,我是刑警寧澤萎胰,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站棚辽,受9級(jí)特大地震影響技竟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屈藐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一榔组、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧联逻,春花似錦搓扯、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至公壤,卻和暖如春爱态,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背境钟。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俭识,地道東北人慨削。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像套媚,于是被迫代替她去往敵國(guó)和親缚态。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容