為什么要多渠道
App一般會上傳多個(gè)應(yīng)用商店,例如應(yīng)用寶鸠珠、小米巍耗、華為秋麸、OPPO等渐排。
現(xiàn)在的手機(jī)都自帶應(yīng)用商店,用戶一般會在自帶應(yīng)用商店上搜索灸蟆。
產(chǎn)品驯耻、運(yùn)營可統(tǒng)計(jì)使用不同手機(jī)品牌的用戶的消費(fèi)情況,App日活炒考、月活可缚、UV、PV等斋枢。
傳統(tǒng)方式打包的痛點(diǎn)
如果App不是很出名帘靡,一般都需要自己打包上傳,除非像微信這種瓤帚,商店自動抓包進(jìn)行上傳描姚。曾經(jīng)參加過一個(gè)技術(shù)沙龍,微信的工程師說到戈次,他們的熱更新技術(shù)轩勘,如果使用了360加固進(jìn)行,就是失效怯邪,但是360應(yīng)用商店必須加固后才能上傳绊寻,所以他們就不上傳了,最后360應(yīng)用商店也還是抓包上傳了(可見有實(shí)力就是不一樣)。
不同的渠道澄步,可能App名字不同冰蘑,App圖標(biāo)Icon不同,如果手動替換的話村缸,體力勞動特別累人懂缕,筆者就曾經(jīng)做過一個(gè)app,需要給不同的景區(qū)生成app王凑,每個(gè)景區(qū)的圖標(biāo)和app名字都不一樣搪柑,傳統(tǒng)方式就是打包完一個(gè),手動替換再進(jìn)行打包索烹,10個(gè)還好工碾,慢慢后面景區(qū)越來越多,上升了50個(gè)百姓,工作量可想而知渊额,而且還容易錯(cuò),每個(gè)都需要手動檢查一遍(曾經(jīng)花2個(gè)小時(shí)垒拢,對著屏幕一個(gè)個(gè)替換旬迹,眼睛都花了,自然容易出錯(cuò)G罄唷)奔垦,這樣進(jìn)行幾次后,意識到這種重復(fù)勞動不應(yīng)該由我們來做尸疆,應(yīng)該交給機(jī)器呀椿猎!
Gradle配置多渠道打包
后面我們搜索了一下資料,決定使用Gradle配置的方式進(jìn)行打包寿弱,但他也有優(yōu)缺點(diǎn)犯眠。
優(yōu)點(diǎn):就是每個(gè)包都只需要配置好要替換的文件和占位,即可開始打包症革。
缺點(diǎn):每次打包一個(gè)渠道包筐咧,都需要編譯一次,項(xiàng)目非常大的時(shí)候噪矛,可謂非常耗時(shí)量蕊!
Manifest占位,動態(tài)替換meta標(biāo)簽值摩疑,動態(tài)標(biāo)識渠道
實(shí)現(xiàn)步驟
-
配置App模塊的build.gradle文件危融。
- 使用productFlavors,添加2個(gè)渠道雷袋,例如360和應(yīng)用寶吉殃。
- 注意如果是3.0版本的Gradle辞居,必須要添加上flavorDimensions緯度。
- 使用manifestPlaceholders蛋勺,增加清單文件占位瓦灶,格式:[占位名:值, 占位名:值]。
apply plugin: 'com.android.application'
android {
//省略其他配置...
//3.0版本Gradle開始必須添加緯度
flavorDimensions "default"
//多渠道打包配置
productFlavors {
//渠道包
app_360 {
manifestPlaceholders = [channel: "app_360"]
}
app_qq {
manifestPlaceholders = [channel: "app_qq"]
}
}
}
- 清單文件增加占位標(biāo)識
- 例如我們使用友盟進(jìn)行渠道記錄抱完,增加一個(gè)meta_data標(biāo)簽贼陶,標(biāo)簽名為UMENG_CHANNEL,值是占位符標(biāo)識${channel}巧娱。
注意這個(gè)標(biāo)識要和第一步的build.gradle文件中的productFlavors配置一致
- 例如build.gradle碉怔。
//占位名:channel,值:app_360
manifestPlaceholders = [app_name: "360渠道包", channel: "app_360"]
- meta標(biāo)簽禁添。
<!-- 占位名:channel撮胧,不包括${} -->
<meta-data
android:name="UMENG_CHANNEL"
android:value="${channel}" />
- 完整配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.zh.makechannelpackage">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- 省略其他配置... -->
<!-- 多渠道配置 -->
<meta-data
android:name="UMENG_CHANNEL"
android:value="${channel}" />
</application>
</manifest>
- 在Java代碼層讀取meta標(biāo)簽,獲取其值即可老翘。
//渠道工具類
public class ChannelUtil {
/**
* 獲取app包內(nèi)的渠道標(biāo)識
*/
public static String getChannel(Context context) {
try {
PackageManager manager = context.getPackageManager();
ApplicationInfo appInfo = manager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
return appInfo.metaData.getString("UMENG_CHANNEL");
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return "";
}
}
}
//調(diào)用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String channel = ChannelUtil.getChannel(getApplicationContext());
Toast.makeText(getApplicationContext(), "當(dāng)前渠道:" + channel, Toast.LENGTH_SHORT).show();
}
}
給不同的編譯環(huán)境配置清單Key
除了productFlavors中配置manifestPlaceholders生成渠道包外芹啥,在編譯環(huán)境buildTypes下也可以使用,例如不同編譯環(huán)境下铺峭,高德地圖的key不同墓怀,就可以在buildTypes中使用。
- 修改build.gradle配置卫键。
buildTypes {
debug {
manifestPlaceholders = [amap_key: "amp_debug_xxxxxkey"]
//省略其他配置...
}
release {
manifestPlaceholders = [amap_key: "amp_release_xxxxxkey"]
//省略其他配置...
}
}
- 清單文件中添加高德Key配置傀履。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.zh.makechannelpackage">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 配置高德key -->
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="${amap_key}" />
</application>
</manifest>
Lib模塊共享,App接入問題
一般我們都會使用多個(gè)Lib進(jìn)行分模塊開發(fā)永罚,例如支付模塊的Pay庫中的微信回調(diào)Activity啤呼,7.0獲取文件需要使用FileProvider等,都需要制定包名呢袱,但是我們的lib需要提供給其他App進(jìn)行接入,清單文件的配置需要接入方配置翅敌,如果配置比較多羞福,而且后續(xù)版本需要更換配置,都需要接入方重新配置會比較麻煩蚯涮,那么可不可以將配置留在Lib庫中治专,通過動態(tài)配置的方式動態(tài)配置呢。
- 例如微信支付的回調(diào)Activity配置遭顶,通過applicationId占位张峰,可以動態(tài)獲取到接入方的包名,只要按照約定棒旗,在包名下建立wxapi包下喘批,建立WXEntryActivity文件即可。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.zh.makechannelpackage">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 動態(tài)配置微信回調(diào)Activity -->
<activity
android:name="${applicationId}.wxapi.WXEntryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<!-- 省略其他配置... -->
</application>
</manifest>
- 再例如圖片選擇庫,使用到了FileProvider饶深,我們不希望接入方配置餐曹,則也可以使用applicationId進(jìn)行占位。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.zh.makechannelpackage">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- FileProvider配置 -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- 省略其他配置... -->
</application>
</manifest>
動態(tài)指定App名稱
經(jīng)過前面的配置已經(jīng)足夠配置出不同渠道標(biāo)識的渠道包了敌厘,我們還可以繼續(xù)拓展下台猴,例如不同渠道的App名字不同。(一般因?yàn)橛械膽?yīng)用商店會以App名稱而被拒...)
- 修改build.gradle文件俱两。
manifestPlaceholders中添加一個(gè)標(biāo)識饱狂,app_name,值為對應(yīng)的名稱宪彩。例如360渠道為360渠道包嗡官,應(yīng)用寶渠道為應(yīng)用寶渠道。
//3.0Gradle開始必須添加緯度
flavorDimensions "default"
//多渠道打包配置
productFlavors {
//渠道包
app_360 {
manifestPlaceholders = [app_name: "360渠道包", channel: "app_360"]
}
app_qq {
manifestPlaceholders = [app_name: "應(yīng)用寶渠道包", channel: "app_qq"]
}
}
動態(tài)指定App包名
例如我們debug環(huán)境下毯焕,希望測試包和正式包能夠共存衍腥,那么共存就需要包名不同,所以可以對包名進(jìn)行替換或者加后綴纳猫。
- 修改build.gradle文件婆咸。
- applicationIdSuffix,給包名添加后綴芜辕。例如測試版加上.internal后綴尚骄。
- applicationId,整個(gè)替換包名侵续。(一般不會這么干倔丈,只是提一下)
//3.0Gradle開始必須添加緯度
flavorDimensions "default"
//多渠道打包配置
productFlavors {
//開發(fā)版
developer {
//測試版加包名后綴,方便和正式版共存
applicationIdSuffix ".internal"
manifestPlaceholders = [app_name: "開發(fā)版", channel : "app_internal"]
}
production {
//也可以完全替換包名
applicationId "me.zh.demo"
manifestPlaceholders = [app_name: "正式版",
channel: "app_internal"]
}
}
動態(tài)生成變量
開發(fā)中状蜗,Log打印是必不可少的需五,但是我們在正式環(huán)境是需要將Log打印去掉的,常規(guī)做法就是打印函數(shù)前加一個(gè)LogEnable的變量轧坎,將打印調(diào)用去掉宏邮。而我們一般會在常量類中添加開關(guān)變量。
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Logger.setDelegate(new L());
Logger.setLogPrintEnable(true);
}
}
問題:而每次打包之前都需要將開關(guān)變量設(shè)置為false缸血,很容易忘蜜氨。
期望:我們希望不同buildType下的編譯環(huán)境,打印Log是不一樣的捎泻,例如buildType為debug時(shí)飒炎,Log開關(guān)為開,buildType為release時(shí)笆豁,Log開關(guān)為關(guān)郎汪。
- Gradle為我們提供了buildConfigField赤赊,用于動態(tài)生成變量在BuildConfig,那么我們給不同的buildType進(jìn)行添加即可怒竿。
//編譯類型
buildTypes {
debug {
//打印開關(guān)變量
buildConfigField("boolean", "LOG_ENABLE", "true")
//省略其他配置...
}
release {
buildConfigField("boolean", "LOG_ENABLE", "false")
//省略其他配置...
}
}
- 在Java代碼中砍鸠,設(shè)置BuildConfig中生成的變量即可。
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Logger.setDelegate(new L());
Logger.setLogPrintEnable(BuildConfig.LOG_ENABLE);
}
}
不同渠道替換資源文件
能替換包名和App名字了耕驰,原以為完畢爷辱,結(jié)果運(yùn)營說,360商店我們要出首發(fā)朦肘!圖標(biāo)和啟動圖要加上360的標(biāo)識饭弓!這時(shí)候就需要用到同結(jié)構(gòu)、同名文件夾來替換了媒抠。
在和main文件夾下弟断,建立同名渠道的文件夾,例如app_360渠道趴生,建立的文件夾名為app_360阀趴。
assets文件夾、res文件夾苍匆、以及AndroidManifest.xml清單文件結(jié)構(gòu)都要和main中的一致刘急。
AppIcon同名即可替換。清單文件同名即可浸踩,內(nèi)部特定不同的內(nèi)容即可叔汁。
關(guān)于Java代碼多渠道
上面說到資源文件,建立同目錄進(jìn)行替換检碗,那么Java代碼可以同級目錄下据块,同名Java文件替換嗎?很遺憾的是不可以折剃,需要不同渠道另假,進(jìn)行不同的邏輯時(shí),只能通過剛才ChannelUtil獲取渠道信息微驶,進(jìn)行邏輯判斷了浪谴。
總結(jié)
使用Gradle來進(jìn)行配置,大大減少了我們的工作量因苹,媽媽再也不用擔(dān)心我打渠道包需要2小時(shí)啦,至于gradle每構(gòu)建一個(gè)渠道包都編譯一次問題篇恒,可以單獨(dú)給assets設(shè)置一個(gè)配置文件扶檐,后續(xù)使用zip修改配置文件,達(dá)到只打一個(gè)包胁艰,其他包都使用打包工具進(jìn)行打包款筑,時(shí)間可以節(jié)省非常多智蝠。