同時安裝正式版與 debug 版的方法
開發測試不再需要覆蓋正式版 App

最近接手了一個新的專案,測試 app 時需要覆蓋掉原來機器上已經有的 release 版本。當需要對比線上版本與目前開發中的版本行為上的不同之處時,造成了不方便。其實,可以利用 gradle script 於編譯 debug 版時修改不一樣的 Package Name,達成正式版與測試版同時安裝在同一台機器上的目標。
分離 packageName (application id)
這裡的分離 packageName,不限定是 release 版和 debug 版兩種 BuildTypes。其實,可以依照自己的需求切分各種不同的 packageName。
而最近我自己遇到的需求是,希望不取代線上的 release 版來對比開發中的版本行為是否一致,所以我只需要分離 release 和 debug 這兩種 buildTypes 即可。
為了達成目標,會需要使用到的是 applicationIdSuffix [doc] 這個設定值。舉個例子來說明,以下範例設定檔擷取自我的專案 CoffeeTrip 。
套用了這個設定後,debug build 會在 PackageName 後頭加上 .debug 與正式版的 packageName 不一樣,debug build 最後的 packageName 會成為 tw.com.louis383.coffeefinder.debug,就能夠連同正式版一起安裝。

如果是不同的 productFlavor 也能如法炮製,
但很可惜的是 android.variantFilter 沒有 applicationIdSuffix 可供設定的。需要更複雜的情境,可以用 flavorDimensions 來為各個 ProductFlavors 設定 dimension。透過 ProductFlavors 設定 applicationId ,再配合不同 buildTypes 去指定 applicationIdSuffix 。
照上面的設定,可以 build 出 2 (FlavorDimensions) * 2 (ProductFlavors) * 2( BuildTypes) = 8 種不同的 app。

另外,需要處理 paidGlobal、freeChina 此類 merged Flavor 的話,可以透過 application.variants 進行細部處理。
不一樣的應用程式名稱
現在可以根據不同的需求編譯出不同的 app 共存,但如果編譯出來 app 的名稱都相同,反而會造成新的困擾。
就像不同的 ProductFlavors/BuildTypes 使用不同的目錄區別程式碼,Gradle 可以根據不同目錄,去取得資源。
以我的專案 CoffeeTrip 為例,我只有切分 debug 和 release 版所以只需要可以區別兩者即可。所以,我在專案的程式碼目錄下新建了 debug/res/values 目錄,再建立一個 strings.xml 。

編譯 debug build 時,會透過 debug/res/values 目錄下的字串設定取得 App 名稱;而編譯 release build 時,透過 main/res/values 下的 strings.xml 取得。

另外,也可以在 build.gradle 中寫設定處理不同 ProductFlavors/BuildTpyes 的 AppName,這樣子就不用新建這麼多目錄。
注意事項
分離好環境之後,一些服務 (如 Facebook 登入、Firebase、 Google Map API for Android ⋯) 需要在該服務後台,加上新的 packageName。
有時AndroidManifest.xml 裡頭的一些設定值也需要指定 packageName,例如 FileProvider 、uses-permission (像是以前的 GCM Permission)、intent-filter 的設定值。
如果不依照動態變化過的 packageName 來設定,會出現錯誤。在這個地方,我們可以利用 manifestPlacehoders 來處理需要動態調整 AndroidManifest.xml 內容的情境。
這裡用以前 GCM Permission (現在已不需要,因為 FCM 取代了 GCM,並且不用設定這個 Permission) 作為例子說明,再自行套入各自的需求即可。
首先在 build.gradle 中加入 manifestPlaceholders 的設定:
如此一來, AndroidManifest.xml 設定值可以透過 ${variableName} 來取得數值。
另外,可以從 resValue 或 BuildConfigField設定 applicationId,在 Java/Kotlin 程式碼內需要存取 packageName 時,獲取動態變化過的數值。
resValue('string', 'applicationId', applicationId)// to get the value
val value = resources.getString(R.string.applicationId)orBuildConfigField 'String' 'APPLICATION_ID' applicationId// to get the value
val value = BuildConfig.APPLICATION_ID
為 Icon 加點變化
如同上面的 App 名稱修改的需求—— 為各個不同的 buildTypes/ProductFlavors 設定自己的 icon 。為達目的,可以直接在個別的資源目錄下放置不同的 icon 。但這需要做額外的功,最近注意到一個有趣的 Library,可以設定不同的情境為 icon 做點變化。
Easylauncher gradle plugin for Android
這個 Library 可以把 icon 加上緞帶,方便識別各種不同的 buildTypes/ProductFlavors 。

不過對於 Android 8.0 的 Adaptive Icon 支援度還不太好。被加上緞帶的 icon 是 fourground-icon , background-icon 則不受影響。造成緞帶縮小得比背景圖層小,緞帶上的字很多的話,則需要特別注意。