Android Studio項(xiàng)目Gradle構(gòu)建實(shí)踐

參考

名詞

  • 構(gòu)建類型(BuildType),編譯時(shí)的類型辜限,如debug, release
  • 產(chǎn)品風(fēng)味(ProductFlavor),不同的產(chǎn)品特征严蓖,可以有不同的包名等等薄嫡。
  • 構(gòu)建變體(BuildVariant),每一個(gè)特定唯一確定版本apk都是一個(gè)構(gòu)建變體的產(chǎn)物氧急,其由構(gòu)建類型和產(chǎn)品風(fēng)味組成。
  • APG毫深,全稱是Android Plugin for Gradle吩坝,google為使用gradle構(gòu)建而開發(fā)的插件。

1 一個(gè)典型的Android Studio 項(xiàng)目

1.1 項(xiàng)目結(jié)構(gòu)

一個(gè)新建的Android Stuido項(xiàng)目結(jié)構(gòu)如下:

項(xiàng)目結(jié)構(gòu)

包含三個(gè).gradle文件:

  • settings.gradle 文件對(duì)應(yīng)腳本執(zhí)行時(shí)的setting對(duì)象哑蔫,該文件最先被解析和執(zhí)行钉寝,一些通用的初始化操作可以放在這里執(zhí)行,項(xiàng)目包含多個(gè)子工程或者模塊時(shí)闸迷,必須在該文件中include嵌纲,這也是其最重要的功能之一。新建項(xiàng)目默認(rèn)的settings.gradle腥沽。
include ':app'

這里我想在腳本剛執(zhí)行時(shí)打印項(xiàng)目的存放路徑操作:

String projectDir = rootProject.projectDir.getAbsolutePath();
println projectDir
include ':app'
  • 項(xiàng)目根目錄下的build.gradle逮走,對(duì)應(yīng)腳本執(zhí)行時(shí)的rootProject對(duì)象,一般不做具體的模塊構(gòu)建操作今阳,用于指定項(xiàng)目所依賴的遠(yuǎn)程倉庫和使用的Gradle plugin 插件版本师溅,適用與所有的子工程或者模塊。
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    //jcenter一個(gè)著名的遠(yuǎn)程代碼倉庫
    repositories {
        jcenter()
    }
    
    
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  1. 上面指定遠(yuǎn)程倉庫的作用就是在需要依賴的庫在本地找不到時(shí)酣栈,會(huì)到該倉庫中去尋找并自動(dòng)下載险胰。
  2. 依賴的構(gòu)建插件,注意該插件不是Gradle的版本矿筝,是插件的版本起便,由google開發(fā)
  • 每個(gè)子項(xiàng)目或模塊下單獨(dú)的build.gradle腳本文件,在這里指定各自依賴的SDK窖维,庫屬性等等榆综,這也是我們編譯的腳本的主體。
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.1"
    defaultConfig {
        applicationId "com.inpor.fmcdevicedemon"
        minSdkVersion 14
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.1'
    testCompile 'junit:junit:4.12'
}
  • apply plugin 指定要加載的插件铸史,這里是一個(gè)應(yīng)用鼻疮,所以加載com.android.application插件,注意這個(gè)插件是我們上面使用的google開發(fā)的com.android.tools.build:gradle:2.2.3中攜帶的琳轿。

  • android閉包來自于google插件判沟,這里查看其DSL文檔:http://google.github.io/android-gradle-dsl/

1.2 Gradle Project

我們的AS項(xiàng)目對(duì)于Gradle而言就是一個(gè)個(gè)Gradle項(xiàng)目崭篡,而Gradle項(xiàng)目對(duì)于我們而言就是一個(gè)個(gè)task構(gòu)成的挪哄,我們可以點(diǎn)擊Android Studio的最右邊的Gradle工具欄,可以查看其項(xiàng)目結(jié)構(gòu)琉闪。

gradle project

比如上面我們點(diǎn)開other目錄迹炼,雙擊第一個(gè)任務(wù),此時(shí)就可以直接這個(gè)任務(wù),生成一個(gè)apk斯入。

gradle project2

2 配置基本編譯參數(shù)

2.1 基本使用

這里主要是指設(shè)置編譯時(shí)指定的SDK砂碉、bulidtools版本,包名刻两,應(yīng)用版本號(hào)等等增蹭,注意這里面定義的屬性會(huì)覆蓋AndroidMainfest.xml文件中定義的。

//編譯時(shí)的SDK版本號(hào)
compileSdkVersion 25

//編譯時(shí)指定的buildtools版本
buildToolsVersion "25.0.1"

