1. 前言
Gradle系列已完成,專注於Gradle,有如下幾篇文章
Android開發,打包的時候可能會打內測包,外側包,release包等,還有就是有時候還需要打不同渠道的包等.這時它們裡面的包名,應用圖示,應用名稱,某些資原始檔,某些java檔案等可能不同,如果通過人工去手動改,改了之後再打包的話,那就太麻煩了.現在有了Gradle,它可以幫到我們.
ps: 請先搞懂Android DSL的基本配置,比如compileSdkVersion是什麼本文不會再介紹.有需要則查官方文件,還有就是皇叔寫的寫給Android開發的Gradle知識體系非常不錯.
demo原始碼: github.com/xfhy/Gradle…
2. 統一配置
2.1 以前的配置方式
今天我來帶大家實現一種很方便的配置專案諸如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
}
}
複製程式碼
這種方式可以,但是不夠優雅.我們寫好rootProject,然後再輸入"."的時候AS不會提示你有哪些可用的變數,不能智慧提示.而且即使你一字不差的寫好了,用Ctrl+滑鼠左鍵也點不過去.在ext{}下的那些變數,你用快捷鍵搜尋在哪些地方使用到了,AS也不知道....是不是覺得差點意思.
2.2 推薦的配置方式
ps: 這種配置方式,最開始是看到柯基大佬在使用,覺得太棒了,哈哈.這種配置方式,好像只能是3.5+版本的AS
我們來實現一種更優雅的方式,實現上面的功能.建立一個buildSrc這個名字的module,這個module的名稱必須為buildSrc.因為我們建立的這個module是AS專門用來寫外掛的,會自動參與編譯.建立好之後刪除Android那一堆東西,什麼java程式碼,res,清單檔案等.只剩下build.gradle和.gitignore
把build.gradle檔案內容改成
repositories {
google()
jcenter()
}
apply {
plugin 'groovy'
plugin 'java-gradle-plugin'
}
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation "commons-io:commons-io:2.6"
}
複製程式碼
然後在main下面建立資料夾groovy,sync一下.沒啥問題的話,應該能編譯過.然後在groovy資料夾下面建立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將這些引數全部替換掉.
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直譯為產品風味,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、應用名.並且可以在裡面配置各種你之前在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 渠道變數
首先來介紹一個關鍵詞擴充套件:applicationVariants,它是在AppExtension裡面的,它的官方文件,它意思是返回應用程式專案包含的構建變體的集合,是用all關鍵詞進行遍歷.我們拿到了這些變體之後,可以根據當前是哪個變體來構建出相應變體所特殊的變數.比如內測和外測它們的地址肯定不一樣的,那麼通過這種方式可以很方便地整出來.構建的變數會存在於相應的BuildConfig中,然後在java程式碼中直接引用就行,替換地址時也不需要動java程式碼,只需在gradle中改一下,然後它編譯的時候就會自動構建BuildConfig,自動將地址搞成最新的了.說了這些多,show me the code!
android {
applicationVariants.all { variant ->
//構建變體專屬變數
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個變數,不同的變體會構建不同的值,比如上面的BASE_URL
我們會在free變體編譯的時候就會在BuildConfig生成一個變數,值是http://31.13.66.23
.我們來看一下BuildConfig中是些什麼內容:
//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構建時自動為我們建立的,不需要去修改.我們構建的變體變數在最下面,這裡面的值確實是我們在gradle程式碼中寫的那樣.這個裡面已經有一些不是我們搞出來的變數了,比如是否是DEBUG,APPLICATION_ID,VERSION_CODE之類的.我們在java程式碼中使用的時候,直接BuildConfig.BASE_URL
這種方式進行使用即可,它就是一個普通的java類,裡面定義了一些變數而已.
當然除了上面的渠道變數之外,還有一些變數是公用的,每個變體都是一樣的那種.我們可以寫到defaultConfig
下面.
android {
defaultConfig {
buildConfigField("String", "APP_DESCRIPTION", "\"你沒有見過的船新版本\"")
buildConfigField("String[]", "TAB", "{\"首頁\",\"排行榜\",\"我的\"}")
}
}
複製程式碼
3.3 打包檔案命名
還是利用上面的applicationVariants
,當我們拿到了變體之後,在打包的時候動態的將打包之後的檔名改一下.比如改成下面這種形式
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
目錄下產生相應的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
}
}
複製程式碼
指定了簽名以及密碼之後,打包的時候就只需要在命令列執行gradlew assembleVipRelease
即可,不用開啟Android Studio了.
3.5 資源
Android Studio提供了程式碼整合功能.只需要建立app/src/xxFlavorName/assets
,app/src/xxFlavorName/src
,app/src/xxFlavorName/res
即可.當在Build Variants中切換切換變體之後,AS就只會編譯對應變體的資源+main下面的資源.
可以看到,free下面的資料夾自動變色了,這些是free變體特殊的東西,只有在free編譯的時候才會被用到.java程式碼,res資源等,到時是需要和main下面的一起合併的.
假如我在free變體下建立了Test.java,然後可以在main下面引用到,就和平時使用一樣.但是如果相同包名下如果free中有Test.java,main中也有,那麼是編譯不過的. 還有就是當main裡面用到了Test.java的時候,在Build Variants中切換成了vip,而vip中剛好沒有Test.java,就會報錯的,因為找不到這個檔案.
上面這個問題,可以用sourceSets來解決,sourceSets可以指定程式碼資原始檔的位置.雖然上面建立的free,vip等變體資料夾下面也是放這些東西的,但是用sourceSets比他們優先順序高.
下面來看它的普通用法,一看就懂.
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下面建立common/java
資料夾,用於存放公共的程式碼.
sourceSets {
sourceSets.main.java.srcDirs = ['src/main/java', 'src/common/java']
}
複製程式碼
上面的Test.java問題,可以用sourceSets解決.在common資料夾建立一個公共的Test.java,然後其他變體可以使用.在free在使用自己特殊的Test.java,只拿給free用.
專案的結果是這樣的,這是變體是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. 總結
又學到了一大波乾貨內容.對於渠道包,可能不一定會用得到,但是其實還是挺有用的. 同一套程式碼可以產出多個app,俗稱馬甲包,可能很多公司都在搞這種.如果用得上,希望能幫到你.
參考:
- 這樣使用Gradle可以神奇地打各種渠道包 mp.weixin.qq.com/s/_CahiMe8A…
- 一個專案如何編譯多個不同簽名、包名、資源等,的apk? mp.weixin.qq.com/s/OQtAVhQVP…