我想叶堆,每當(dāng)發(fā)布新版本時,大家都會對那么多發(fā)布渠道感到頭疼不已驹吮,本文主要介紹三種Android的多渠道打包方式茸时。下面的例子我以 TalkingData 統(tǒng)計(jì)為例子贡定。
1. Gradle 配置 Flavor
1.1 在 AndroidManifest.xml 中配置 meta-data 標(biāo)簽:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package.name">
<application>
<meta-data android:name="TD_CHANNEL_ID" android:value="${TD_CHANNEL_VALUE}"/>
</application>
</manifest>
1.2 在項(xiàng)目的 build.gradle 里配置 productFlavors,定義渠道號:
productFlavors {
yyb {}
bdsjzs {}
wdj {}
xiaomi {}
sjzs360 {}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [TD_CHANNEL_VALUE: name]
}
}
上面定義了一些渠道(比如:豌豆莢可都,360缓待,小米等),TD_CHANNEL_VALUE
就是你在 meta-data 標(biāo)簽中配置的值渠牲。
同時旋炒,需要注意的是,你需要在 defaultConfig
中配置一個默認(rèn)的渠道名稱:
manifestPlaceholders = [TD_CHANNEL_VALUE: "default_channel_name"]
該原理就是在打包過程中更換 meta-data 標(biāo)簽中的內(nèi)容签杈。
優(yōu)勢:方便理貨瘫镇,可以根據(jù)自身的需求配置不同的渠道執(zhí)行不同的邏輯(比如:小米渠道有一個獨(dú)立的方法,其他渠道沒有)答姥;
劣勢:打包速度慢(打包時是對每個渠道都進(jìn)行一次完整的打包過程)铣除。
2.第三方打包工具
第三方的打包工具原理跟 Gradle 一樣,也是去修改 AndroidManifest.xml 中的 meta-data 標(biāo)簽的內(nèi)容鹦付,然后執(zhí)行N次打包簽名的操作實(shí)現(xiàn)多渠道打包的尚粘。
優(yōu)勢:簡單方便,幾乎不用自身做什么工作敲长;
劣勢:打包速度過慢郎嫁;
3.美團(tuán)的多渠道打包方案
多渠道打包原理可以參考美團(tuán)Android自動化之旅—生成渠道包這篇文章。Demo 可以查看 快速多渠道打包潘明。
3.1 實(shí)現(xiàn)原理
直接解壓apk行剂,解壓后的根目錄會有一個META-INF
目錄,如下圖所示:
如果在META-INF目錄內(nèi)添加空文件钳降,可以不用重新簽名應(yīng)用。因此腌巾,通過為不同渠道的應(yīng)用添加不同的空文件遂填,可以唯一標(biāo)識一個渠道。
這樣澈蝙,每打一個渠道包只需復(fù)制一個apk吓坚,在 META-INF 中添加一個使用渠道號命名的空文件即可。這種打包方式速度非车朴快礁击,900多個渠道不到一分鐘就能打完。
2.2 多渠道實(shí)現(xiàn)步驟
2.2.1 android 代碼中獲取渠道號
/**
* 渠道號工具類:解析壓縮包,從中獲取渠道號
*/
public class ChannelUtil {
private static final String CHANNEL_KEY = "uuchannel";
private static final String DEFAULT_CHANNEL = "internal";
private static String mChannel;
public static String getChannel(Context context) {
return getChannel(context, DEFAULT_CHANNEL);
}
public static String getChannel(Context context, String defaultChannel) {
if (!TextUtils.isEmpty(mChannel)) {
return mChannel;
}
//從apk中獲取
mChannel = getChannelFromApk(context, CHANNEL_KEY);
if (!TextUtils.isEmpty(mChannel)) {
return mChannel;
} //全部獲取失敗
return defaultChannel;
}
/**
* 從apk中獲取版本信息
*
* @param context
* @param channelKey
* @return
*/
private static String getChannelFromApk(Context context, String channelKey) {
long startTime = System.currentTimeMillis();
//從apk包中獲取
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
//默認(rèn)放在meta-inf/里哆窿, 所以需要再拼接一下
String key = "META-INF/" + channelKey;
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(key)) {
ret = entryName; break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String channel = "";
if (!TextUtils.isEmpty(ret)) {
String[] split = ret.split("_");
if (split != null && split.length >= 2) {
channel = ret.substring(split[0].length() + 1);
}
System.out.println("-----------------------------");
System.out.println("渠道號:" + channel + "链烈,解壓獲取渠道號耗時:" + (System.currentTimeMillis() - startTime) + "ms");
System.out.println("-----------------------------");
} else {
System.out.println("未解析到相應(yīng)的渠道號,使用默認(rèn)內(nèi)部渠道號");
channel = DEFAULT_CHANNEL;
}
return channel;
}
然后在代碼中初始化調(diào)用TalkingData設(shè)置渠道號:
String channelName = ChannelUtil.getChannel(mContext);
TCAgent.init(mContext,Constants.APP_KEY_TD,channelName );
2.2.2 編寫渠道號文件
修改 info/channel.txt
(一行代表一個渠道):
yyb
bdsjzs
wdj
xiaomi
...
2.2.3 編寫 python 腳本
實(shí)現(xiàn)解壓 apk 文件挚躯,遍歷渠道文件 為 META-INF 目錄添加新文件强衡,重新壓縮 apk 文件等邏輯:
# coding=utf-8
import zipfile
import shutil
import os
def delete_file_folder(src):
'''delete files and folders'''
if os.path.isfile(src):
try:
os.remove(src)
except:
pass
elif os.path.isdir(src):
for item in os.listdir(src):
itemsrc=os.path.join(src,item)
delete_file_folder(itemsrc)
try:
os.rmdir(src)
except:
pass
# 創(chuàng)建一個空文件,此文件作為apk包中的空文件
src_empty_file = 'info/empty.txt'
f = open(src_empty_file,'w')
f.close()
# 在渠道號配置文件中码荔,獲取指定的渠道號
channelFile = open('./info/channel.txt','r')
channels = channelFile.readlines()
channelFile.close()
print('-'*20,'all channels','-'*20)
print(channels)print('-'*50)
# 獲取當(dāng)前目錄下所有的apk文件
src_apks = [];
for file in os.listdir('.'):
if os.path.isfile(file):
extension = os.path.splitext(file)[1][1:]
if extension in 'apk':
src_apks.append(file)
# 遍歷所以的apk文件漩勤,向其壓縮文件中添加渠道號文件
for src_apk in src_apks:
src_apk_file_name = os.path.basename(src_apk)
print('current apk name:',src_apk_file_name)
temp_list = os.path.splitext(src_apk_file_name)
src_apk_name = temp_list[0]
src_apk_extension = temp_list[1]
apk_names = src_apk_name.split('-');
output_dir = 'outputDir'+'/'
if os.path.exists(output_dir):
delete_file_folder(output_dir)
if not os.path.exists(output_dir):
os.mkdir(output_dir)
# 遍歷從文件中獲得的所以渠道號,將其寫入APK包中
for line in channels:
target_channel = line.strip()
target_apk = output_dir + apk_names[0] + "-" + target_channel+"-"+apk_names[2] + src_apk_extension
shutil.copy(src_apk, target_apk)
zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
empty_channel_file = "META-INF/uuchannel_{channel}".format(channel = target_channel)
zipped.write(src_empty_file, empty_channel_file)
zipped.close()
print('-'*50)
print('repackaging is over ,total package: ',len(channels))
input('\npackage over...')
2.2.4 打包一個簽名 APK 包
將APK 包放在 python 腳本的同級目錄
2.2.5 執(zhí)行python 腳步缩搅,多渠道打包
python Android.py
最后就會在 同級目錄下生成 outputDir
目錄越败,里面就是各個渠道的 APK 。
優(yōu)勢:打包速度很快硼瓣,很方便究飞;
劣勢:不夠靈活,不能靈活的配置不同的渠道不同的業(yè)務(wù)邏輯巨双;
總結(jié)
三種打包方式中噪猾,各有各的優(yōu)勢,大家在選擇多渠道打包方式的時候可以根據(jù)自身的需求來選擇