defaultConfig {
    //應(yīng)用的包名
    applicationId "com.inpor.fmcdevicedemon"
    
    //指定應(yīng)用可以安裝到的系統(tǒng)的最低版本闹伪,這里14對(duì)應(yīng)的是android 4.0
    minSdkVersion 14
    
    //運(yùn)行時(shí)指定使用的sdk的版本沪铭,主要針對(duì)當(dāng)存在多個(gè)sdk版本時(shí)壮池,優(yōu)先使用的SDK版本
    targetSdkVersion 25
    
    //應(yīng)用版本號(hào)
    versionCode 1
    versionName "1.0"
    
    //執(zhí)行單元測(cè)試時(shí)指定的Runner偏瓤,在正式打包時(shí)并不會(huì)使用到
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

2.2 提取公用字段定義到其他文件中

前面說過我們可以把一個(gè)通用的屬性存放到項(xiàng)目根目錄下的build.gradle中。

//使用ext表示導(dǎo)出
ext {
    compileSdk = 25
    buildTools = "25.0.1"
    targetSdk = 25
    minSdk = 14
}

然后在app的build.gradle文件中使用定義的通用屬性

compileSdkVersion rootProject.ext.compileSdk
buildToolsVersion rootProject.ext.buildTools
defaultConfig {
    applicationId "com.inpor.fmcdevicedemon"
    minSdkVersion rootProject.ext.minSdk
    targetSdkVersion rootProject.ext.targetSdk
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

如果是多項(xiàng)目關(guān)聯(lián)椰憋,把一些共有的屬性提取出來就很有用了厅克。

2.3 使用resConfigs只打包需要的資源

只打包我們需要的資源。我們知道google給我們的apk提供了國(guó)際化支持橙依,如適應(yīng)不同的屏幕分辨率的drawable資源证舟,還有適應(yīng)不同語言的字符串資源等等,但是在很多情況下我們只需要一些指定分辨率和語言的資源就可以了窗骑,這個(gè)時(shí)候我們可以使用resConfigs方法來配置女责。

defaultConfig中添加如下配置之后

defaultConfig {
    .....
    // 過濾,對(duì)于國(guó)際化支持只打包中文資源创译,和"xxhdpi"
    // 注意如果在這里指定了dpi抵知,則flavor中不能指定的dpi與這里必須一致否則會(huì)報(bào)錯(cuò)
    resConfigs "zh-rCN", "xhdpi"
}

在添加resConfigs之前,反編譯的res目錄截圖:

未過濾圖片

在添加上述resConfigs配置之后软族,反編譯res目錄:

過濾后的圖片

注意:

  • 使用resConfigs并不會(huì)過濾默認(rèn)的drawable, values目錄刷喜,這是為了保證App在任何時(shí)候都有一個(gè)默認(rèn)的可選值。
  • resConfigs也可以在后面要講到的productFlavor中也可以使用立砸。

3 signingConfigs(Apk簽名配置)

3.1 配置不同的簽名

在默認(rèn)情況下掖疮,AS中編譯apk使用的是SDK中的Debug簽名,不需要顯式的指定簽名配置項(xiàng)颗祝,在signingConfigs的閉包中我們可以自定義多個(gè)簽名配置浊闪,一個(gè)典型的簽名配置:

signingConfigs {
    
    //debug簽名
    debug {
        //簽名秘鑰庫文件的存放的位置,這里使用的是相對(duì)路徑
        storeFile file('sign/debug.keystore')
        
        //秘鑰庫的訪問密碼
        storePassword 'android'
        
        //別名螺戳,因?yàn)橐粋€(gè)密碼庫可以供多個(gè)項(xiàng)目使用搁宾,所以別名不同,最后的簽名也是不同的温峭。
        keyAlias 'androidreleasekey'
        
        //別名的私鑰密碼
        keyPassword 'android'
    }
    
    
    release {
        storeFile file('sign/platform.keystore')
        storePassword 'android'
        keyAlias 'androidreleasekey'
        keyPassword 'android'
    }
}

3.2 從指定文件加載簽名和秘鑰

如果希望不在build.gradle中暴露自己的簽名秘鑰猛铅,可以將這些參數(shù)放到一個(gè)專門的文件中,比如在項(xiàng)目的根目錄下添加一個(gè)keystore.properties文件凤藏。

//test
debugStoreFile=sign/debug.keystore
debugStorePassword=android
debugKeyAlias=androidreleasekey
debugKeyPassword=android

//release
releaseStoreFile=sign/platform.keystore
releaseStorePassword=android
releaseKeyAlias=androidreleasekey
releaseKeyPassword=android

在app模塊的build.gradle中奸忽,解析這個(gè)文件

// Create a variable called keystorePropertiesFile, and initialize it to your
// keystore.properties file, in the rootProject folder.
def keystorePropertiesFile = rootProject.file("keystore.properties")

// Initialize a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()

// Load your keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android{
    .....
}

修改signConfigs閉包堕伪,引用文件中定義的屬性

signingConfigs {
    debug {
        keyAlias keystoreProperties['debugKeyAlias']
        keyPassword keystoreProperties['debugKeyPassword']
        storeFile file(keystoreProperties['debugStoreFile'])
        storePassword keystoreProperties['debugStorePassword']
    }
    
    release{
        keyAlias keystoreProperties['releaseKeyAlias']
        keyPassword keystoreProperties['releaseKeyPassword']
        storeFile file(keystoreProperties['releaseStoreFile'])
        storePassword keystoreProperties['releaseStorePassword']
    }
}

4 編譯類型(buildTypes

在Android studio中我們可以自定義不同的編譯類型,如調(diào)試版本栗菜,發(fā)行版本欠雌,在不同的版本中可以配置不同的參數(shù)與添加屬性,工程自帶有一個(gè)debug編譯類型疙筹,另外用戶無法自定義為testtype富俄,它已經(jīng)被單元測(cè)試占用。

比如下面我定義了三個(gè)不同的buildType而咆,分別設(shè)置不同的屬性值

buildTypes {

    debug {
        //指定簽名文件的配置霍比,不指定則使用SDK中默認(rèn)的debug簽名
        signingConfig signingConfigs.debug
        
        //壓縮對(duì)齊,提高運(yùn)行時(shí)的效率暴备,也可以使用zipAlignEnabled true
        setZipAlignEnabled(true)
        
        //可以調(diào)試
        debuggable true
        
        //jni可調(diào)試
        jniDebuggable true
        
        //渲染腳本可調(diào)試
        renderscriptDebuggable true
    }


    //在發(fā)行版本中娜亿,不允許調(diào)試炼列,并且添加代碼混淆
    release {

        setZipAlignEnabled(true)
        debuggable false
        jniDebuggable false
        renderscriptDebuggable false
        
        //指定簽名文件為release簽名,注意非debug,如果不指定簽名丑婿,則打出來的包不會(huì)簽名
        signingConfig signingConfigs.release
        
        //minifyEnabled表示代碼是否可以壓縮搜骡,裁剪優(yōu)化框舔,需要配合其他的工具一起使用末早,如proguard
        //添加代碼混淆,注意添加混淆時(shí)涛浙,必須將minifyEnabled 置為true康辑,否則混淆不生效
        //同樣如果沒有使用代碼混淆必須置為false,否則編譯失敗
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

    }
    
    //自定義一個(gè)type蝗拿,不使用代碼混淆晾捏,并且添加一個(gè)string資源到xml資源文件中
    custom{
        zipAlignEnabled true
        debuggable false
        jniDebuggable false
        renderscriptDebuggable false

        //指定簽名文件為release簽名
        signingConfig signingConfigs.release

        //添加一個(gè)字符資源到values/strings.xml文件中,目前無法指定資源的語言類別
        resValue "string", "custom_name", "測(cè)試用戶"
    }

}

上面的minifyEnabled還可以配合shrinkResources屬性使用哀托,移除沒有使用到的資源文件惦辛。

buildTypes {
    custom {
        ......
        minifyEnabled true
        shrinkResources true
        ......
    }
}

在實(shí)際測(cè)試中發(fā)現(xiàn),上述裁剪可以剪裁布局仓手、圖片胖齐、菜單,但是不會(huì)移除values嗽冒。

注意shrinkResources優(yōu)化并不一定會(huì)刪除沒有用到的文件呀伙,在我的實(shí)際測(cè)試中,它會(huì)圖片添坊、布局變成最小剿另,沒有刪除它們

當(dāng)我們需要?jiǎng)討B(tài)加載資源時(shí),需要在不要使用該優(yōu)化雨女,否則可能會(huì)出現(xiàn)運(yùn)行時(shí)報(bào)錯(cuò)或者顯示效果不正確的問題谚攒,如果要使用該優(yōu)化可以在res/raw/keep.xml中進(jìn)行特定資源的保持或優(yōu)化,如下例子不優(yōu)化layout/test_layout氛堕。

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/test_layout"/>

參考(官網(wǎng)持有資源一章 ):https://developer.android.google.cn/studio/build/shrink-code.html#keep-resources

5 splits(拆分只包含某些需要屬性的apk)

splits

其作用是將當(dāng)前配置的版本的apk分裂生成多個(gè)只包含指定屬性的Apk馏臭,目前在google給我們提供根據(jù)language, abi, density進(jìn)行拆分。

//過濾只打包英文和簡(jiǎn)體中文的資源
splits{
    //設(shè)置根據(jù)language拆分測(cè)試未通過讼稚,應(yīng)該是字符串的表現(xiàn)形式不對(duì)
//        language{
//            enable true
//            include "values-zh-rCN"
//            include "zh-rCN"
//        }

    density{
        enable true
        reset()  // Clears the default list from all densities to no densities.
        include "mdpi", "xxhdpi" // Specifies the two densities we want to generate APKs for.
    }
}

上面的配置編譯之后會(huì)生成3個(gè)Apk:

app-mdpi-custom.apk  //裁剪掉大部分非mdpi資源的apk

app-xxhdpi-custom.apk  //裁減掉大部分非xxdpi資源的apk

app-universal-custom.apk //未做任何裁剪的apk
              

參考Android apk splits 官方文檔:https://developer.android.google.cn/studio/build/configure-apk-splits.html

6 PackagingOptions(指定添加/移除某些文件到最終的apk中)

首先看其DSL結(jié)構(gòu)圖括儒。



PackagingOptions不同于resConfigs,后者過濾某些資源目錄锐想,前者是在打包Apk的時(shí)候(已經(jīng)執(zhí)行過編譯了)排除一些文件帮寻,在實(shí)際測(cè)試中并不能用于過濾資源文件等等,更多是用于過濾一些與工程沒有直接關(guān)系的文件(聲明痛倚、版本控制等等)规婆。

  • First-pick,如果要添加的文件已經(jīng)存在于Apk中蝉稳,則會(huì)被忽略,如果有多個(gè)路徑于指定的pattern掘鄙,只添加第一個(gè)耘戚。
  • Merge,合并的意思操漠,如果文件不存在收津,則添加,如果文件已經(jīng)存在浊伙,則合并內(nèi)容撞秋。
  • Exclude,不包含的內(nèi)容嚣鄙,默認(rèn)以下的內(nèi)容不會(huì)被打包到Apk中:
/META-INF/LICENCE
/META-INF/LICENCE.txt
/META-INF/NOTICE
/META-INF/NOTICE.txt
/LICENCE
/LICENCE.txt
/NOTICE
/NOTICE.txt
**/.svn/** (all .svn directory contents)
**/CVS/** (all CVS directory contents)
**/SCCS/** (all SCCS directory contents)
**/.* (all UNIX hidden files)
**/.*/** (all contents of UNIX hidden directories)
**/*~ (temporary files)
**/thumbs.db
**/picasa.ini
**/about.html
**/package.html
**/overview.html
**/_*
**/_*/**

PackagingOptions更多的用于去除編譯時(shí)依賴不同的包時(shí)吻贿,含有相同的文件時(shí),去除編譯時(shí)的重復(fù)錯(cuò)誤中哑子。如:

//在打包時(shí)舅列,移除一些許可,注意文檔
packagingOptions {
    exclude 'META-INF/DEPENDENCIES.txt'
    exclude 'META-INF/NOTICE'
    exclude 'META-INF/NOTICE.txt'
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/LICENSE.txt'
}

實(shí)際測(cè)試中還沒有發(fā)現(xiàn)有其他的作用卧蜓,待補(bǔ)充帐要。

官方DSL文檔鏈接:http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html

7 lintOptions

lint檢查工具是google開發(fā)的一款代碼掃描工具,其主要用于掃描布局弥奸,未使用資源榨惠,國(guó)際化等等問題,其作用在這里不是我們關(guān)注的重點(diǎn),其使用和配置方法請(qǐng)查看官方文檔:https://developer.android.google.cn/studio/write/lint.html赠橙。

這里我們要考慮的是link選項(xiàng)對(duì)我們打包的影響伸蚯,要注意link檢查拋出來的錯(cuò)誤,并不會(huì)導(dǎo)致編譯時(shí)候的錯(cuò)誤简烤,可能會(huì)導(dǎo)致運(yùn)行時(shí)的錯(cuò)誤剂邮,以是lintOptions的屬性截圖:

lintOptions

在Android Studio中默認(rèn)下,link檢查報(bào)錯(cuò)會(huì)導(dǎo)致編譯中斷横侦,為了避免這個(gè)問題挥萌,我們可以在android閉包中添加如下代碼:

android {
    ......
    lintOptions {
        //關(guān)閉編譯release版本的lint檢查
        checkReleaseBuilds false
        
        //關(guān)閉link檢查報(bào)錯(cuò)中斷編譯選項(xiàng)
        abortOnError false
    }
    ......
}

在日常研發(fā)中,我們應(yīng)當(dāng)頻繁執(zhí)行lint檢查枉侧,以優(yōu)化代碼和提前暴露一些可能運(yùn)行時(shí)報(bào)錯(cuò)的代碼引瀑。

8 productFlavor(產(chǎn)品風(fēng)味)

8.1 基本屬性與方法

productFlavor與其說是產(chǎn)品風(fēng)味還不如說是產(chǎn)品工廠,我們可以根據(jù)不同的需要榨馁,最終生成不同的apk憨栽。多渠道打包是productFlavor的最常用的功能之一。上面說到的defaultConfig我們可以認(rèn)為一種簡(jiǎn)略的默認(rèn)productFlavor翼虫,所以我們完全可以在自定義的productFlavor中覆蓋defaultConfig中的任意配置項(xiàng)屑柔。

  • 基本屬性
perpties
  • 方法與閉包


    method

參考:http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.ProductFlavor.html

從上面的屬性與方法中我們發(fā)現(xiàn)可以設(shè)置包名,版本珍剑,混淆文件, NDK等等

8.2 示例

現(xiàn)在我創(chuàng)建多個(gè)productFlavor掸宛,它們具有不同的包名,不同的版本號(hào)

productFlavors{
    sky{
        //直接在原來的包名后加后綴
        applicationIdSuffix ".sky"
        
        //指定不同的版本
        versionName '1.2.0'
        versionCode 120
    }

    gavin{
        //重新命名包名
        applicationId "com.gavin.gradlebuilddemo"
        
        //指定不同的最小編譯版本
        minSdkVersion 17
        targetSdkVersion 21
    }

    smith{
        
        applicationId "com.smith.gradlebuilddemo"
        
        //指定不同的resConfig
        resConfigs "zh-rHK",

        //添加resValue
        resValue 'string', 'smith', 'this is smith'
    }
}

考慮到一種情況招拙,有時(shí)候我們有些公共的資源和配置是某些productFlavors公用的唧瘾,我們希望把它們提取出來,減少重復(fù)别凤,這個(gè)時(shí)候我們可以使用flavorDimensions來實(shí)現(xiàn)我們的需求饰序。

//使用dimensions將一些公共的修改獨(dú)立出來,可以重復(fù)使用规哪,減小代碼的重復(fù)
flavorDimensions 'type', 'common'
    
productFlavors{
    sky{
        dimension 'type'
        
        //直接在原來的包名后加后綴
        applicationIdSuffix ".sky"
        
        //指定不同的版本
        versionName '1.2.0'
        versionCode 120
    }

    gavin{
        dimension 'type'
        
        //重新命名包名
        applicationId "com.gavin.gradlebuilddemo"
        
        //指定不同的最小編譯版本
        minSdkVersion 17
        targetSdkVersion 21
    }

    smith{
        dimension 'type'
        
        applicationId "com.smith.gradlebuilddemo"
        
        //指定不同的resConfig
        resConfigs "en", "hdpi"

        //添加resValue
        resValue 'string', 'smith', 'this is smith'
    }
    
    commonClient{
        dimension 'common'
        
        //添加resValue
        resValue 'string', 'common_client', 'this is common_client'
    }
    
    commonPrivate{
        dimension 'common'
        
        //指定一個(gè)私有的混淆規(guī)則
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-private.pro'
    }
}

采用上述實(shí)現(xiàn)中求豫,會(huì)將兩兩不同的dimension進(jìn)行組合,最終生成我們需要的apk由缆。比如app-sky-commonClient-debug.apk注祖。

現(xiàn)在我們來計(jì)算一下最終可以生成的apk的數(shù)目,我們將flavorDimensions看成數(shù)組的話均唉,最終可以生成的apk的數(shù)目為:

count = BuildType.size * flavorDimensions[0].size * ... flavorDimensions[n].size

這里的flavorDimensions[n].size是指每個(gè)DimensionsproductFlavors中的個(gè)數(shù)是晨,比如上面的最終能夠生成的apk個(gè)數(shù)就是:3 * 3(type)* 2(common) = 18。

8.3 資源替換

同樣的我們也可以在src\main的同級(jí)目錄下給每個(gè)productFlavors建立目錄存放我們的特定資源舔箭。

srcFlavorDir

替換res資源

替換res資源采用的是合并的原則罩缴,即最終的資源是所有進(jìn)行合并的資源的并集蚊逢,出現(xiàn)資源ID重復(fù)的資源,采用優(yōu)先級(jí)最高的那個(gè)箫章,具體的優(yōu)先級(jí)后面會(huì)講到烙荷。

main/res/values/strings.xml中定義了這樣的資源。

<resources>
    <string name="app_name">GradleBuildDemo</string>
    <string name="hello">hello world</string>
    <string name="enter_button_str">enter</string>
    <string name="cancel_button_str">cancel</string>
    <string name="input_tips">Please input the word you want</string>
    <string name="cancel_tips_msg">button is clicked</string>
</resources>

sky/res/values/strings.xml中重新定義了如下資源檬寂。

<resources>
    <string name="app_name">GradleBuildDemo_Sky</string>
    <string name="hello">hello world, sky</string>
    <string name="enter_button_str">enter sky help</string>
    <string name="cancel_button_str">cancel sky</string>
</resources>

最終合并的資源是這樣的终抽。

<resources>
    <string name="app_name">GradleBuildDemo_Sky</string>
    <string name="hello">hello world, sky</string>
    <string name="enter_button_str">enter sky help</string>
    <string name="cancel_button_str">cancel sky</string>
    <string name="input_tips">Please input the word you want</string>
    <string name="cancel_tips_msg">button is clicked</string>
</resources>

注意layout資源是以整個(gè)文件覆蓋的方式合并的桶至。

assets目錄

assets目錄中的文件是以整個(gè)文件覆蓋的方式進(jìn)行合并的昼伴。

java原代碼目錄

源碼文件的合并與其他的不同,如果我們想在不同的變體中對(duì)一個(gè)類做不同的實(shí)現(xiàn)镣屹,那么我們就不能在main/java目錄下定義這個(gè)類圃郊,只能在每個(gè)變體中單獨(dú)定義,并且路徑要一致女蜈,而且對(duì)一些變體進(jìn)行組合時(shí)持舆,同時(shí)也只能存在一份代碼。

以下示例中伪窖,我分別在sky, gavin兩個(gè)flavors中定義了HelpTestActivity類逸寓。

sky版本的HelpTestActivity類。

package com.sky.gradlebuilddemo.activity;

import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.sky.gradlebuilddemo.R;

/**
 * PACKAGE_NAME
 * [function]
 * [detail]
 * Created by Sky on 2016/10/24.
 * modify by
 */

public class HelpTestActivity extends AppCompatActivity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_help_test);
        Button skyButton = (Button) findViewById(R.id.skyButton);

        //點(diǎn)擊按鈕彈出提示文案惰许,
        skyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final Snackbar snackbar = Snackbar.make(HelpTestActivity.this.getWindow().getDecorView(),
                        "hello snackbar", Snackbar.LENGTH_LONG);
                snackbar.setAction("Change Color", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        snackbar.getView().setBackgroundResource(R.color.colorPrimary);
                    }
                }).show();

            }
        });
    }
}

