參考
中文官網(wǎng)構(gòu)建指南:https://developer.android.google.cn/studio/build/index.html#
配置構(gòu)建:https://developer.android.google.cn/studio/build/index.html
Mainfest合并規(guī)則:https://developer.android.google.cn/studio/build/manifest-merge.html
多變體(特定版本)構(gòu)建指南:https://developer.android.google.cn/studio/build/build-variants.html
裁剪未使用代碼和資源:https://developer.android.google.cn/studio/build/shrink-code.html
配置依賴項(xiàng):https://developer.android.google.cn/studio/build/dependencies.html
名詞
- 構(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)如下:
包含三個(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
}
- 上面指定遠(yuǎn)程倉庫的作用就是在需要依賴的庫在本地找不到時(shí)酣栈,會(huì)到該倉庫中去尋找并自動(dòng)下載险胰。
- 依賴的構(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)琉闪。
比如上面我們點(diǎn)開other
目錄迹炼,雙擊第一個(gè)任務(wù),此時(shí)就可以直接這個(gè)任務(wù),生成一個(gè)apk斯入。
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
編譯類型疙筹,另外用戶無法自定義為test
的type
富俄,它已經(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)
其作用是將當(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ǔ)充帐要。
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
的屬性截圖:
在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)屑柔。
- 基本屬性
-
方法與閉包
從上面的屬性與方法中我們發(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è)Dimensions
在productFlavors
中的個(gè)數(shù)是晨,比如上面的最終能夠生成的apk個(gè)數(shù)就是:3 * 3(type)* 2(common) = 18。
8.3 資源替換
同樣的我們也可以在src\main
的同級(jí)目錄下給每個(gè)productFlavors
建立目錄存放我們的特定資源舔箭。
替換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è)BuildType
或ProductFlavor
或BuildVariant
聊倔,看官方的文檔描述晦毙。
參考: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í)是怎樣的呢钱豁?
需要補(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中使用的是google
的AndroidSourceSet
。
android
閉包中的sourceSets
就是由上面用戶定義的一系列的AndroidSourceSet
的集合溃卡。
先看AndroidSourceSet
的DSL屬性結(jié)構(gòu)圖:
從上面的圖中我們可以看出溢豆,針對(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)注意:
srcDir
和srcDirs
的區(qū)別膊夹,當(dāng)使用srcDir
指定文件目錄時(shí),不允許將要合并的源集的同一目錄下有一樣名稱的資源捌浩,提示重復(fù)資源異常放刨,而srcDirs
則會(huì)根據(jù)前面所說的優(yōu)先級(jí)進(jìn)行覆蓋。如果我們?cè)?code>src/main的同級(jí)目錄下尸饺,也建立一個(gè)如下的文件目錄:
當(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為例)。
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
。
group
和module
可以配合一起使用也可以單獨(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ú)使用
group
和module
//移除所有依賴項(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 參考
-
Google官網(wǎng)依賴項(xiàng)說明:
-
Gradle官方說明文檔:
-
依賴的
dependencies赖捌、transitive祝沸、force、exclude
及版本沖突處理
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
屬性
manifestPlaceholders
在build.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.
}