Android多渠道打包,由于傳統(tǒng)打包速度比較慢单芜,本篇主要 介紹一種新的打包方案细疚,速度可提升百倍。
背景:
android studio支持多渠道打包方式俩莽,是通過gradle 的變種方式來進行的:
//build.gradle
manifestPlaceholders = [FFCHANNEL_ID_VALUE: channel]
//AndroidManifest.xml
<meta-data
android:name="FFCHANNEL_ID"
android:value="${FFCHANNEL_ID_VALUE}" />
這種方式是google推薦的方式旺坠,但這種方式對于幾個渠道包是可以支持的, 當我們需要打上百個渠道包時扮超,這種方案耗時很長取刃,是項目開發(fā)不能忍受的。而目前我們項目需要進行加固后保障安全出刷,發(fā)布市場的app需要進行加固蝉衣。當發(fā)布app時候,我們需要在集成打包機器上進行打包巷蚪,目前項目為11個渠道包病毡,耗時一個多小時,時間已經(jīng)屬于不可容忍的,況且還未進行加固屁柏,目前加固11個包啦膜,需要半小時有送,廣州銀行80個渠道包,2G多僧家,全部加固完成需要半天時間雀摘。這種情況下,我們需要考慮采用新的方案來解決打渠道包耗時費力的問題八拱。
新方案
新方案是通過zip包注釋的方案進行多渠道打包的阵赠,由開發(fā)提供一個生成包即可,使用該包進行加固肌稻,作為母包清蚀。通過開發(fā)提供的腳本,批量生成多渠道包爹谭,基本屬于拷貝復(fù)制的節(jié)奏枷邪,由于目前apk為27M多,偏大诺凡,所以基本一個渠道包為1s东揣,生成80個渠道包耗時80秒,親測腹泌。
可以考慮以后由開發(fā)只提供母包嘶卧,由運營發(fā)布市場的同事根據(jù)運營需求來自己執(zhí)行腳本發(fā)布各市場渠道包,簡單凉袱,方便芥吟,快捷,不受限制绑蔫。
原理
使用APK注釋字段保存渠道信息和MAGIC字節(jié)运沦,通過腳本將取渠道信息寫入apk注釋字段中。 android使用的apk包的壓縮方式是zip配深,與zip有相同的文件結(jié)構(gòu)携添,在zip的Central directory file header中包含一個File comment區(qū)域,可以存放一些數(shù)據(jù)篓叶。File comment是zip文件如果可以正確的修改這個部分烈掠,就可以在不破壞壓縮包、不用重新打包的的前提下快速的給apk文件寫入自己想要的數(shù)據(jù)缸托。 comment是在Central directory file header末尾儲存的左敌,可以將數(shù)據(jù)直接寫在這里,下表是header末尾的結(jié)構(gòu)
image
由于數(shù)據(jù)是不確定的俐镐,我們無法知道comment的長度矫限,從表中可以看到zip定義comment的長度的位置在comment之前,所以無法從zip中直接獲取comment的長度。這里我們需要自定義comment的長度叼风,在自定義comment內(nèi)容的后面添加一個區(qū)域儲存comment的長度取董,結(jié)構(gòu)如下圖。 image
目前也已經(jīng)和愛加密加固的同事進行了確認无宿,愛加密的加固方式不會占用該zip注釋茵汰,不會和該多渠道包方案沖突,也已經(jīng)在項目中測試驗證通過孽鸡。
實現(xiàn)
寫入 :寫入可以使用java蹂午,也可以使用python,目前考慮到可以對外提供彬碱,使用python豆胸。
讀取 :讀取需要在Android代碼中用java實現(xiàn)。
//定義魔法串作為結(jié)束符
MAGIC = '!ZXK!'
//寫入
def write_market(path, market, output):
'''
write market info to apk file
write_market(apk-file-path, market-name, output-path)
'''
path = os.path.abspath(path)
output = unicode(output)
if not os.path.exists(path):
print('apk file',path,'not exists')
return
if read_market(path):
print('apk file',path,'had market already')
return
if not output:
output = os.path.dirname(path)
if not os.path.exists(output):
os.makedirs(output)
name,ext = os.path.splitext(os.path.basename(path))
# name,package,vname,vcode
app = parse_apk(path)
apk_name = '%s-%s-%s-%s%s' % (app['app_package'],
market.decode('utf8'), app['version_name'], app['version_code'], ext)
# apk_name = name + "-" + market + ext
apk_file = os.path.join(output,apk_name)
shutil.copy(path,apk_file)
# print('apk file:',apk_file)
index = os.stat(apk_file).st_size
index -= ZIP_SHORT
with open(apk_file,"r+b") as f:
f.seek(index)
# write comment length
f.write(struct.pack('<H',len(market) + ZIP_SHORT + len(MAGIC)))
# write comment content
# content = [market_string + market_length + magic_string]
f.write(market)
f.write(struct.pack('<H',len(market)))
f.write(MAGIC)
return apk_file
//讀取
public static String readZipComment(File file) throws IOException {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "r");
long index = raf.length();
byte[] buffer = new byte[MAGIC.length];
index -= MAGIC.length;
// read magic bytes
raf.seek(index);
raf.readFully(buffer);
// if magic bytes matched
if (isMagicMatched(buffer)) {
index -= SHORT_LENGTH;
raf.seek(index);
// read content length field
int length = readShort(raf);
if (length > 0) {
index -= length;
raf.seek(index);
// read content bytes
byte[] bytesComment = new byte[length];
raf.readFully(bytesComment);
return new String(bytesComment, UTF_8);
} else {
throw new IOException("zip comment content not found");
}
} else {
throw new IOException("zip comment magic bytes not found");
}
} finally {
if (raf != null) {
raf.close();
}
}
}
使用
目前提供寫入腳本到項目中堡妒,讀取渠道號代碼已經(jīng)集成到j(luò)ava代碼中配乱。
使用如下:
格式是:python ngpacker.py [file] [market] [output]
eg:
python ngpacker.py app-release_signed.apk markets.txt apks
渠道配置文件如下:
1001#bdsj
1002#azsc
1003#91zs
1004#wdj
1005#txyyb
1006#xmyy
1007#360sj
1008#hwyy
1009#anzhisc
1010#ppzs
1011#lsd