使用 多渠道productFlavor、多客戶、多版本buildType 、多模組moudle 配置的 那些點點滴滴
---------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------- 專案 使用 多渠道 已經蠻久了, 最近又有新需求過來了,所以多渠道這塊也重新看了下。。。。
然而新需求很坑爹(或許是我能力不夠),反正 事實就是 浪費了我兩天時間, 看了N多部落格,也沒找到對應的解決方案
---------------------------------------------------------------------------------------------------------------------------------------------------------------
一、總覽
-
buildType
- productFlavors
- sourceSets
- 佔位符 manifestPlaceholders 和 動態引數buildConfigField
- 編譯變體版本(修改名稱,自定義編譯)
- 多模組的動態引數配置
- 個人小結
- 疑問
- 拓展
以下兩片參考文章 提供了詳細的 gradle中的相關引數介面
二、buildType 編譯型別
- 可以重定義 一切 defaultConfig{} 裡的引數 和 android{} 下的直接引數 (後者未完全驗證,有誤請指正)
- 可以定義 定位符(manifestPlaceholders) ,動態引數(buildConfigField)、簽名、混淆等等配置,
- 可以自定義增加 如下面(test),通過initwith,重新拷貝了一份debug的配置(當前已經定義的debug配置)
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug{ } test.initWith(buildTypes.debug) test{ } }
三、productFlavors 渠道配置 (因為一開始是因釋出渠道寫的,所以渠道號叫習慣了,並非正式翻譯)
其實看了下百度翻譯 是產品特點,主要是 各個不同的配置。(1和2都可上面一樣)
- 可以重定義 一切 defaultConfig{} 裡的引數 和 android{} 下的直接引數 (後者未完全驗證,有誤請指正)
- 可以定義 定位符(manifestPlaceholders) ,
- 動態引數(buildConfigField)、
- 簽名、
- 混淆等等配置,
- productFlavors 使用的時候 需要現在 defaultConfig{} 配置引數 flavorDimensions, 然後再在productFlavors定義中申明對應的緯度
android{ defaultConfig { flavorDimensions "Char","Num" } productFlavors { A { dimension "Char" } B { dimension "Char" } Num1{ dimension "Num" } Num2{ dimension "Num" } } }
1. flavorDimensions 是個維度引數配置項, 用陣列比較好理解,
我這裡是2個維度,所以對應二維陣列,[A,B][Num1,Num2],
如果是 三維, 那麼就是 對應三位陣列 [A,B] [Num1,Num2] [C1,C2]
2.對應 會編譯出來的版本 有12個, 2*2*3(3是 buildType時候自定義了一個aaa)
版本名字順序是 根據 flavorDimensions順序 , 【維度一】【維度二】【緯度...】【buildType】
三、sourceSet 資原始檔配置
- 如果不設定,則是android預設的,
- main 代表主幹程式碼 (下面的是已經對主幹預設路徑修改過的)
- Num1是 分支程式碼,Num1和上面的渠道號定義保持一致。
- 不同的渠道號可以指向相同的路徑,即他們引用同一個檔案
- java檔案不會被替換,(即,"src/main/java/a.java" 和 "src/Num1/java/a.java" 是同一路徑檔案,編譯會報錯告訴你已經定義了a.java檔案)
- aidl檔案和jni檔案 未驗證,還不清楚
- 使用:用函式呼叫:定義非main的兩個目錄下,相同結構路徑,相同檔名,相同函式名,(方法內容不同)
用介面呼叫:定義非main的兩個目錄下,相同結構路徑,實現相同介面 (具體類不同)
- res下的資原始檔,同路徑下可以被直接替換
android{ sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } Num1 { manifest.srcFile 'src/' + 'Num1' + '/BctcManifest.xml' assets.srcDirs = ['src/' + 'Num1' + '/assets'] java.srcDirs = ['src/' + 'Num1' + '/java'] } } }
根據上面的 配置, 去將程式碼 或 資原始檔等 放入對應的路徑
四、動態引數定義
動態引數定義 有兩種, 一個是 佔位符 manifestPlaceholders = [] , 一個是 buildConfigField()
- 佔位符 manifestPlaceholders :就是在前面程式碼裡面 直接預留一個位置,然後等編譯的時候 把資料放進去,
- buildConfigField :是在 BuildConfig.java 這個自動生成的檔案裡面去定義一個引數
- 兩個均可以在 defaultConfig 、buildtype 、productFlavors 三處定義
- 均可在程式碼裡使用,
- 佔位符 manifestPlaceholders 可以在xml檔案中使用,如AndroidManifest.xml
manifestPlaceholders buildConfigField 在 defaultConfig{} 定義 √ √ 在 buildtype {} 定義 √ √ 在 productFlavors {} 定義 √ √ AndroidManifest.xml 使用 √ × 程式碼中 使用 √ (要xml定義,再程式碼獲取) √
buildConfigField 使用: 在定義String型別值 需要 對 雙引號 轉義, 即 需要 "ttt", 那麼定義的時候需要把雙引號也寫進去, 寫成 " \"ttt\" "
productFlavors { A { dimension "Char" buildConfigField("String", "test","\"ttt\"") buildConfigField("int", "test1","123") buildConfigField("boolean", "test2","true") } }
使用:
public void test(){ String s = BuildConfig.test; int s1 = BuildConfig.test1; boolean s2 = BuildConfig.test2; }
執行結果:
manifestPlaceholders 在AndroidManifest.xml中使用
定義:
productFlavors { Num1 { dimension "Num" manifestPlaceholders = [test_manifestPlace1: "替代的內容1",test_manifestPlace2: "替代的內容2"] } }
在AndroidManifest.xml裡直接使用:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="test7permission.cn.testduoqudao"> <application> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="${test_manifestPlace1}"/> <action android:name="${test_manifestPlace2}"/> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
執行結果:
manifestPlaceholders 程式碼中使用
定義: 需要在build.gradle裡面定義初始值,再通過AndroidManifest.xml來轉達一下
productFlavors { Num1 { dimension "Num" manifestPlaceholders = [test_manifestPlace1: "替代的內容1",test_manifestPlace2: "替代的內容2"] }
<application > <activity android:name=".MainActivity"> <!--//這一句起到至關重要作用--> <meta-data android:name="test_activity" android:value="${test_manifestPlace1}"/> </activity> <!--//這一句起到至關重要作用--> <meta-data android:name="test_application" android:value="${test_manifestPlace2}"/> </application>
在程式碼裡使用:
public static String init(Application application) { String result = ""; try { ApplicationInfo applicationInfo = application.getPackageManager().getApplicationInfo(application.getPackageName(), PackageManager.GET_META_DATA); result = applicationInfo.metaData.getString("test_application"); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return result; } public static String init(Activity activity) { String result = ""; try { ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo(activity.getComponentName(), PackageManager.GET_META_DATA); result = activityInfo.metaData.getString("test_activity"); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return result; }
(這個blog說 還可以在 service,receive 裡面使用(使用同上原理), 但是很遺憾我寫的時候報錯,有些無法引用, 所以就沒貼出來,有需要的就自行去研究下吧)
五、編譯變體版本(修改名稱,自定義編譯)
1. 修改apk生成的名稱,效果是 app_v版本名稱_日期_維度名稱_buildType(版本號).apk
android{ android.applicationVariants.all { variant -> variant.outputs.all { def fileName = "app_v${defaultConfig.versionName}_${releaseTime()}_${variant.flavorName}_${variant.buildType.name}(${variant.versionCode}).apk" outputFileName = fileName } } } static def releaseTime() { return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC")) }
2.前面因為 buildType 和 productFlavors 維度版本,這樣基本會有很多版本,
當我們執行 assemble 會預設遍歷所有的版本生成一遍,但是有時候有些版本是不需要的,這個時候就可以使用下面配置進行遮蔽了(下面配置是單維度的,多維度的判斷和這個有點區別)
android{ //執行assemble時 過濾不必要的版本 android.variantFilter { variant -> if ('release'.equals(variant.buildType.name)) { variant.getFlavors().each() { flavor -> //這裡需要注意下, 多維度 和此處不一樣 if ('Num1'.equals(flavor.name)) { variant.setIgnore(true) } } } } }
六、多模組的動態引數配置
當主模組app配置了引數,而有其他moudle也配置了相同引數, 一兩個模組還好,還可以手動修改
但是當模組以多,那麼每次有變動需要新增/修改,就會擔心會不會又哪裡漏掉
所以 以下操作是針對多module同一配置引數的,包括不限於 自定義引數,defaultConfig(),引用第三方的包,各種引數設定(由於用途較多,請自行開腦洞)
1. 在根路徑下面, 新建config.gradle,(和整個專案的build.gradle同一級目錄)
檔案下面 加了些參考配置,如果有需要保持各個moudle的版本引數都保持一致,或者 引進jar包 都保持一致的話,自行選擇性新增。
//使用 需要在build.gradle 頭部新增 引用 : apply from: "config.gradle" ext { /*------------------------- 簽名配置--------------------------------------*/ signingConfigs = [ "filePath" : "aaa.keystore", "keyAlias" : "bbb", "keyPassword" : "ccc", "storePassword": "dddd" ] /*------------------------- 簽名配置--------------------------------------*/ /*------------------------- 後臺地址配置--------------------------------------*/ url = [ "API_HOST_DEBUG" : "\"https://*.*.*.*/\"", "API_HOST_RELEASE": "\"https://*.*.*.*/\"" ] url2 = [ "API_HOST_DEBUG" : "\"https://*.*.*.*/\"", "API_HOST_RELEASE": "\"https://*.*.*.*/\"" ] /*------------------------- 後臺地址配置--------------------------------------*/ /*------------------------- 測試後臺配置--------------------------------------*/ test = [ "API_HOST_DEBUG" : "\"http://10.20.11.204:30160/\"", "API_HOST_RELEASE": "\"http://10.20.11.204:30160/\"", "codepath" : "Num1"//程式碼路徑參考Num1的, 因為這裡Num1的 路徑是都放在"Num1"下面 ] /*------------------------- 測試後臺配置--------------------------------------*/ /*------------------------- 參考配置--------------------------------------*/ /* android = [ compileSdkVersion: 23, buildToolsVersion: "23.0.3", minSdkVersion : 15, targetSdkVersion : 22, versionCode : 1, versionName : "1.0" ] dependencies = [ "gson" : "com.google.code.gson:gson:2.6.2", "eventbus" : 'org.greenrobot:eventbus:3.0.0', "butterknife" : 'com.jakewharton:butterknife:7.0.1', "support-design" : 'com.android.support:design:24.1.1', "support-appcompatV7": 'com.android.support:appcompat-v7:24.1.1', "support-percent" : 'com.android.support:percent:24.1.1', "support-multidex" : 'com.android.support:multidex:1.0.1', "glide" : 'com.github.bumptech.glide:glide:3.7.0', "support-v4" : 'com.android.support:support-v4:24.1.1', "okhttp3" : 'com.squareup.okhttp3:okhttp:3.3.1', "nineoldandroids" : 'com.nineoldandroids:library:2.4.0' ]*/ }
2. 專案根目錄下的build.gradle新增一句引用
//新增的 apply from: "config.gradle" buildscript { …… } allprojects { …… }
3. 現在所有moudle模組目錄下的 build.gradle 就可以使用了
請自行感受 rootProject.ext.url["API_HOST_DEBUG"], 這句 和 config.gradle中定義 的關係。
android { signingConfigs { main { File key = new File(rootProject.ext.signingConfigs["filePath"]) keyAlias rootProject.ext.signingConfigs["keyAlias"] keyPassword rootProject.ext.signingConfigs["keyPassword"] storeFile file(key) storePassword rootProject.ext.signingConfigs["storePassword"] } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.main buildConfigField("String", "url_aaa", rootProject.ext.url["API_HOST_DEBUG"]) } } } dependencies { implementation rootProject.ext.lib["gson"] }
七、個人小結
1. 前面說到 引數配置 均可以在 defaultConfig 、buildtype 、productFlavors 三處定義,
再仔細看 這三者其實是同一層級的,如下。 那麼當他們 均 定義一個引數時, 究竟哪個生效???
android{ defaultConfig{ } buildtype{ } productFlavors{ } }
結果: 後者覆蓋前面的,即現在這個順序的話是,相同定義最後生效的是:productFlavors{} 定義的值
2.在gradle中自定義一個引數 比如 int a=0, 然後依次在不同的地方進行 賦不同的值,最後的結果是什麼?
結果:永遠是 最後一次賦值,並不會因為編譯的版本不同而不同。
八、疑問
問題一:
背景:有客戶A、客戶B;
客戶A有 正式後臺 (URL1)和測試後臺(URL2),且地址不一樣
客戶B後臺地址(URL3)
問題:應該怎麼動態配置? (先別急著下定論說很容易)
分析一:最優雅的情況是,app_A_release_URL1.apk 對應 URL1 正式 後臺; app_A_debug_URL2.apk 對應 URL2測試後臺
肯定第一時間想到的是下面的這個, 但是很遺憾下面的模式無法設定B客戶的 後臺地址 URL3,
android{ buildType{ release{ url = URL1 } debug{ url = URL2 } } productFlavors { A { 客戶A } B { 客戶B } } }
分析二:productFlavors() 使用兩個維度的配置,然而效果還是和 分析一 一樣,無效
分析三:如果額外加個客戶A-Test , 在4個版本中只選擇2個有用的版本。那麼勢必導致,測試版本和正式版本,無法相互安裝/替換(對於交付第三方安裝到系統/測試,均有不小的阻力)
分析四:在buildType()和productFlavors() 中加入變數判斷編譯版本, 然而很遺憾我沒有找到相關變數,自己定義也失敗(參見 七.第2小點)
主要矛盾:在普通狀況下release和debug的版本必定是 相同的配置, release和debug是相對於程式碼區分,即一個除錯開發版本,一個釋出版本。
而如今,我想用配置一的除錯版本,來和配置二的開發版本 做驗證。
臨時解決方法: (都能解決,但都不完美 不優雅)
1. 如分析三,新建一個客戶,新增配置,
缺點:生成apk的版本不一致,無法互相替換。如果是系統預置的應用,更是麻煩
2. 在程式碼裡進行判斷,如果是僅僅一個URL地址不同,那麼可以考慮
缺點:其他的客戶B也必須配置Debug引數和Release引數,哪怕B客戶只有一個引數;
當客戶A的配置引數不僅僅只有一個URL不一樣的時候, 有十幾個引數的話,那這個也很麻煩。
九、擴充套件 (先挖個坑在這吧,埋不埋的以後再說了)
---------------------------------------------------------------------------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~ 終於寫好了啊 ~ ~~
早就想寫,也早就知道 很麻煩, 但沒想到 一寫就是整整1天半的時間。。。
感謝看完! 或 拉到了底部!
---------------------------------------------------------------------------------------------------------------------------------------------------------------