1. 前言
Gradle系列已完成,專注于Gradle,有如下幾篇文章
- Gradle系列(一) Groovy 基礎(chǔ)
- Gradle系列(二) Gradle執(zhí)行順序和task
- Gradle系列(三) Gradle配置構(gòu)建和渠道包
- Gradle系列(四) Gradle插件
Android開發(fā),打包的時候可能會打內(nèi)測包,外側(cè)包,release包等,還有就是有時候還需要打不同渠道的包等.這時它們里面的包名,應(yīng)用圖標,應(yīng)用名稱,某些資源文件,某些java文件等可能不同,如果通過人工去手動改,改了之后再打包的話,那就太麻煩了.現(xiàn)在有了Gradle,它可以幫到我們.
ps: 請先搞懂Android DSL的基本配置,比如compileSdkVersion是什么本文不會再介紹.有需要則查官方文檔,還有就是皇叔寫的寫給Android開發(fā)的Gradle知識體系非常不錯.
demo源碼: GradleStudy
2. 統(tǒng)一配置
2.1 以前的配置方式
今天我來帶大家實現(xiàn)一種很方便的配置項目諸如compileSdkVersion,三方庫引入等.最終的效果如下,可以直接通過Config點出來,并且還可以通過Ctrl+鼠標左鍵點過去.
以前,很多很多項目會將一些基本的配置放到Project的build.gradle中,類似
ext {
compileSdkVersion = 29
buildToolsVersion = "29.0.0"
targetSdkVersion = 29
minSdkVersion = 21
versionCode = 1
versionName = "1.0.0"
}
然后在各個module的build.gradle中進行使用這個配置
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
versionCode rootProject.versionCode
versionName rootProject.versionName
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
}
}
這種方式可以,但是不夠優(yōu)雅.我們寫好rootProject,然后再輸入"."的時候AS不會提示你有哪些可用的變量,不能智能提示.而且即使你一字不差的寫好了,用Ctrl+鼠標左鍵也點不過去.在ext{}下的那些變量,你用快捷鍵搜索在哪些地方使用到了,AS也不知道....是不是覺得差點意思.
2.2 推薦的配置方式
ps: 這種配置方式,最開始是看到柯基大佬在使用,覺得太棒了,哈哈.這種配置方式,好像只能是3.5+版本的AS
我們來實現(xiàn)一種更優(yōu)雅的方式,實現(xiàn)上面的功能.創(chuàng)建一個buildSrc這個名字的module,這個module的名稱必須為buildSrc.因為我們創(chuàng)建的這個module是AS專門用來寫插件的,會自動參與編譯.創(chuàng)建好之后刪除Android那一堆東西,什么java代碼,res,清單文件等.只剩下build.gradle和.gitignore
把build.gradle文件內(nèi)容改成
repositories {
google()
jcenter()
}
apply {
plugin 'groovy'
plugin 'java-gradle-plugin'
}
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation "commons-io:commons-io:2.6"
}
然后在main下面創(chuàng)建文件夾groovy,sync一下.沒啥問題的話,應(yīng)該能編譯過.然后在groovy文件夾下面創(chuàng)建Config.groovy文件
class Config {
static applicationId = 'com.xfhy.gradledemo'
static appName = 'GradleDemo'
static compileSdkVersion = 29
static buildToolsVersion = '29.0.2'
static minSdkVersion = 22
static targetSdkVersion = 29
static versionCode = 1
static versionName = '1.0.0'
}
可以看到,我們將常用配置全部填入這里.這個時候去module的build.gradle將這些參數(shù)全部替換掉.
android {
compileSdkVersion Config.compileSdkVersion
buildToolsVersion Config.buildToolsVersion
defaultConfig {
applicationId Config.applicationId
minSdkVersion Config.minSdkVersion
targetSdkVersion Config.targetSdkVersion
versionCode Config.versionCode
versionName Config.versionName
}
....
}
完美.同理,將三方庫也可以加進來
class Config {
static depConfig = [
support : [
appcompat_androidx : "androidx.appcompat:appcompat:$appcompat_androidx_version",
recyclerview_androidx: "androidx.recyclerview:recyclerview:$recyclerview_androidx_version",
design : "com.google.android.material:material:$design_version",
multidex : "com.android.support:multidex:$multidex_version",
constraint : "com.android.support.constraint:constraint-layout:$constraint_version",
],
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
leakcanary : [
android : "com.squareup.leakcanary:leakcanary-android:$leakcanary_version",
android_no_op : "com.squareup.leakcanary:leakcanary-android-no-op:$leakcanary_version",
support_fragment: "com.squareup.leakcanary:leakcanary-support-fragment:$leakcanary_version",
],
]
}
在build.gradle中使用
dependencies {
implementation Config.depConfig.support.recyclerview_androidx
....
}
3. 渠道包
3.1 productFlavors
productFlavors直譯為產(chǎn)品風味,Android這邊用它來做多渠道.在app的build.gradle中加入如下配置
android {
flavorDimensions "channel"
productFlavors {
free {
dimension "channel"
//程序包名
applicationId "com.xfhy.free"
//替換清單文件中的標簽
manifestPlaceholders = [
APP_ICON: "@drawable/ic_launcher",
APP_NAME: "xx免費版",
]
//versionName
versionName "2.0.0"
//versionCode
versionCode 2
}
vip {
dimension "channel"
//程序包名
applicationId "com.xfhy.vip"
//替換清單文件中的標簽
manifestPlaceholders = [
APP_ICON: "@drawable/ic_launcher",
APP_NAME: "xxVip版",
]
//versionName
versionName "3.0.0"
//versionCode
versionCode 3
}
svip {
dimension "channel"
}
}
}
如代碼所示,我們配置了3種類型的風味,在productFlavors中可以配置包名(applicationId)、版本號(versionCode)拭抬、版本名(versionName)、icon帽蝶、應(yīng)用名.并且可以在里面配置各種你之前在defaultConfig里面配置的東西.還可以配置src代碼目錄,res目錄之類的.并且這個時候Build Variants里面有了多種類型,比如:freeDebug,freeRelease,vipDebug,vipRelease等.你在Build Variants里面選擇freeDebug,則是使用free風味,并且是debug時使用的配置.
<application
xmlns:tools="http://schemas.android.com/tools"
android:icon="${APP_ICON}"
android:label="${APP_NAME}"
android:theme="@style/AppTheme"
android:largeHeap="true"
tools:replace="android:label">
...
</application>
3.2 渠道變量
首先來介紹一個關(guān)鍵詞擴展:applicationVariants,它是在AppExtension里面的,它的官方文檔,它意思是返回應(yīng)用程序項目包含的構(gòu)建變體的集合,是用all關(guān)鍵詞進行遍歷.我們拿到了這些變體之后,可以根據(jù)當前是哪個變體來構(gòu)建出相應(yīng)變體所特殊的變量.比如內(nèi)測和外測它們的地址肯定不一樣的,那么通過這種方式可以很方便地整出來.構(gòu)建的變量會存在于相應(yīng)的BuildConfig中,然后在java代碼中直接引用就行,替換地址時也不需要動java代碼,只需在gradle中改一下,然后它編譯的時候就會自動構(gòu)建BuildConfig,自動將地址搞成最新的了.說了這些多,show me the code!
android {
applicationVariants.all { variant ->
//構(gòu)建變體專屬變量
switch (variant.flavorName) {
case 'free':
buildConfigField("String", "BASE_URL", "\"http://31.13.66.23\"")
buildConfigField("String", "TOKEN", "\"dhaskufguakfaskfkjasjhbfree\"")
break
case 'vip':
buildConfigField("String", "BASE_URL", "\"http://31.13.66.24\"")
buildConfigField("String", "TOKEN", "\"dhaskfagafkjasjhbvip\"")
break
case 'svip':
buildConfigField("String", "BASE_URL", "\"http://31.13.66.25\"")
buildConfigField("String", "TOKEN", "\"dhaskufgufgsdagajasjhbsvip\"")
break
}
}
}
將上面的代碼寫在app的build.gradle中,在上面的gradle代碼中我們定義了2個變量,不同的變體會構(gòu)建不同的值,比如上面的BASE_URL
我們會在free變體編譯的時候就會在BuildConfig生成一個變量,值是http://31.13.66.23
.我們來看一下BuildConfig中是些什么內(nèi)容:
//build\generated\source\buildConfig\free\debug\com\xfhy\gradledemo\BuildConfig.java
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.xfhy.free";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "free";
public static final int VERSION_CODE = 2;
public static final String VERSION_NAME = "2.0.0";
// Fields from the variant
public static final String BASE_URL = "http://31.13.66.23";
public static final String TOKEN = "dhaskufguakfaskfkjasjhbfree";
}
這個文件是gradle構(gòu)建時自動為我們創(chuàng)建的,不需要去修改.我們構(gòu)建的變體變量在最下面,這里面的值確實是我們在gradle代碼中寫的那樣.這個里面已經(jīng)有一些不是我們搞出來的變量了,比如是否是DEBUG,APPLICATION_ID,VERSION_CODE之類的.我們在java代碼中使用的時候,直接BuildConfig.BASE_URL
這種方式進行使用即可,它就是一個普通的java類,里面定義了一些變量而已.
當然除了上面的渠道變量之外,還有一些變量是公用的,每個變體都是一樣的那種.我們可以寫到defaultConfig
下面.
android {
defaultConfig {
buildConfigField("String", "APP_DESCRIPTION", "\"你沒有見過的船新版本\"")
buildConfigField("String[]", "TAB", "{\"首頁\",\"排行榜\",\"我的\"}")
}
}
3.3 打包文件命名
還是利用上面的applicationVariants
,當我們拿到了變體之后,在打包的時候動態(tài)的將打包之后的文件名改一下.比如改成下面這種形式
applicationVariants.all { variant ->
variant.outputs.all {
def type = variant.buildType.name
def channel = variant.flavorName
outputFileName = "demo_${variant.versionName}_${channel}_${type}.apk"
}
}
最后它打出來的包是這樣的demo_2.0.0_free_debug.apk
,寫完之后可以使用gradlew assembleFreeDebug
命令試一下.命令運行之后會在app\build\outputs\apk\free\debug
目錄下產(chǎn)生相應(yīng)的apk文件.
3.4 簽名
可以在gradle中指定打包時的簽名文件,密碼啥的
signingConfigs {
debug {
storeFile file('../keys/xfhy.jks')
storePassword "qqqqqq"
keyAlias "xfhy"
keyPassword "qqqqqq"
v1SigningEnabled true
v2SigningEnabled true
}
release {
storeFile file('../keys/xfhy.jks')
storePassword "qqqqqq"
keyAlias "xfhy"
keyPassword "qqqqqq"
v1SigningEnabled true
v2SigningEnabled true
}
}
指定了簽名以及密碼之后,打包的時候就只需要在命令行執(zhí)行gradlew assembleVipRelease
即可,不用打開Android Studio了.
3.5 資源
Android Studio提供了代碼整合功能.只需要創(chuàng)建app/src/xxFlavorName/assets
,app/src/xxFlavorName/src
,app/src/xxFlavorName/res
即可.當在Build Variants中切換切換變體之后,AS就只會編譯對應(yīng)變體的資源+main下面的資源.
可以看到,free下面的文件夾自動變色了,這些是free變體特殊的東西,只有在free編譯的時候才會被用到.java代碼,res資源等,到時是需要和main下面的一起合并的.
假如我在free變體下創(chuàng)建了Test.java,然后可以在main下面引用到,就和平時使用一樣.但是如果相同包名下如果free中有Test.java,main中也有,那么是編譯不過的. 還有就是當main里面用到了Test.java的時候,在Build Variants中切換成了vip,而vip中剛好沒有Test.java,就會報錯的,因為找不到這個文件.
上面這個問題,可以用sourceSets來解決,sourceSets可以指定代碼資源文件的位置.雖然上面創(chuàng)建的free,vip等變體文件夾下面也是放這些東西的,但是用sourceSets比他們優(yōu)先級高.
下面來看它的普通用法,一看就懂.
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
然后我們除了在src/main/java
下有java代碼,還可以指定在其他地方有java代碼.比如下面這樣.可以在src下面創(chuàng)建common/java
文件夾,用于存放公共的代碼.
sourceSets {
sourceSets.main.java.srcDirs = ['src/main/java', 'src/common/java']
}
上面的Test.java問題,可以用sourceSets解決.在common文件夾創(chuàng)建一個公共的Test.java,然后其他變體可以使用.在free在使用自己特殊的Test.java,只拿給free用.
項目的結(jié)果是這樣的,這是變體是vip的時候:
sourceSets {
main {
java.srcDirs = ['src/main/java']
}
free {
java.srcDirs = ['src/free/java']
}
svip {
java.srcDirs = ['src/common/java']
}
vip {
java.srcDirs = ['src/common/java']
}
}
4. 總結(jié)
又學到了一大波干貨內(nèi)容.對于渠道包,可能不一定會用得到,但是其實還是挺有用的. 同一套代碼可以產(chǎn)出多個app,俗稱馬甲包,可能很多公司都在搞這種.如果用得上,希望能幫到你.
參考:
- 這樣使用Gradle可以神奇地打各種渠道包 https://mp.weixin.qq.com/s/_CahiMe8A6m40TI-iiP9kw
- 一個項目如何編譯多個不同簽名弃甥、包名扶供、資源等缎玫,的apk趟径? https://mp.weixin.qq.com/s/OQtAVhQVPNVxo9zJc3NG9w