gavin版本的HelpTestActivity

package com.sky.gradlebuilddemo.activity;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

import com.sky.gradlebuilddemo.R;


/**
 * PACKAGE_NAME
 * [function]
 * [detail]
 * Created by Sky on 2016/10/24.
 * modify by
 */

public class HelpTestActivity extends AppCompatActivity {


    private ListView listView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_help_test);
        listView = (ListView) findViewById(R.id.msgListView);
        String[] msgs = getResources().getStringArray(R.array.listMsg);
        listView.setAdapter(new SimpleListAdapter(this, msgs));
    }

    private static class SimpleListAdapter extends BaseAdapter{

        private String[] data;

        private Context context;

        SimpleListAdapter(Context context, String[]data){
            this.data = data;
            this.context = context;
        }

        @Override
        public int getCount() {
            return data.length;
        }

        @Override
        public Object getItem(int position) {
            return data[position];
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if(convertView == null){
                convertView = LayoutInflater.from(context).inflate(R.layout.list_item_layout, null);
            }
            TextView  textView = (TextView) convertView.findViewById(R.id.itemTextView);
            textView.setText(data[position]);
            return convertView;
        }
    }


}

MainActivity中調(diào)用它席覆。

package com.sky.gradlebuilddemo;

import android.content.Intent;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.sky.gradlebuilddemo.activity.HelpTestActivity;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button enterButton = (Button) findViewById(R.id.enter_button);

        enterButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity.this.startActivity(new Intent(MainActivity.this, HelpTestActivity.class));
            }
        });

        .......
    }
}

