本篇文章已授權(quán)微信公眾號 hongyangAndroid (鴻洋)獨(dú)家發(fā)布
前言
公司有需求app需要拆分為兩個:云部署版和企業(yè)版。
a.云部署版就是原app ,保持不變
b.企業(yè)版是新app,與云部署的界面,功能有差異,并且不上架應(yīng)用市場,只在企業(yè)內(nèi)網(wǎng)使用。用戶手機(jī)上可以同時安裝這兩個app。
拆分策略
根據(jù)需求,首先兩個app肯定是一個項(xiàng)目,不可能再copy一份代碼分開維護(hù)。那么至少要處理這些事務(wù):
gradle要支持差異化打包,通過AndroidStudio要能夠打包出兩個apk,并且這兩個app的報名不能一樣,才能同時安裝在同一部手機(jī)上。
代碼中有一個全局字段,區(qū)分兩個版本做界面的差異化(app名稱,圖標(biāo)),一些功能
由于我們app有推送服務(wù) ,要保證兩個app的推送互不干擾,點(diǎn)擊推送消息的通知欄能夠跳到指定app
我們app有QQ/微信分享功能,假設(shè)分享一個網(wǎng)頁到QQ,那么點(diǎn)擊qq里面的消息要能正確啟動并跳轉(zhuǎn)到當(dāng)前分享的app。
如果你們使用了teamcity,還要差異化編譯腳本。(注意:當(dāng)前兩個項(xiàng)目是同一個svn分支)
解決方案
- gradle要支持差異化打包
先思考下什么樣的兩個app能夠安裝在同一部手機(jī)上?很多人說包名不同。
其實(shí)這個是eclipse時代的說法,到了AndroidStudio上準(zhǔn)確來講,是ApplicationId不同的app可以安裝在同一部手機(jī)上??梢钥聪翧ndroidStudio下構(gòu)建的項(xiàng)目,不同的moduler里面都有一個AndroidManifest.xml文件,并且每個AndroidManifest.xml里面package都不同,這個package影響的是每個moduler里面R.java.xxx的包名,以及這個moduler內(nèi)所有java文件的包名。不同的moduler這個package肯定不同所以我們在不同的moduler可以使用相同的資源文件名,和java文件名。
再說app目錄下build.gradle里面定義的applicationId:

這個值默認(rèn)跟app目錄下manifest.xml里面package一樣,如果你不寫的話。這個才是決定app唯一性的字段(不僅是本地安裝,也包括應(yīng)用市場判斷的依據(jù))。
OK,明白了packageName和applicationId的區(qū)別,如何用gradle構(gòu)建兩個app呢?很簡單:
productFlavors {
cloud {
//云部署版本
resValue "string","app_name", "MyLuban"
resValue "string","app_scheme", "bv4phone"
resValue "string","app_link_scheme", "cloud"
manifestPlaceholders = [app_icon:"@drawable/ic_launcher"]
}
entp {
//企業(yè)版本
applicationIdSuffix ".entp"
resValue "string","app_name", "MyLuban企業(yè)版"
resValue "string","app_scheme", "bv4phoneentp"
resValue "string","app_link_scheme", "entp"
manifestPlaceholders = [app_icon:"@drawable/ic_launcher_entp"]
}
}
關(guān)鍵代碼是:(其他代碼先忽略)
//企業(yè)版本
applicationIdSuffix ".entp"
給企業(yè)版的app的applicationId加后綴,就可以和云部署區(qū)分開了。注意千萬不要改變云部署的applicationId,因?yàn)槟愕腶pp在應(yīng)用市場就是原來的applicationId,一旦改了就無法再覆蓋上架了。

- 界面和功能差異化
如何定制不同的app名稱:
resValue "string","app_name", "MyLuban"
這個語句可以定義任何不同的String,比如這里名字為app_name,取值MyLuban。然后在manifest.xml中使用:
android:label="@string/app_name"
定制不同的app圖標(biāo):
manifestPlaceholders = [app_icon:"@drawable/ic_launcher"]
在manifest.xml中使用:
android:icon="${app_icon}"
當(dāng)我們加入產(chǎn)品差異化編譯一次后,在build文件夾的BuildConfig文件可以看到這個app的編譯信息:

