需求
一份源碼淹魄,修改API地址郁惜,包名,替換桌面圖標(biāo)和一些其他資源文件甲锡,生成不同的APK賣給不同的人兆蕉。
PS 篇幅略長,但是絕對(duì)全是干貨
解決方案
為解決這個(gè)問題缤沦,我經(jīng)歷了以下三個(gè)階段:
1. 需要幾份APK就copy幾份源碼進(jìn)行修改虎韵。
2. 將整個(gè)項(xiàng)目作為module來引用。
3. 使用Gradle的Product Flavors來構(gòu)建變種缸废。
第一種方案:需要幾份APK就copy幾份源碼進(jìn)行修改
這是早期做Android開發(fā)包蓝,仍然是EC橫行的時(shí)候,很痛苦企量,需要幾份就復(fù)制幾份测萎,開始簡單,但是后期維護(hù)
卻是特別的痛苦梁钾。原因是當(dāng)出現(xiàn)一個(gè)BUG的時(shí)候需要修改多次绳泉,雖然說修改完成一份,后面只需要復(fù)制黏貼姆泻,但是也是痛苦零酪,可以想象一下冒嫡,被繁瑣的事情纏繞。
第二種方案:將整個(gè)項(xiàng)目作為module來引用
這是我接觸到了AS四苇,當(dāng)時(shí)考慮的就是將整個(gè)項(xiàng)目改造成為module孝凌,創(chuàng)建不同的主項(xiàng)目來引用同一份源碼,最終改造到一半月腋,不可行蟀架,因?yàn)橐薷拇罅康拇a,比如:
1. ID不再是final類型的了,switch view.id 之類的方法不能再使用榆骚。
第三種方案:使用Gradle的Product Flavors來構(gòu)建變種
前面兩種方案簡直是讓我生不如死片拍,但是隨著時(shí)間的積累,慢慢的發(fā)現(xiàn)出現(xiàn)一種叫做多渠道打包方法妓肢,于是我按照這個(gè)思路找下去捌省,終于在Google的官方文檔中找到了解決方案:
有興趣的小伙伴可以直接去看,里面描述了如何去使用碉钠。
實(shí)踐
1.基礎(chǔ)配置
1.創(chuàng)建一個(gè)普通的空白項(xiàng)目
這一步就不截圖了纲缓,創(chuàng)建就可以了。
2.在manifest中進(jìn)行占位符配置
${APP_ICON} 占位啟動(dòng)圖標(biāo)
${APP_NAME} 占位app名稱
同理喊废,其他第三方需要的相關(guān)配置也是可以使用占位符祝高,例如極光相關(guān)和友盟相關(guān)
<permission
android:name="${JPUSH_MESSAGE_VALUE}"
android:protectionLevel="signature" />
<meta-data
android:name="UMENG_APPKEY"
android:value="${UMENG_APPKEY}" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL}" />
以上的占位符,都可以在gradle中使用其它值來替換污筷。
在Gradle中對(duì)占位符等進(jìn)行配置
- 對(duì)defaultConfig節(jié)點(diǎn)做一些修改工闺,增加manifestPlaceholders字段,來替換manifest中的占位符颓屑。為了快速演示斤寂,所以就沒配置極光和友盟了,都是一樣的揪惦。
defaultConfig {
applicationId "com.aohanyao.product.flavorsdemos"
minSdkVersion 18
targetSdkVersion 25
versionCode 1
versionName "1.0"
manifestPlaceholders = [APP_ICON: "@mipmap/ic_launcher",
APP_NAME: "這是默認(rèn)的配置"]
}
上面的配置將圖標(biāo)改為了ic_launcher,app的名稱改為了這是默認(rèn)的配置罗侯,為了保險(xiǎn)起見器腋,先直接運(yùn)行APP,看看有沒有更改成功钩杰。