9 sourceSets(資源集合的集合)

9.1 sourceSet與優(yōu)先級(jí)

sourceSet就是所謂的源集,包含特定源代碼和所需資源汹买,每一個(gè)源集不是任意命名的,每一個(gè)源集對(duì)應(yīng)一個(gè)BuildTypeProductFlavorBuildVariant聊倔,看官方的文檔描述晦毙。

sourceSet

參考:https://developer.android.google.cn/studio/build/index.html#sourcesets

Android Studio在編譯某個(gè)構(gòu)建變體的時(shí)候,并不是單獨(dú)的使用某個(gè)源集耙蔑,而是merge不同的源集见妒,比如有一個(gè)SkyCommonClientCustom的構(gòu)建變體,并且定義了skyCommonClientCustom, custom, sky, commonClient這四個(gè)源集甸陌,那么在構(gòu)建的時(shí)候就會(huì)合并上述的四個(gè)源集和默認(rèn)的main源集的源代碼和資源须揣。

上面提到了merge合并資源,那么合并的優(yōu)先級(jí)是怎樣的呢钱豁?


SourceSetPority

需要補(bǔ)充一點(diǎn)耻卡,如果在ProductFlavor使用了flavorDimensions,比如:

flavorDimensions 'type', 'common'

sourceSets{
    sky{
       .....
    }
    
    commonClient{
       .....
    }
}

