因為我們發(fā)布或者推廣的渠道不同,就造成了我們的Android App可能會有很多個,因為我們需要細分他們统刮,才能針對不同的渠道做不同的處理,比如統計跟蹤账千、是否升級侥蒙、App名字是否一致等等。尤其在國內這個各種應用市場百家爭鳴的時代匀奏,我們需要發(fā)布的App渠道甚至多個好幾百個鞭衩,而且各有各的特殊處理,所以這就更需要我們有一套自動的滿足多渠道構建的工具來幫我們解決這個問題,有了Android Gradle的Flavor后论衍,我們就可以完美的解決以上問題瑞佩,并且可以實現批量自動化。這一章主要介紹多渠道構建的基本原理坯台,然后使用Flurry和友盟這兩個最常用的分析統計平臺作為例子來演示多渠道構建炬丸,接著我們介紹下Flavor的每個配置的用法,讓大家可以根據需求定制自己的每個渠道蜒蕾,最后我們會介紹一種快速打包上百個渠道的方法稠炬,以提高多渠道構建的效率。
11.1 多渠道構建的基本原理
在Android Gradle中咪啡,定義了一個叫Build Variant的概念首启,直譯是構建變體,我喜歡叫它為構件-構建的產物(Apk),一個Build Variant=Build Type+Product Flavor撤摸,Build Type就是我們構建的類型毅桃,比如release和debug,Product Flavor就是我們構建的渠道准夷,比如baidu钥飞,google等等,他們加起來就是baiduRelease衫嵌,baiduDebug读宙,googleRelease,googleDebug渐扮,共有這幾種組合的構件產出,Product Flavor也就是我們多渠道構建的基礎掖棉,下面我們看看如何新增一個Product Flavor墓律。
Android Gradle為我們提供了productFlavors方法來添加不同的渠道,它接受域對象類型的ProductFlavor閉包作為其參數幔亥,前面章節(jié)我們在介紹Build Type的時候也介紹過域對象耻讽,所以我們可以為productFlavors{}閉包添加很多的渠道,每一個都是一個ProductFlavor類型的渠道帕棉,在NamedDomainObjectContainer中的名字就是渠道名针肥,比如baidu,google等香伴。
以上的渠道配置之后慰枕,Android Gradle就會生成很多Task,基本上都是基于Build Type+Product Flavor的方式生成的即纲,比如assembleBaidu具帮,assembleRelease,assembleBaiduRelease等等,assemble開頭的負責生成構件產物(Apk)蜂厅,比如assembleBaidu運行之后會生成baidu渠道的release和debug包匪凡;assembleRelease運行后會生成所有渠道的release包;而assembleBaiduRelease運行后只會生成baidu的release包掘猿。除了assemble系列的病游,還有compile系列的、install系列的等等稠通,大家可以通過運行./gradlew tasks來查看有哪些任務衬衬。除了生成的Task之外,每個ProductFlavor還可以有自己的SourceSet采记,還可以有自己的Dependencies依賴佣耐,這意味著什么,意味著我們可以為每個渠道定義他們自己的資源唧龄、代碼以及依賴的第三方庫兼砖,這為我們自定義每個渠道提供很大的便利和靈活性,后面的11.3小節(jié)的多渠道定制我們會詳細介紹這部分內容既棺。
11.2 Flurry多渠道和友盟多渠道構建
Flurry和友盟是我們常用的兩個App統計分析工具讽挟,基本上所有的軟件都會接入其中的一個。Flurry本身沒有渠道的概念丸冕,它有Application耽梅,所以可以把一個Application當成一個渠道,這樣就可以使用Flurry統計每個渠道的活躍新增等情況胖烛;友盟本身有渠道的概念眼姐,只要我們在AndroidManifest.xml配置標注即可,下面以這兩種統計演示下多渠道的用法佩番。
Flurry的統計是以Application劃分渠道的众旗,每個Application都有一個Key,我們稱之為Flurry Key趟畏,這個當我們在Flurry上創(chuàng)建Application的時候就自動幫我們生成好了贡歧,現在我們要做的就是為每個渠道配置不同的Flurry Key,還記得我們在第九章講的自動已BuildConfig嗎赋秀,利用的就是這個功能利朵。
這樣每個渠道的BuildConfig類中都有會有名字為FLURRY_KEY常量定義,它的值是我們在渠道中使用buildConfigField指定的值猎莲,每個渠道都不一樣绍弟,這樣我們只需要在代碼中使用這個常量即可,這樣每個渠道的統計分析就可以做到了著洼。
`Fluury.init(this, FLUURY_KEY);
友盟的話晌柬,本身是有渠道的概念姥份,不過它不是在代碼中指定的,而是在AndroidManifest.xml中配置的年碘,通過配置meta-data標簽來設置澈歉。`
<mete-data android:value="Channel
ID" android:name="UMENG_CHANNEL">
示例中的Channel ID就是我們的渠道值,比如baidu屿衅,google等埃难。如果我們就動態(tài)的改變的,就需要用到我們在9.5小結講到的manifestPlaceholders涤久,這一小節(jié)就是以友盟的多渠道為例進行講解的涡尘,大家可以再回過頭來看一下,這里不在進行詳細講了响迂。
現在通過這兩個例子我們可以發(fā)現考抄,我們所做的其實就是對每個渠道,根據不同的業(yè)務進行不同的定制蔗彤,這里是兩個統計分析川梅,以后可能還有其他監(jiān)控、推送等業(yè)務然遏,在定制的過程中我們用到了Android Gradle提供的不同的配置以及功能贫途,最終來達到我們的目的,所以下一節(jié)我們就詳細的講下對渠道(ProductFlavor)的定制待侵,然后大家根據這些Android Gradle提供的對渠道定制的功能丢早,來實現自己不同渠道的業(yè)務需求。
11.3 多渠道構建定制
多渠道的定制秧倾,其實就是對Android Gradle插件的ProductFlavor的配置怨酝,通過配置ProductFlavor達到我們靈活細化的控制每一個渠道的目的。
Flavor這個單詞比較有意思那先,看字面意思是氣味农猬、味道的意思,所以ProductFlavor也就是產品的氣味或者味道胃榕,多種不同的產品味道盛险,就是我們所說的多渠道了瞄摊。多渠道這個國內特色勋又,特別多的渠道,如果你的App不出海的話换帜,所以針對國內這些渠道就好了楔壤,如果要出海,要再加上出海的國外的渠道惯驼。
11.3.1 applicationId
它是ProductFlavor的屬性蹲嚣,用于設置該渠道的包名递瑰,如果你的App想為該渠道設置特別的包名,可以使用applicationId這個屬性進行設置隙畜。
如上示例抖部,就可以為google這個渠道設置其特有的包名,這樣打包的時候google渠道使用的是org.flysnow.app.example112.google這個包名议惰,而其他渠道使用的是org.flysnow.app.example112這個包名慎颗。看下它的方法原型實現
很明顯可以看到是setter方法言询,接受一個字符串作為參數俯萎,作為該渠道的新包名。
11.3.2 consumerProguardFiles
即使一個屬性运杭,也有一個同名的方法夫啊,它只對Android庫項目有用,當我們發(fā)布庫項目生成一個AAR包的時候辆憔,使用consumerProguardFiles配置的混淆文件列表也會被打包到AAR里一起發(fā)布撇眯,這樣當應用項目引用這個AAR包、并且啟用混淆的時候躁愿,會自動的使用AAR包里的混淆文件對AAR包里的代碼進行混淆叛本,這樣我們就不用對該AAR包進行混淆配置了,因為它自帶了彤钟。
和我們前面講的配置混淆是一樣的方式来候,可以指定多個,使用逗號分開逸雹。
從源代碼中也可可以看出有兩種設置方式营搅,一種是我們剛剛演示的方法,另外一種是屬性設置梆砸,他們兩個的區(qū)別在于:consumerProguardFiles方法是一直添加的转质,不會清空以前的混淆文件,而consumerProguardFiles屬性配置的方式是每次都是新的混淆文件列表帖世,以前配置的會先被清空休蟹。
11.3.3 manifestPlaceholders
這個屬性已經在9.5節(jié)介紹過,這里不像細講日矫,大家可以再翻看9.5小節(jié)熟悉一下赂弓。
11.3.4 multiDexEnabled
這個屬性用來啟用多個dex的配置,主要用來突破65535方法的問題哪轿,大家可以參考9.11一節(jié)的介紹盈魁,這里不再詳細表述。
11.3.5 proguardFiles
混淆使用的文件配置窃诉,可以參考8.3一節(jié)里關于混淆的講解杨耙,這里不再詳述赤套。
11.3.6 signingConfig
簽名配置,請參考8.2配置簽名信息一節(jié)珊膜,這里不再詳述容握。
11.3.7 testApplicationId
我們一般都會對Android進行單元測試,這個單元測試有自己的專門的Apk測試包车柠,testApplicationId是用來適配測試包的包名的唯沮,它的使用方法和我們前面介紹的applicationId一樣。
一般的testApplicationId的值為App的包名+.test堪遂,當然大家也可以設置其他的介蛉。
它是一個屬性,自然也是有setter方法的溶褪,源代碼可以看到币旧,接受一個String類型的值作為參數。
11.3.8 testFunctionalTest和testHandleProfiling
也是和單元測試有關猿妈,Boolean型屬性吹菱,testFunctionalTest表示是否是功能測試,testHandleProfiling表示是否啟用分析功能彭则。
Boolean型鳍刷,true和false兩個選擇,示例表示作為功能測試并且啟用了分析功能俯抖。
以上是這兩個屬性的源代碼配置输瓜,他們主要用來控制測試包生成的AndroidManifest.xml,因為他們最終的配置還要體現在AndroidManifest.xml文件中的instrumentation標簽的配置上》移迹可以參考
http://developer.android.com/intl/zh-cn/guide/topics/manifest/instrumentation-element.html
11.3.9 testInstrumentationRunner
用來配置運行測試使用的Instrumentation Runner的類名尤揣,是一個全路徑的類名,而且必須是android.app.Instrumentation的子類柬祠,一般情況下北戏,我們使用android.test.InstrumentationTestRunner,當然也可以自定義漫蛔,根據自己的需求嗜愈。
和其他的屬性的配置一樣直接配置即可,接受一個字符串類型的參數莽龟,值為android.app.Instrumentation子類的全限定路徑的類名蠕嫁。
11.3.10 testInstrumentationRunnerArguments
這個是配合著上一個屬性用的,它用來配置Instrumentation Runner使用的參數轧房,其實他們最終使用的都是adb shell am instrument這個命令拌阴,參數就是我們使用-e標記指定的那些绍绘,所以這里使用testInstrumentationRunnerArguments參數都會被轉換傳遞給am instrument這個命令使用奶镶,也就是轉為-e key value -e key value這種命令行的方式使用迟赃。
我們可以使用示例中的方法指定很多個參數,從使用上我們也可以看出厂镇,它是一個Map<String,String>,和我們前面講的manifestPlaceholders很相似纤壁。其他的一些參數配置可以參考
http://developer.android.com/intl/zh-cn/tools/testing/testing_otheride.html
11.3.11 versionCode和versionName
配置渠道的版本號和版本名稱,詳情參考8.1.4和8.1.5兩個小節(jié)捺信。
11.3.12 useJack
Boolean類型的屬性酌媒,用于標記是否啟用Jack和Jill這個全新的、高性能的編譯器迄靠。目前我們使用的是常規(guī)的成熟的Android編譯框架秒咨,這個有個問題,就是太慢掌挚,所以Google他們又搞了一個全新的雨席、高性能的編譯器,這個就是Jack和Jill吠式,目的就是簡化編譯的流程陡厘,提高編譯的速度和性能,不過目前他們還處于實驗階段特占,有寫特性還不支持糙置,比如注解,比如JDK8的特性等等是目,大家可以自己測試用用谤饭,但是正式產品種還是不要使用。要啟用Jack編譯非常簡單懊纳,只需要設置useJack為true即可网持,默認是false。
這樣即可啟用长踊,如上示例其實調用的是useJack這個方法功舀,我們看下它的源代碼
如果你想使用屬性setter的方式,可以直接用=賦值身弊。
他們的結果是一樣的辟汰,但是一般我們都是使用方法,這樣看著比較優(yōu)雅阱佛。關于Jack和Jill這種編譯方式有興趣的話帖汞,可以參考
http://tools.android.com/tech-docs/jackandjill。
11.3.13 dimension
有時候凑术,我們想基于不同的標準來構建我們的App翩蘸,比如免費版還是收費版、x86版還是arm版等等淮逊,在不考慮BuildType的情況下催首,這里有4種組合:x86的免費版扶踊、x86的收費版、arm的免費版郎任、arm的收費版秧耗。對于這種情況,我們有兩種方式來構建舶治,第一種是通俗的用法分井,就是配置4個ProductFlavor,他們分別是x86free霉猛、x86paid尺锚、armfree、armpaid惜浅,然后針對這4個ProductFlavor配置缩麸,滿足我們的需求即可。這種方式比較通俗易懂赡矢,但是有個問題杭朱,就是配置腳本的冗余,比如free的配置是有共性的吹散,但是我們要在兩個free里把共性的配置寫兩遍弧械,同樣x86這類也是,腳本冗余了空民,而且每次改動都要一個個去修改刃唐,也很麻煩,而且現在才有兩個維度界轩,每個維度的可選項都不會有很多画饥,我們還可以忍受,如果有很多種維度呢浊猾?每個維度又有很多可選項呢抖甘?下面我們來介紹第二種方法,通過dimension多維度的方式來解決這個問題葫慎。
dimension是ProductFlavor的一個屬性衔彻,接受一個字符串,作為該ProductFlavor的維度偷办,其實我們可以簡單的理解為對ProductFlavor進行分組艰额,比如free和paid可以認為他們都是屬于版本(version),而x86和arm是屬于架構(abi),這樣就把他們分成了兩組椒涯,而dimension接受的參數就是我們分組的組名柄沮,也是維度名稱。維度名稱不是隨便指定的,我們在使用他們之前祖搓,必須要先聲明狱意,這和我們Java的變量差不多,要先定義好才能使用棕硫,那么怎么定義的,這個就是使用android對象的flavorDimensions方法聲明的袒啼。
flavorDimensions是我們使用的android{}里的方法哈扮,它和productFlavors{}是平級的,一定要先使用flavorDimensions聲明維度蚓再,我們才能在ProductFlavor使用滑肉。
該方法可以接受一個可變的字符串類型的參數,所以我們可以 同時指定多個維度摘仅,但是一定要記住靶庙,這些維度是有順序的,是有優(yōu)先級的娃属,第一個參數的優(yōu)先級最大六荒,其次是第二個,以此類推矾端,所以聲明之前一定要根據自己的需求指定好順序掏击。
如上例子所示褂微,最后生成的variant(構建產物)會被如下幾個ProductFlavor對象配置细诸。
- android里的defaultConfig配置,我們前面講過扣猫,它也是一個ProductFlavor殴玛。
- abi維度的ProductFlavor捅膘,被dimension配置標記為abi的ProductFlavor
- version維度的ProductFlavor,被dimension配置標記為version的ProductFlavor
維度的 優(yōu)先級 非常重要滚粟,因為高優(yōu)先級的flavor會替換掉低優(yōu)先級的資源寻仗、代碼、配置等凡壤,在例子中愧沟,優(yōu)先級為abi>version>defaultConfig,因為abi的順序在version之前鲤遥。
聲明了維度沐寺,我們就可以在ProductFlavor使用他們了。
通過dimension指定ProductFlavor所屬的維度盖奈,非常方便混坞,剩下的事情交給Android Gradle即可,它會幫我們生成相應的Task、SourceSet究孕、Dependencies等啥酱。以前我們講一個構建產物(variant)=BuildType+ProductFlavor,現在ProductFlavor這個維度又被我們通過dimension細化分組厨诸,所以就多了一些維度镶殷,比如示例中的abi和version,現在構建產物(variant)=BuildType+Abi+Version了微酬,所以會生成如下的variant:
- ArmFreeDebug
- ArmFreeRelease
- ArmPaidDebug
- ArmPaidRelease
- X86FreeDebug
- X86FreeRelease
- X86PaidDebug
- X86PaidRelease
這種我們只用根據維度去分組绘趋、去配置,剩下的讓Android Gradle幫我們組合可能結果的variant颗管,實現了共性配置--也就是模塊化編程陷遮,維護起來也很方便。
這一節(jié)節(jié)本上介紹了所有的ProductFlavor的配置垦江,很多因為以前介紹過帽馋,所以這里就略過了,特意用一個標題來說說明是想讓大家溫故知新一下比吭,除了上面列出的绽族,還有一些方法配置,比如resConfig衩藤、resValue项秉、targetSdkVersion、maxSdkVersion慷彤、minSdkVersion等娄蔼,這些我們前面的章節(jié)都有講過,這里就比一一介紹了底哗,他們主要集中在第八章岁诉、和第九章,大家可以翻翻熟悉一下跋选。
11.4 提高多渠道構建的效率
我們生成多個渠道包涕癣,主要是因為我們想跟蹤每個每個渠道的情況,比如新增前标、活躍坠韩、留存等等,跟蹤的工具一般是Flurry和友盟炼列,所以除了根據渠道號來區(qū)分每個渠道外只搁,大部門情況下,每個渠道并沒有什么不同俭尖,他們唯一的區(qū)別是屬于哪個渠道的氢惋。
對于這種情況洞翩,如果Android Gradle打包,幾百個的情況下會非常慢焰望,因為它對每個渠道包都要執(zhí)行那些構建的過程骚亿,但是我們的每個渠道包只是因為渠道號不同而已,為什么要進行一個完成的構建呢熊赖,為此了打包效率来屠,美團研究出了另外一個辦法,利用了在Apk的META-INF目錄下添加空文件不用重新簽名的原理震鹉,非常高效俱笛,其大概就是:
- 利用Android Gradle打一個基本包(母包)
- 然后基于該包拷貝一個,文件名要能區(qū)分出來產品足陨、打包時間嫂粟、版本娇未、渠道等墨缘。
- 然后對拷貝出來的Apk文件進行修改,在其META-INF目錄下新增空文件零抬,但是空文件的文件名要有意義镊讼,必須包含能區(qū)分渠道的名字比如mtchannel_google。
- 重復2平夜、3生成我們所需的所有的渠道包Apk蝶棋,這個可以使用Python這類腳本來做
- 這樣就生成了我們所有發(fā)布渠道的Apk包了。
那么我們怎么使用呢忽妒,原理也非常簡單玩裙,我們在Apk啟動的時候(Application onCreate)的時候,讀取我們寫Apk中META-INF目錄下的前綴為mtchannel_文件,如果找到的話段直,把文件名取出來吃溅,然后就可以拿到渠道標識(google)了,這里貼一個美團實現的代碼,大家可以參考一下:
public static String getChannel(Context context) {
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
String ret = "";
ZipFile zipfile = null;
try {
zipfile = new ZipFile(sourceDir);
Enumeration<?> entries = zipfile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
String entryName = entry.getName();
if (entryName.startsWith("mtchannel")) {
ret = entryName;
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String[] split = ret.split("_");
if (split != null && split.length >= 2) {
return ret.substring(split[0].length() + 1);
} else {
return "";
}
}
以上代碼邏輯我們可以再優(yōu)化一下鸯檬,比如為渠道做個緩存放在SharePreference里决侈,不能總從Apk里讀取吧,效率是個問題喧务。
對于Python批處理也很簡單赖歌,這里給出一段美團的Python代碼,大家參考補充
import zipfile
zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED)
empty_channel_file = "META-INF/mtchannel_{channel}".format(channel=your_channel)
zipped.write(your_empty_file, empty_channel_file)
添加完空渠道文件后的目錄功茴,META-INFO目錄多了一個名為mtchannel_meituan的空文件:
以上是核心實現庐冯,我們要做的就是保存一個渠道列表,可以用一個文本文件保存坎穿,一行一個渠道肄扎,然后使用Python讀取,for循環(huán)生成不同渠道的Apk包,這個我就不寫代碼了犯祠,大家可以自己試一下旭等,這里給出一個開元的美團方案實現,大家可以參考一下衡载, https://github.com/GavinCT/AndroidMultiChannelBuildTool 搔耕。美團方案地址:
http://tech.meituan.com/mt-apk-packaging.html
11.5 小結
到這里多渠道構建這一章就結束了,多渠道構建利用的主要是對ProductFlavor的定制痰娱,所以我們重點講了ProductFlavor的各個配置弃榨,讓大家都熟悉一下,這樣在碰到多渠道的需求時梨睁,可以對比參考一下能否滿足你的要求鲸睛,或者需要哪些組合可以做到。
此外我們要記得productFlavors是一個ProductFlavor集合坡贺,我們可以通過操縱它做很多批量處理的事情官辈,比如9.5小節(jié)中的批量修改AndroidManifest.xml中友盟統計的渠道名等等,這個批處理的功能要合理的利用遍坟。
本文屬自學歷程, 僅供參考
詳情請支持原書 Android Gradle權威指南