從上圖可以看到纫塌,已經(jīng)直接運(yùn)行安裝成功了,可以證明我們替換占位符是正確的讲弄。
2.變種的配置措左,ProductFlavors配置
在android節(jié)點(diǎn)中配置productFlavors節(jié)點(diǎn),將defaultConfig節(jié)點(diǎn)復(fù)制一份過來避除,修改一下需要修改的地方怎披,具體如下配置:
productFlavors {
//變種1
flavorsdemo1 {
applicationId "com.aohanyao.product.flavorsdemos.demo1"http://修改了包名
minSdkVersion 18
targetSdkVersion 25
versionCode 1
versionName "1.0"
manifestPlaceholders = [APP_ICON: "@mipmap/ic_launcher1",
APP_NAME: "變種1"]
}
//變種2
flavorsdemo2 {
applicationId "com.aohanyao.product.flavorsdemos.demo2"http://修改了包名
minSdkVersion 18
targetSdkVersion 25
versionCode 1
versionName "1.0"
manifestPlaceholders = [APP_ICON: "@mipmap/ic_launcher2",
APP_NAME: "變種2"]
}
}
為了方便區(qū)分胸嘁,我增加了ic_launcher1和ic_launcher2兩個(gè)圖標(biāo)。
詳細(xì)配置截圖:
在上面的配置中凉逛,分別修改了APP的包名性宏,名稱以及圖標(biāo)。接下來就是驗(yàn)證是否成功了状飞。只要這這兩個(gè)都能安裝上就算是成功(同樣的包名是不能安裝在同一臺(tái)手機(jī)上的毫胜,當(dāng)然有種黑科技不算)。配置完成后同步一下诬辈。
3.選擇構(gòu)建變體
菜單->Build->Select Build Variant
這時(shí)候左下會(huì)出現(xiàn)一個(gè)窗口酵使,如果Select Build Variant無法選中的話,請(qǐng)先選擇app目錄焙糟。
可以看到口渔,在gradle中配置的flavorsdemo1和flavorsdemo2都出現(xiàn)在了了選項(xiàng)中,一個(gè)是debug版本酬荞,一個(gè)是release版本搓劫,不用管它,直接選擇debug版本就好混巧,選擇完成后會(huì)重新創(chuàng)建枪向,這里先選擇flavorsdemo1Debug,等待build完成后直接運(yùn)行咧党。安裝完成后再選擇flavorsdemo2Debug版本秘蛔,等待build,安裝傍衡。見證奇跡的時(shí)候到了:
可以看到深员,變種1和變種2都安裝成功了,而且圖標(biāo)都不一樣蛙埂。這時(shí)候可能會(huì)有人問:包名呢倦畅?來來,使用ADM來看看正在運(yùn)行中的程序:
看绣的,demo1和demo2都在叠赐,而且和我們配置的一毛一樣呀,看到這里屡江,有沒有朋友想到更廣泛的用途呢芭概?
到這里,構(gòu)建變種的基礎(chǔ)就算完成惩嘉,接下來就是配置不同的源碼和資源文件罢洲。
2.源碼配置
業(yè)務(wù)場(chǎng)景
- 構(gòu)建多個(gè)APP,那么其中的API的地址肯定是不一樣的對(duì)吧文黎,我的做法是將API存放在一個(gè)全局的常亮類中惹苗,那么久可以將它抽取出來殿较,為不同的變種配置不同的地址。
- 權(quán)限控制鸽粉,頁面跳轉(zhuǎn)控制斜脂。說的庸俗一點(diǎn),同一個(gè)APP触机,有一個(gè)功能模塊A是后來開發(fā)的帚戳,而前面有些客戶沒有給這個(gè)功能模塊A的錢,那肯定是不能給他看的呀儡首,所以可以在一個(gè)類中加個(gè)flag來控制顯示與隱藏功能片任。
- 反正呢,就是用來配置少量不同源碼蔬胯,大多數(shù)源碼是應(yīng)該寫在main下对供,變種只用來做少量配置。
實(shí)踐
在這我只實(shí)現(xiàn)第一條氛濒,一法通百法通产场,其它都是一樣的。
- 首先要使用project視圖舞竿,這樣才能完全的看到整個(gè)項(xiàng)目結(jié)構(gòu)京景,然后在src下(和main同級(jí))創(chuàng)建和變種名字相同的目錄.以下:
- 創(chuàng)建包
首先在flavorsdemo1和flavorsdemo2下創(chuàng)建java和res文件夾骗奖,讓后再java目錄下創(chuàng)建包确徙,這里創(chuàng)建的包要和main下面的一樣,而且flavorsdemo1和flavorsdemo2的目錄也必須一致执桌,還有變種中存在的類鄙皇,main中是不允許存在的。
先看圖仰挣,再解釋:
三個(gè)源碼下的包名都是一致的伴逸,在變種中增加了global包,并創(chuàng)建了一個(gè)全局類
flavorsdemo1 Constant
public class Constant {
public static final String API_ADDRESS="這是變種1的API地址";
}
flavorsdemo2 Constant
public class Constant {
public static final String API_ADDRESS="這是變種2的API地址";
}
兩個(gè)類的名字膘壶,變量名稱都是一樣的违柏,只是值不相同,接下來在MainActivity中引用
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context="com.aohanyao.product.flavorsdemos.MainActivity">
<TextView
android:id="@+id/tv_api"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
private TextView tvApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvApi = (TextView) findViewById(R.id.tv_api);
tvApi.setText(Constant.API_ADDRESS);
}
}
直接對(duì)Constant.API_ADDRESS進(jìn)行引用香椎,來,直接啟動(dòng)兩個(gè)變種吧禽篱。
圖中分別是flavorsdemo1和flavorsdemo1兩個(gè)變種畜伐,可見,顯示的都是對(duì)應(yīng)的Constant.API_ADDRESS中的值躺率,看到這里玛界,配置不同源碼部分就算是完成万矾,所以應(yīng)該發(fā)散一下思維,這可可以做到很多事情的慎框。
3.資源配置
業(yè)務(wù)場(chǎng)景
比如良狈,變種一和變種二的某個(gè)功能模塊相同,但是名字和圖標(biāo)不相同
1. 大量文字變更
2. 大量圖標(biāo)變更
實(shí)踐
替換字符串資源和圖片資源
首先在兩個(gè)變種的res目錄下創(chuàng)建values目錄笨枯,創(chuàng)建相同的資源文件fd_string.xml薪丁,各自創(chuàng)建相同的字符串資源名稱。并在MainActivity進(jìn)行了引用馅精。
接下來分別啟動(dòng)兩個(gè)變種严嗜,看看結(jié)果
很明顯,達(dá)到了想要的效果洲敢。
下一步就是圖片資源的替換漫玄,和上面一樣,創(chuàng)建相應(yīng)的資源文件夾压彭,放入名稱一樣睦优,內(nèi)容不一樣的圖片,并在MainActivity中引用壮不。
來看看結(jié)果
至此汗盘,利用Gradle構(gòu)建變種就算是完全完成了。
最后
如果文中有什么不對(duì)的地方忆畅,歡迎指出衡未!
源碼地址 歡迎star
我的簡書
來來掃下碼,關(guān)注一下吧,或者微信搜索AndroidRookie