productFlavors{
    sky{
        dimension 'type'
        //直接在原有包名后面添加
        applicationIdSuffix ".sky"
    }

    commonClient{
        dimension 'common'
    }
}

那么源集sky的優(yōu)先級(jí)高于commonClient牲尺,所以如果把flavorDimensions看做一個(gè)數(shù)組的話卵酪,最終的優(yōu)先級(jí)是:

BuildVariant > BuidlType > flavorDimensions[0] > ... > flavorDimensions[x] > main > 內(nèi)容庫依賴項(xiàng)(aar等)

9.2 sourceSet的基本屬性

sourceSet簡(jiǎn)單來說就是指定在編譯指定了某些特定源代碼和資源的集合幌蚊,在Android中使用的是googleAndroidSourceSet

android閉包中的sourceSets就是由上面用戶定義的一系列的AndroidSourceSet的集合溃卡。

先看AndroidSourceSet的DSL屬性結(jié)構(gòu)圖:

AndroidSourceSet

從上面的圖中我們可以看出溢豆,針對(duì)每個(gè)AndroidSourceSet可以配置不同的:

//跨進(jìn)程通信聲明文件(.aidl)
aidl

//assets文件下文件
assets

//.java文件目錄
java

//.c, .cpp文件位置
jni 

//.so文件路徑,注意該路徑只需要指定到所包含平臺(tái)的外層瘸羡,不需要指定到具體的平臺(tái)如`armeabi`漩仙,否則無法找到SO
jnilibs

//mainfest文件
manifest

//Android resource
res

//(java resource)
resource

//渲染腳本
rendersript

最終根據(jù)這些不同的資源集合生成不同的apk

以下四個(gè)屬性為只讀屬性:

//sourceSet的名稱犹赖,如custom
name 

//編譯時(shí)的配置名稱队他,如customCompile,與后面要講的dependencies的配置項(xiàng)compile相對(duì)應(yīng)冷尉。
compileConfigurationName

//如customApk漱挎,與后面要講的dependencies的配置項(xiàng)apk相對(duì)應(yīng)。
packageConfigurationName

//如customProvided雀哨,與后面要講的dependencies的配置項(xiàng)provided相對(duì)應(yīng)磕谅。 
providedConfigurationName

9.3 示例

sourceSets {
    main{
        jniLibs.srcDirs=['libs']
    }

    custom{
        //指定一個(gè)新的jnilibs為根目錄下的jnilibs
        jniLibs.srcDirs=[rootProject.projectDir.absolutePath + '/jnilibs']

        //指定一個(gè)新的assets為根目錄下的skyTestAssets目錄
        assets.srcDirs = [rootProject.projectDir.absolutePath + '/assets/skyTestAssets']
    }
}

上面的例子中我們給custom這個(gè)源集指定了新的jniLibs和Assets目錄,上面的源集也可以采用閉包的形式雾棺。

custom{
    
    jniLibs{
        //指定一個(gè)新的jnilibs為根目錄下的jnilibs
        srcDirs=[rootProject.projectDir.absolutePath + '/jnilibs']
    }

    
    assets{
        //指定一個(gè)新的assets為根目錄下的skyTestAssets目錄
        srcDirs = [rootProject.projectDir.absolutePath + '/assets/skyTestAssets']
    }
}

兩點(diǎn)注意:

  • srcDirsrcDirs的區(qū)別膊夹,當(dāng)使用srcDir指定文件目錄時(shí),不允許將要合并的源集的同一目錄下有一樣名稱的資源捌浩,提示重復(fù)資源異常放刨,而srcDirs則會(huì)根據(jù)前面所說的優(yōu)先級(jí)進(jìn)行覆蓋。

  • 如果我們?cè)?code>src/main的同級(jí)目錄下尸饺,也建立一個(gè)如下的文件目錄:

buildTypeCustonDir

當(dāng)在源集中指定了asserts的目錄時(shí)进统,custom/assets目錄會(huì)直接失效。

9.4 未解決的問題

在上面的源集中浪听,Android官方構(gòu)建指南沒有提及一點(diǎn)螟碎,就是如何過濾源集目錄下的一些文件不編譯或打包到最后的apk中,我使用如下方式迹栓,希望過濾掉src/main/assets/mainIngoreTest.txt文件不打包到最終的apk中掉分。

sourceSets
{
    .....
    custom{
        //指定一個(gè)新的jnilibs為根目錄下的jnilibs
        jniLibs.srcDirs=[rootProject.projectDir.absolutePath + '/jnilibs']

        //指定一個(gè)新的assets為根目錄下的skyTestAssets目錄
        assets.srcDirs = [rootProject.projectDir.absolutePath + '/assets/skyTestAssets']
        
        //意圖過濾掉`src/main/assets/mainIngoreTest.txt`文件
        assets.exclude (project.projectDir.absolutePath + "\\src\\main\\assets\\mainIngoreTest.txt")
        
        //或者采用閉包
        assets.exclude{
            File f = it.file
            println f.absolutePath
            f.absolutePath.endsWith("mainIngoreTest.txt")
        }
        
    }
    .....
}

采用上述方式并不能成功過濾掉文件,并且上面的閉包中的代碼也沒有執(zhí)行克伊,目前還沒有找到原因酥郭。

10 Dependencies(依賴內(nèi)容庫)

依賴內(nèi)容庫指的是不是當(dāng)前工程的代碼或資源,它們存在于其他的項(xiàng)目中愿吹,它們可以以源碼庫不从,jar,arr形式存在洗搂。

10.1 聲明依賴項(xiàng)的三種方式

先看聲明方式消返。