因此我們可以寫一個工具類,來保存當(dāng)前的產(chǎn)品類型,在Application的onCreate里面初始化:
//APP產(chǎn)品類型
switch (BuildConfig.FLAVOR){
case "cloud":
ProductUtil.setProductType(CLOUD);
break;
case "entp":
ProductUtil.setProductType(ENTERPRISE);
break;
}
ProductUtil里面很簡單就是set/get方法,然后其他任何就可以獲取當(dāng)前app的產(chǎn)品類型,繼續(xù)做界面和功能的差異化。
還有個小問題,此時點(diǎn)擊run,AS會編譯哪個app呢?
點(diǎn)擊run的默認(rèn)執(zhí)行:在AS1.5是按照productFlavors 排序從上到下的,執(zhí)行debug編譯
在AS2.1之后是按照首字母排序,比如這里就是執(zhí)行 cloud-debug版本
更改方式:

- 推送差異化
我們app的推送服務(wù)是基于webSocket自己實(shí)現(xiàn)的,單獨(dú)開啟進(jìn)程里運(yùn)行service來實(shí)現(xiàn)獲取推送。即使使用第三方的推送,原理也差不多。
那么,手機(jī)上運(yùn)行兩個app之后,推送服務(wù)會相互干擾嗎?

很簡單看下手機(jī)設(shè)置里面的應(yīng)用進(jìn)程就知道,兩個app進(jìn)程包括啟動的推送Servcie進(jìn)程,完全獨(dú)立,這樣就可以保證app的推送互不干擾而不需要特殊處理。
但是點(diǎn)擊推送消息通知欄跳轉(zhuǎn)app,就要區(qū)分開了。因?yàn)檫@里的跳轉(zhuǎn)一般是Intent隱式跳轉(zhuǎn),我們應(yīng)該為跳轉(zhuǎn)目標(biāo)Activity的IntentFilter做差異化:
<intent-filter>
<action android:name="com.myluban.push" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="@string/app_link_scheme" />
</intent-filter>
用兩個不同的字段區(qū)分data,字段在gradle里面生成(看上文的gradle配置),然后在啟動Intent的位置作區(qū)分:
private String PUSH_SCHEME = "cloud";
f(ProductUtil.getProductType()==ProductType.ENTERPRISE){
PUSH_SCHEME = "entp";
}
Intent jumpIntent = new Intent("com.myluban.push");
jumpIntent.setData(Uri.parse(PUSH_SCHEME + "://abc"));
//...
分享差異化
如果你的分享也是打開一個網(wǎng)頁,點(diǎn)擊網(wǎng)頁上的按鈕啟動自身app。那么,跟推送跳轉(zhuǎn)類似,也是在IntentFilter作區(qū)分。只不過要前端配合一下,根據(jù)不同產(chǎn)品的分享,設(shè)置不同的scheme。teamcity編譯腳本差異化
teamcity是用于持續(xù)集成的工具,主要是方便測試可以隨時一鍵編譯最新代碼,打包apk。teamcity一般是根據(jù)不同的項(xiàng)目,建立不同的編譯選項(xiàng)。那這里我們兩個app其實(shí)是同一個svn分支,怎么區(qū)分呢?答案就是建立兩套編譯腳本,gradle是支持的:(由于每個公司teamcity都不一樣,貼出關(guān)鍵代碼):
set build_cmd=assembleCloudRelease
if "%2%"=="myluban_enterprise" (
set apk_prefix=myluban_entp
set build_cmd=assembleEntpRelease
)
echo //////// 編譯release apk ////////
set srcPack=%apk_prefix%-release.apk
call %workDir%/gradlew.bat -b %workDir%/%projectDir%/build.gradle %build_cmd% -Ptargetdir=%cd%\%workDir%\release -Papkname=%srcPack% -x lint
其實(shí)就是執(zhí)行2個不同gradle命令。
其他
本文總結(jié)了app拆分的策略,以及一些問題的解決,還有其他疑問歡迎留言。