變種的使用環(huán)境
在開(kāi)發(fā)過(guò)程中我們可能會(huì)遇到這樣一種情況:
在現(xiàn)有的APP的基礎(chǔ)上更換部分UI或是增刪某個(gè)功能模塊坐漏,而大體上兩個(gè)apk有很多共通的地方薄疚,面對(duì)這種情況的解決方法無(wú)非以下幾種:
1.將現(xiàn)有代碼復(fù)制粘貼一份碧信,在另一個(gè)工程中進(jìn)行修改
2.使用代碼托管工具的分支功能
3.使用全局靜態(tài)變量控制某個(gè)功能開(kāi)關(guān)
以上方法在新打包的apk較少的情況下使用是沒(méi)什么問(wèn)題的,但當(dāng)需要打包的apk過(guò)多時(shí)街夭,以上方法在后續(xù)開(kāi)發(fā)時(shí)就會(huì)變得異常麻煩砰碴,所以面對(duì)這種情況板丽,變種便于管理的優(yōu)勢(shì)就體現(xiàn)得淋漓盡致
正式使用
gradle配置
首先需要在app所在的module的gradle中配置flavorDimensions(風(fēng)味維度)埃碱,使變種的flavors保持在同一維度中
flavorDimensions的值可以自由定義
然后需要在android下建立productFlavors,然后創(chuàng)建一個(gè)自定義名稱的子項(xiàng)
同時(shí)砚殿,在src下建立與main同級(jí)的文件夾啃憎,文件夾名稱需與在productFlavors中的子項(xiàng)名稱保持一致
配置完成后點(diǎn)擊sync更新配置,使用變種的第一步就算完成了
此時(shí)我們可以點(diǎn)擊Android Studio的左下角的Build Variants標(biāo)簽頁(yè)展開(kāi)變種目錄辛萍,選擇對(duì)應(yīng)的變種耳幢,運(yùn)行時(shí)就會(huì)編譯對(duì)應(yīng)的變種中的文件
當(dāng)然,僅僅是這樣處理的話,我們打包出來(lái)的apk會(huì)互相覆蓋掉,這是因?yàn)榫幾g出來(lái)的apk包名一致導(dǎo)致的。
我們可以在productFlavors下對(duì)變種添加配置掠河,分別設(shè)置applicationId奉瘤,這樣肌访,編譯出來(lái)的apk就可以在手機(jī)上共存蟹演。
productFlavors{
variant1{
applicationId "com.sariki.variants1"
}
variant2{
applicationId "com.sariki.variants2"
}
}
功能模塊配置
變種搭建完成后,那么就到了代碼環(huán)節(jié)羞反,需要注意的有兩點(diǎn):
1.變種中的文件繼承main目錄中的文件夾結(jié)構(gòu)布朦,所以新建變種目錄時(shí)需要將包結(jié)構(gòu)與main的包結(jié)構(gòu)保持一致
2.變種與main中不能存在同級(jí)同名的文件,應(yīng)在main中刪除該文件后在變種目錄中創(chuàng)建
(例如:main目錄下activityA中需要跳轉(zhuǎn)到activityB昼窗,但這個(gè)activityB根據(jù)變種有不同的功能是趴,這時(shí)就需要在不同變種下創(chuàng)建該activityB并將main目錄下對(duì)應(yīng)的activityB文件刪除)
既然打包多份app,我們?cè)谝恍┙缑婵赡芫蜁?huì)根據(jù)變種來(lái)對(duì)某些組件進(jìn)行顯示隱藏等操作澄惊,如果大體上與本體保持一致且沒(méi)有新增一些功能唆途,我們可以不采取刪除main目錄下文件后在變種中創(chuàng)建的方式來(lái)處理,可以借助BuildConfig類來(lái)進(jìn)行判斷掸驱。
首先我們到gradle下的productFlavors中對(duì)對(duì)應(yīng)的變種配置buildConfigField肛搬,
productFlavors{
variant1{
applicationId "com.sariki.variants1"
buildConfigField("boolean", "SHOW_TOAST", "true")
}
variant2{
applicationId "com.sariki.variants2"
buildConfigField("boolean", "SHOW_TOAST", "false")
}
}
代碼中可以使用該變量來(lái)進(jìn)行判斷處理
buildConfigField是在編譯時(shí)就會(huì)根據(jù)你的參數(shù)創(chuàng)建一個(gè)靜態(tài)成員變量,因此毕贼,我們也可以借由這個(gè)來(lái)進(jìn)行網(wǎng)絡(luò)端口配置來(lái)適應(yīng)變種對(duì)應(yīng)的服務(wù)器
productFlavors{
variant1{
applicationId "com.sariki.variants1"
buildConfigField("boolean", "SHOW_TOAST", "true")
buildConfigField("String", "SERVER_HOST", "\"http://111.111.11.1/\"")
}
variant2{
applicationId "com.sariki.variants2"
buildConfigField("String", "SHOW_TOAST", "true")
buildConfigField("String", "SERVER_HOST", "\"http://222.222.22.2/\"")
}
}
當(dāng)需要操作的變量過(guò)多時(shí)温赔,使用buildConfigField會(huì)使gradle文件變得過(guò)于冗雜,這個(gè)時(shí)候我們可以新建一個(gè)java文件作為一個(gè)載體帅刀,在其中創(chuàng)建靜態(tài)成員變量替代BuildConfig
(該文件也需要在不同變種文件夾的相同層級(jí)目錄中分別創(chuàng)建)
資源文件配置
與java文件相同让腹,資源文件如果根據(jù)變種有變更远剩,也需要在對(duì)應(yīng)的文件目錄中創(chuàng)建相同的文件,但資源文件與java文件有一個(gè)不同點(diǎn):變種同層級(jí)同名資源文件可與main目錄下的同層級(jí)同名資源文件共存骇窍,編譯時(shí)會(huì)選擇變種目錄下的文件瓜晤。
舉個(gè)栗子:
我們?cè)赼pplication中設(shè)置icon,以background_test文件為例腹纳,我們分別在main和變種的資源文件目錄下創(chuàng)建該文件
[圖片上傳失敗...(image-dd08fd-1606287792466)]
[圖片上傳失敗...(image-cfde7e-1606287792466)]
main目錄下的background_test文件為藍(lán)色痢掠;
variant1目錄下的background_test為灰色;
variant2目錄下的background_test為黑色嘲恍;
而最后編譯出來(lái)的apk顯示的為變種對(duì)應(yīng)background_test的顏色足画。
因此,在其他需要根據(jù)變種修改圖片資源文件的地方都可以按這種方式來(lái)修改佃牛。
[圖片上傳失敗...(image-dd8efd-1606287792466)]
說(shuō)完圖片文件淹辞,我們回到剛才所說(shuō)的編譯時(shí)會(huì)選擇變種文件目錄下的文件這一點(diǎn)。
在values文件夾下我們一般會(huì)使用strings.xml俘侠,colors.xml等文件來(lái)定義配置象缀,通常這些xml文件里面會(huì)有大量的item配置,如果根據(jù)上述圖片的操作去將xml文件完完整整的復(fù)制一份到變種目錄下就會(huì)造成資源浪費(fèi)以及擴(kuò)大工程占用空間爷速,但如果不復(fù)制這份xml文件又該怎么去修改文件里面的部分配置呢央星?
其實(shí)我們可以依靠xml配置的覆蓋機(jī)制來(lái)解決這個(gè)問(wèn)題。
舉個(gè)栗子:
我們?cè)趍ain目錄下的strings.xml中新建一行名為"test_txt"的string惫东,
編譯后需要在variant1中顯示為“變種1”莉给,variant2中顯示為“變種2”。
那么廉沮,我們可以直接在對(duì)應(yīng)變種目錄中新建一個(gè)xml文件(命名無(wú)強(qiáng)制規(guī)定)颓遏,然后在其中創(chuàng)建一個(gè)同樣名為"test_txt"的string并賦值。
這樣滞时,編譯后變種中xml的string會(huì)覆蓋掉main目錄下strings.xml中同名string的值州泊。
main目錄下strings.xml配置
variant1目錄下variant_stings.xml配置
variant2目錄下variant_stings.xml配置
測(cè)試界面的xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:textSize="30sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="@string/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:textSize="30sp"
android:text="@string/test_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
我們給textview設(shè)置內(nèi)容后可以編譯看一下效果
variants1:
variant2:
這樣,我們就可以達(dá)到只修改少量配置來(lái)更改文本或顏色的目的
密鑰配置
我們?cè)陂_(kāi)發(fā)過(guò)程中漂洋,可能會(huì)用到第三方的sdk來(lái)支持我們做一些功能,而這些sdk都會(huì)使用獨(dú)特的密鑰來(lái)獲得使用權(quán)限力喷,我們?cè)跇?gòu)建變種時(shí)這些sdk也需要用到刽漂,那么我們?cè)撊绾螌?duì)這些密鑰進(jìn)行配置呢?
現(xiàn)在大部分的sdk密鑰會(huì)需要在AndroidManifest中進(jìn)行配置弟孟,而其實(shí)在AndroidManifest中贝咙,我們可以直接引用gradle中的配置。
以網(wǎng)易云信密鑰配置為例:
<meta-data
android:name="com.netease.nim.appKey"
android:value="" />
我們需要在value一欄中填寫(xiě)從網(wǎng)易云信獲得的密鑰拂募,這時(shí)庭猩,我們可以使用引用的方式來(lái)進(jìn)行配置窟她。
首先我們到gradle下的productFlavors中,配置對(duì)應(yīng)變種的manifestPlaceholders參數(shù)
productFlavors{
variant1{
applicationId "com.sariki.variants1"
manifestPlaceholders = [valueName : "xxxxxxxxxx"]
}
variant2{
applicationId "com.sariki.variants2"
manifestPlaceholders = [valueName : "xxxxxxxxxxxx"]
}
}
格式為 [自定義的變量名稱 : "密鑰值"]
然后回到清單文件中蔼水,使用${xxx}的方式來(lái)引用指定的值
<meta-data
android:name="com.netease.nim.appKey"
android:value="${valueName}" />
同樣震糖,也可以在部分需要完整包名結(jié)構(gòu)的配置中,使用${applicationId}方式來(lái)引用包名趴腋,例如部分廠商的推送以及一些其他的sdk配置吊说,例
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
但也有部分無(wú)法直接使用引用的方式來(lái)使用包名的情況,如接入微信第三方登錄時(shí)用到的微信sdk优炬,需要我們手動(dòng)創(chuàng)建WXEntryActivity颁井,并在清單文件中聲明,
<activity android:name=".wxapi.WXEntryActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true"
android:launchMode="singleTask"
/>
但由于我們構(gòu)建變種時(shí)使用的包名不同蠢护,name字段索引不到WXEntryActivity的位置雅宾,直接配置的話會(huì)出現(xiàn)WXEntryActivity無(wú)法被調(diào)起的情況礁哄,這時(shí)我們就需要使用別名來(lái)進(jìn)行配置
<activity-alias
android:name="${applicationId}.wxapi.WXEntryActivity"
android:exported="true"
android:targetActivity=".wxapi.WXEntryActivity" />
添加這些配置就可以正常索引了
多module變種配置
在部分開(kāi)發(fā)環(huán)境中讽坏,我們可能會(huì)需要對(duì)多個(gè)module進(jìn)行變種配置赊颠,與上述配置流程沒(méi)有太大區(qū)別惧所,但是需要注意的有兩點(diǎn):
1.多個(gè)module的flavorDimensions需要保持一致
2.多個(gè)module的productFlavors需要保持一致羔挡,即module1中productFlavors中配置了多少個(gè)赶促,module2中同樣需要配置多少個(gè)
基于以上第二點(diǎn)妒蔚,建議不要在過(guò)多的module中進(jìn)行變種配置疮鲫,這樣會(huì)導(dǎo)致打一個(gè)新包需要配置的地方變得過(guò)多蘸劈,在配置環(huán)節(jié)變得繁瑣昏苏,有違初衷。
結(jié)尾
以上就是變種在構(gòu)建過(guò)程中會(huì)涉及的一些方法了威沫,但值得一提的是贤惯,變種有著馬甲包與渠道包區(qū)別,渠道包一般是為了統(tǒng)計(jì)上架不同應(yīng)用市場(chǎng)的流量而在APP中設(shè)置相關(guān)統(tǒng)計(jì)渠道棒掠,而馬甲包除了可以滿足渠道包的需求外孵构,可以針對(duì)不同的變種做出資源變更以及功能變更,更多使用于一份源碼根據(jù)需求定制化不同APP的情景烟很。