android {...}
...
dependencies {


    // 添加含有源碼的模塊依賴
    compile project(":mylibrary")

    // 遠(yuǎn)程二進(jìn)制庫依賴
    compile 'com.android.support:appcompat-v7:25.1.0'

    // 本地庫(jar)依賴
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

下面逐一介紹载弄,以下來自官方文檔。

模塊依賴項(xiàng)

  • compile project(':mylibrary')行聲明了一個(gè)名為mylibrary的本地Android庫模塊作為依賴項(xiàng)撵颊,這樣的庫可能是另外某個(gè)工程的一部分宇攻,此時(shí)是具有源代碼的,注意依賴本地庫模塊時(shí)倡勇,必須在根目錄下的settings.gradle文件中include它逞刷。

遠(yuǎn)程二進(jìn)制依賴項(xiàng)

  • compile 'com.android.support:appcompat-v7:25.1.0' 行通過指定其 JCenter 遠(yuǎn)程倉庫中的標(biāo)識(shí),當(dāng)本地不存在該依賴庫時(shí)妻熊,則自動(dòng)從遠(yuǎn)程下載夸浅,默認(rèn)存放在sdk/extras/目錄下,當(dāng)然我們也可以在 SDK 管理器下載和安裝特定的依賴項(xiàng)扔役。

本地二進(jìn)制依賴項(xiàng)

  • 簡(jiǎn)單來說就是依賴已經(jīng)打包好的jar庫帆喇,compile fileTree(dir: 'libs', include: ['*.jar'])的意思就是依賴app/libs目錄下的所有的以.jar結(jié)尾的文件。

10.2 配置依賴項(xiàng)

當(dāng)我們希望對(duì)依賴項(xiàng)在編譯和打包時(shí)做一些特殊處理的時(shí)候亿胸,通過使用不同的關(guān)鍵詞坯钦,google給我們提供三種配置方式:

  • compile,最常見的配置項(xiàng)侈玄,編譯時(shí)依賴婉刀,Gradle將此配置依賴項(xiàng)添加到類路徑和最終的apk中,其實(shí)就是說在編譯時(shí)和最終的apk中都需要序仙。

  • apk突颊,其指定的依賴項(xiàng)只在打包最終的apk的時(shí)候才需要,此配置只能和JAR二進(jìn)制依賴項(xiàng)一起使用潘悼,而不能與其他庫模塊依賴項(xiàng)或 AAR 二進(jìn)制依賴項(xiàng)一起使用律秃。

  • provided,其指定的依賴項(xiàng)治唤,此配置依賴項(xiàng)將添加到類路徑中友绝,只在編譯的時(shí)候需要,不打包到最終的apk中(也就是說運(yùn)行時(shí)無須該依賴項(xiàng))肝劲,比如我們編譯時(shí)使用的SDK就屬于這一類,同樣的此配置只能和JAR二進(jìn)制依賴項(xiàng)一起使用郭宝,而不能與其他庫模塊依賴項(xiàng)或 AAR 二進(jìn)制依賴項(xiàng)一起使用辞槐。

示例:

dependencies { 

    .....

    // 依賴app/apklib下的jar文件,只在apk打包的時(shí)候依賴
    apk fileTree(dir: 'apklib', include: ['*.jar'])
    
    // 依賴app/rovidedlib下的jar文件粘室,只在編譯的時(shí)候依賴
    provided fileTree(dir: 'providedlib', include: ['*.jar'])
}

10.3 指定特定的構(gòu)建變體的依賴項(xiàng)

在實(shí)際構(gòu)建中榄檬,我們常常遇到有這樣的需求,我們希望某些依賴項(xiàng)只有在某些特定構(gòu)建變體編譯時(shí)才被依賴衔统,在Android Studio中我們可以指定依賴項(xiàng)在以下特定的構(gòu)建類型下才依賴:

  • BuildTypes
  • BuildVariant
  • ProductFlavors

也就是說我們可以以上述任意一種方式指定特定的依賴項(xiàng)鹿榜,我們知道每一個(gè)buildType, flavor都有一個(gè)與之名字相同的的sourceSet海雪,所以我們要指定依賴項(xiàng)為某特定類型的方式為:

  • sourceSet.compileConfigurationName,如skyCompile
  • sourceSet.packageConfigurationName舱殿,如skyApk
  • sourceSet.providedConfigurationName奥裸,如skyProvided

以下是具體示例。

dependencies {

    // BuildTypes為AndroidTest沪袭,做單元測(cè)試時(shí)才編譯
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    
    
    // BuildTypes為test的 依賴項(xiàng)junit
    testCompile 'junit:junit:4.12'

    // BuildTypes為debug的添加含有源碼的模塊依賴
    debugCompile project(":mylibrary")
    
    // flavor為sky的指定jar庫湾宙,且不使用`okhttp-3.2.0.jar`庫,使用skyCommon目錄下的okhttp-3.3.1.jar
    skyCompile fileTree(include: ['*.jar'], dir: 'jar/sky', excludes: ['okhttp-3.2.0.jar'])
    
    // 構(gòu)建變體依賴項(xiàng)指定
    skyCommonClientCustomCompile fileTree(include: ['*.jar'], dir: 'jar/skyCommonClientCustom')

    //以下是正常的依賴
    // 遠(yuǎn)程二進(jìn)制庫依賴
    compile 'com.android.support:appcompat-v7:25.1.0'

    // 本地庫(jar)依賴
    compile fileTree(dir: 'libs', include: ['*.jar'])
    
    compile 'com.android.support:design:24.2.1'
}

注意冈绊,在上述使用xxxCompile時(shí)侠鳄,有時(shí)會(huì)提示找不到對(duì)應(yīng)的xxxCompile方法的錯(cuò)誤:

Error:(180, 0) Could not find method skyCommonClientCustomCompile() for arguments [directory 'jar/skyCommonClientCustom'] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
<a href="openFile:E:\Source\GitHub\GradleBuildDemo\app\build.gradle">Open File</a>

解決辦法為,我們?cè)?code>sourceSets閉包下死宣,新建一個(gè)空的對(duì)應(yīng)的soruceSet就可以了伟恶。

sourceSets{
    .....

    // 此處增加一個(gè)空的sourceSet,是為了解決在dependencies中使用
    // skyCommonClientCustomCompile 指定依賴項(xiàng)時(shí)提示找不到方法的錯(cuò)誤
    skyCommonClientCustom{

    }

}

資源合并的規(guī)則同樣適用與依賴合并毅该,所以在指定特定依賴后博秫,構(gòu)建某個(gè)特定變體時(shí)(flavorType),其編譯時(shí)最終的依賴項(xiàng)(不考慮provided)就變?yōu)椋?/p>

flavorTypeCompile + typeCompile + flavorCompile + compile

指定依賴項(xiàng)的構(gòu)建類型

我們可以直接指定依賴項(xiàng)的構(gòu)建類型鹃骂,這里的構(gòu)建類型可以是BuildVariant, buildType, flavor台盯。

dependencies {
    ...
    // relase構(gòu)建時(shí)指定依賴的`library`也是release
    releaseCompile project(path: ':library', configuration: 'release')
    
    // debug構(gòu)建時(shí)指定依賴的`library`的構(gòu)建也是`debug`
    debugCompile project(path: ':library', configuration: 'debug')
    ......
}

10.4 transitive, force, exclude的使用與依賴沖突解決

通過gradle命令查看依賴樹,在模塊所在的目錄(app目錄)畏线,執(zhí)行gradle dependencies静盅,執(zhí)行結(jié)果如圖(以androidTest為例)。

android_test_dependenice

transitive

transitive用于自動(dòng)處理子依賴項(xiàng)寝殴。默認(rèn)為true蒿叠,gradle自動(dòng)添加子依賴項(xiàng),形成一個(gè)多層樹形結(jié)構(gòu)蚣常;設(shè)置為false市咽,則需要手動(dòng)添加每個(gè)依賴項(xiàng)。

  • 為所有的配置指定自動(dòng)添加子依賴項(xiàng)為false
configurations.all {
   transitive = false
}
  • 為單獨(dú)的某個(gè)依賴項(xiàng)指定字典添加子依賴項(xiàng)為false
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
       transitive = false 
    })

force

即強(qiáng)制設(shè)置某個(gè)模塊的版本抵蚊。

configurations.all {
   resolutionStrategy {
       force 'com.android.support.test:runner:0.2'
   }
}

以上設(shè)置之后所有對(duì)com.android.support.test:runner模塊有依賴的其他庫都被強(qiáng)制使用0.2版本施绎。

exclude

排除依賴項(xiàng)中的某些子依賴項(xiàng),這在解決依賴庫版本沖突或者重復(fù)時(shí)特別有用贞绳,我們可以通過如下兩種方式進(jìn)行排除:

  • group谷醉,maven項(xiàng)目的GroupId,GroupID是項(xiàng)目組織唯一的標(biāo)識(shí)符冈闭,對(duì)于小型的項(xiàng)目俱尼,常常對(duì)應(yīng)JAVA的包的結(jié)構(gòu),是main目錄里java的目錄結(jié)構(gòu)萎攒,但是也可以很多個(gè)項(xiàng)目共用一個(gè)GroupID遇八,如com.android.support下就有很多個(gè)子項(xiàng)目矛绘。

  • module, maven項(xiàng)目的ArtifactID,ArtifactID就是具體某個(gè)項(xiàng)目的唯一的標(biāo)識(shí)符,實(shí)際常常對(duì)應(yīng)項(xiàng)目的名稱刃永,就是項(xiàng)目根目錄的名稱货矮,如support-annotations

groupmodule可以配合一起使用也可以單獨(dú)使用揽碘。

  • 配合使用
//移除所有依賴項(xiàng)中次屠,組織為`com.android.support`項(xiàng)目為`support-annotations`的子依賴項(xiàng)
configurations {
   all*.exclude group: 'com.android.support', module: 'support-annotations'
}

//移除單個(gè)依賴項(xiàng)中,組織為`com.android.support`項(xiàng)目為`support-annotations`的子依賴項(xiàng)
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
})
  • 單獨(dú)使用groupmodule
//移除所有依賴項(xiàng)中雳刺,組織為`com.android.support`的子依賴項(xiàng)
configurations {
   all*.exclude group: 'com.android.support'
}

//移除單個(gè)依賴項(xiàng)中劫灶,組織為`com.android.support`的子依賴項(xiàng)
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support'
})

  • 單獨(dú)使用module
//移除所有依賴項(xiàng)中名為`support-annotations`的子依賴項(xiàng)
configurations {
   all*.exclude module: 'support-annotations'
}

//移除單個(gè)依賴項(xiàng)中名為`support-annotations`的子依賴項(xiàng)
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude module: 'support-annotations'
})

依賴項(xiàng)的版本沖突

gradle在同一個(gè)配置下(例如androidTestCompile),某個(gè)模塊的不同版本同時(shí)被依賴時(shí)掖桦,默認(rèn)使用最新版本昏,gradle同步時(shí)不會(huì)報(bào)錯(cuò),例如:

dependencies {
   androidTestCompile('com.android.support.test:runner:0.4')
   androidTestCompile('com.android.support.test:rules:0.2')
   androidTestCompile('com.android.support.test.espresso:espresso-core:2.1')
}

上面espresso:espresso-core依賴runner不同與上面我們上面指定的版本的runner枪汪,此時(shí)gradle會(huì)自動(dòng)同步最新版本涌穆。

對(duì)于不同的配置下,出現(xiàn)的依賴項(xiàng)不一樣雀久,gradle就會(huì)直接報(bào)錯(cuò)宿稀,這時(shí)我們就可以使用上面提到的force, exclude來解決。

10.5 參考

11 綜合

11.1 過濾不需要生成的apk

當(dāng)我們添加自定義的BuildType, flavor時(shí)越庇,必然會(huì)組合出很多我們不需要的apk罩锐,google也給我們提供了解決方案。

核心就是使用variantFilter這個(gè)方法過濾滿足我們特定條件的所有構(gòu)建變體卤唉。

// 移除不需要打包的apk
variantFilter { variant ->
    String buildTypeName = variant.buildType.name
    String flavors0Name = variant.getFlavors().get(0).name

    //對(duì)于編譯類型為`release 或者 custom` 并且 flavors0類型為`smith 或 gavin`的構(gòu)建類型直接忽略涩惑,不用編譯
    if((buildTypeName.equals('release') || buildTypeName.equals('custom'))
            && (flavors0Name.equals('smith') || flavors0Name.equals('gavin'))) {
        variant.setIgnore(true);
    }
}

11.2 apk名稱修改

Android studio 構(gòu)建生成的apk的默認(rèn)名稱為app-flavor[0]-...-flavor[n]-buildType.apk,google給我們提供了修改Apk名稱的方法桑驱。

applicationVariants包含了所有可能構(gòu)建變體的集合竭恬,我們使用閉包遍歷所有的輸出,修改我們想修改的apk的名稱熬的,以下是一個(gè)示例萍聊。

//修改生成的apk的名稱,命名為demo-flavorsName-buildType-versionName.apk
applicationVariants.all { variant ->
    //遍歷所有的輸出文件
    variant.outputs.each { output ->
        File tempFile = output.outputFile
        //對(duì)于包含`commonClient` flavor的我們?cè)诿Q中去掉它
        if (variant.productFlavors[1].name.contains("commonClient")) {

            output.outputFile = new File(tempFile.parent, tempFile.name.replace(tempFile.name,
                    "demo" + variant.productFlavors[0].name + "_" + variant.buildType.name + "_${variant.versionName}.apk"))
        } else {
            output.outputFile = new File(tempFile.parent, tempFile.name.replace(tempFile.name,
                    "demo" + variant.productFlavors[0].name + "_" + variant.productFlavors[1].name + "_" + variant.buildType.name + "_${variant.versionName}.apk"))
        }
    }
}

11.3 mainfest文件添加屬性

當(dāng)我們打多渠道包或需要給不同的構(gòu)建變體加入的不同的屬性時(shí)悦析,此時(shí)我們就需要修改mainfest文件,gradle給我們提供了兩種方式動(dòng)態(tài)的修改mainfest文件此衅。

在變體對(duì)應(yīng)src目錄下添加一個(gè)mainfest文件

在添加的mainfest文件中添加/修改配置項(xiàng)强戴,此種方式與動(dòng)態(tài)合并res/values/strings.xml的方式一致亭螟,其遵守的規(guī)則也和它們保持一致,這種方式不僅能添加屬性骑歹,還能添加四大組件等等预烙,具體可以參考:

Mainfest合并規(guī)則:https://developer.android.google.cn/studio/build/manifest-merge.html

使用APG中的manifestPlaceholders屬性

manifestPlaceholdersbuild.gradle中以鍵值對(duì)的方式給mainfest中對(duì)應(yīng)鍵設(shè)置相應(yīng)的值的方式來實(shí)現(xiàn),看如下示例道媚,我在mainfest文件中添加以下需要?jiǎng)討B(tài)設(shè)置的屬性扁掸。

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        .....
        <!--渠道配置信息AppKey  -->
        <meta-data
            android:name="APP_KEY"
            android:value="${APP_KEY_VALUE}"/>

        <!-- 產(chǎn)品ID -->
        <meta-data
            android:name="APP_ID"
            android:value="${APP_ID_VALUE}"/>
    
        .....

   </application>

接下來我分別在defaultCofig, sky, gavin三個(gè)變體中從不同路徑的配置文件config.xml中讀取屬性最域,這里我把解析xml文件放在根目錄下自定義的utils.gradle文件中谴分。


/**
 * 解析XML文件
 * */
def parseXml(String path) {
    println ("parseXml called, path = " + path)
    return new XmlParser().parse(path)
}


ext {
    parseXml       = this.&parseXml
}

然后在模塊的build.gradle文件中引用它。

// 加載自定義的utils.gradle
apply from: rootProject.projectDir.getAbsolutePath() + File.separator + "utils.gradle"

最后在三個(gè)變體中解析相應(yīng)的配置文件镀脂。

defaultConfig {
    
    ......

    def defaultConfig = parseXml("app/config/main/config.xml")
    manifestPlaceholders = [
            APP_KEY_VALUE : defaultConfig.appKey[0].text(),
            APP_ID_VALUE  : defaultConfig.id[0].text()
    ]
}

flavor{

    sky{
        ......
        def skyConfig = parseXml("app/config/sky/config.xml")
        manifestPlaceholders = [
            APP_KEY_VALUE : skyConfig.appKey[0].text(),
            APP_ID_VALUE  : skyConfig.id[0].text()
        ]
    }
    
    gavin{
        ......
        def gavinConfig = parseXml("app/config/gavin/config.xml")
        manifestPlaceholders = [
                APP_KEY_VALUE : gavinConfig.appKey[0].text(),
                APP_ID_VALUE  : gavinConfig.id[0].text()
        ]
        
    }
    
    ......
}

這樣最后構(gòu)建出來的apk使用的就是上面配置的不同的值牺蹄。

11.4 APG動(dòng)態(tài)生成的BuildConfig類的使用

使用BuildConfig

使用APG構(gòu)建apk,會(huì)動(dòng)態(tài)自動(dòng)生成一個(gè)BuildConfig類薄翅,里面會(huì)包含當(dāng)前構(gòu)建變體的一些基本屬性沙兰,如版本號(hào)等等,一下是一個(gè)默認(rèn)下的示例(當(dāng)前構(gòu)建變體是skyCommonClientDebug)翘魄。

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.sky.gradlebuilddemo;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.inpor.fmcdevicedemon.sky";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "skyCommonClient";
  public static final int VERSION_CODE = 120;
  public static final String VERSION_NAME = "1.2.0";
  public static final String FLAVOR_type = "sky";
  public static final String FLAVOR_common = "commonClient";
}

一種最典型的用法鼎天,就是在源代碼中判斷當(dāng)前是不是debug版本,然后做某些操作暑竟,如果是其他版本又做什么操作等等斋射。

一個(gè)示例,在MainActivity中添加如下代碼光羞。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    
    .....

    private void checkBuildConfig(){
        if(BuildConfig.DEBUG){
            Log.i(TAG, "now this is debug build");
        }
    }
}

自定義BuildConfig屬性

APG給我們提供了自定義BuildConfig屬性的方法buildConfigField绩鸣,注意,添加Field時(shí)纱兑,最好在defaultConfig中給要添加的Field設(shè)置一個(gè)默認(rèn)值呀闻,否則當(dāng)編譯其他沒有設(shè)置該Field的變體時(shí),會(huì)編譯報(bào)錯(cuò)潜慎,我們可以在buildTypes, flavors中復(fù)寫它捡多。

在下面的示例中我添加一個(gè)字段。

defaultConfig {
    
    ......
    buildConfigField 'int', 'ID', '0'
}

flavor{

    sky{
        ......
        buildConfigField 'int', 'ID', '1'
    }
    
    ......
}

最終生成的BuildConfig是這樣的铐炫。

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.sky.gradlebuilddemo;

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.inpor.fmcdevicedemon.sky";
  public static final String BUILD_TYPE = "debug";
  public static final String FLAVOR = "skyCommonClient";
  public static final int VERSION_CODE = 120;
  public static final String VERSION_NAME = "1.2.0";
  public static final String FLAVOR_type = "sky";
  public static final String FLAVOR_common = "commonClient";
  // Fields from product flavor: sky
  public static final int ID = 1;
  // Fields from default config.
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末垒手,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子倒信,更是在濱河造成了極大的恐慌科贬,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異榜掌,居然都是意外死亡优妙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門憎账,熙熙樓的掌柜王于貴愁眉苦臉地迎上來套硼,“玉大人,你說我怎么就攤上這事胞皱⌒耙猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵反砌,是天一觀的道長(zhǎng)雾鬼。 經(jīng)常有香客問我,道長(zhǎng)于颖,這世上最難降的妖魔是什么呆贿? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮森渐,結(jié)果婚禮上做入,老公的妹妹穿的比我還像新娘。我一直安慰自己同衣,他們只是感情好竟块,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耐齐,像睡著了一般浪秘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上埠况,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天耸携,我揣著相機(jī)與錄音,去河邊找鬼辕翰。 笑死夺衍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喜命。 我是一名探鬼主播沟沙,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼壁榕!你這毒婦竟也來了矛紫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤牌里,失蹤者是張志新(化名)和其女友劉穎颊咬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贪染,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年缓呛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杭隙。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖因妙,靈堂內(nèi)的尸體忽然破棺而出痰憎,到底是詐尸還是另有隱情,我是刑警寧澤攀涵,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響丈莺,放射性物質(zhì)發(fā)生泄漏护锤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一怒详、第九天 我趴在偏房一處隱蔽的房頂上張望炉媒。 院中可真熱鬧,春花似錦昆烁、人聲如沸吊骤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽白粉。三九已至,卻和暖如春鼠渺,著一層夾襖步出監(jiān)牢的瞬間鸭巴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工拦盹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹃祖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓掌敬,卻偏偏與公主長(zhǎng)得像惯豆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奔害,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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