关于Android渠道打包这个事儿:
1.gradle提供了productFlavors
支持多渠道同时打包,优点是可以充分使用条件编译,比如美团的APP在一些渠道叫美团而在另一些渠道却要叫美团团购,没有任何兼容性问题,方便集成。缺点是每次都要重新编译打包,速度特别慢,现在我们公司四十多个渠道,8G内存固态硬盘的笔记本打一次差不多一两个小时,还有可能出现内存溢出。
2.友盟 打包过程:解压apk文件 -> 替换AndroidManifest.xml中的meta-data -> 压缩apk文件 ->签名 缺点是需要解压缩、压缩、重签名耗费时间较多,重签名会导致apk包在运行时有兼容性问题;
3.美团提供了一个解决方案美团Android自动化之旅—生成渠道包,它的原理是在APK文件的META-INF目里增加渠道文件,打包速度非常快,但读取时需要遍历APK文件的数据项,比较慢(需要解压缩,压缩),由于现在这一块的数据现在不参与签名校验,以后如果Google更改签名校验规则可能遇到兼容性问题。
4.packer-ng-plugin 利用的是Zip文件"可以添加comment(摘要)"的数据结构特点,在文件的末尾写入任意数据,而不用重新解压zip文件(apk文件就是zip文件格式);所以该工具不需要对apk文件解压缩和重新签名即可完成多渠道自动打包,高效速度快,无兼容性问题;
现在演示怎么集成packer-ng-plugin:
具体集成过程参见https://github.com/mcxiaoke/packer-ng-plugin?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
这儿只提及我使用的方式。 打包脚本(Python):
pack.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
import sys
import struct
import shutil
import argparse
import time
import datetime
import shutil
from multiprocessing import Pool
__version__ = '1.0.1.20151201'
ZIP_SHORT = 2
MARKET_PATH = 'markets.txt'
OUTPUT_PATH = 'archives'
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)
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))
apk_name = name + "-" + market + ext
apk_file = os.path.join(output,apk_name)
shutil.copy(path,apk_file)
# print('apkfile:',apkfile)
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
def read_market(path):
'''
read market info from apk file
read_market(apk-file-path)
'''
index = os.stat(path).st_size
# print('path:',path,'length:',index)
index -= len(MAGIC)
f = open(path,'rb')
f.seek(index)
# read and check magic
magic = f.read(len(MAGIC))
# print('magic',magic)
if magic == MAGIC:
index -= ZIP_SHORT
f.seek(index)
# read market string length
market_length = struct.unpack('<H',f.read(ZIP_SHORT))[0]
# print('comment length:',market_length)
index -= market_length
f.seek(index)
# read market
market = f.read(market_length)
# print('found market:',market)
return market
else:
# print('magic not matched')
return None
def verify_market(file,market):
'''
verify apk market info
verify_market(apk-file-path,market-name)
'''
return read_market(file) == market
def show_market(file):
'''
show market info for apk file
show_market(apk-file-path)
'''
print('market of',file,'is',read_market(file))
def parse_markets(path):
'''
parse file lines to market name list
parse_markets(market-file-path)
'''
with open(path) as f:
return filter(None,map(lambda x: x.split('#')[0].strip(), f.readlines()))
def process(file, market = MARKET_PATH,output = OUTPUT_PATH):
'''
process apk file to create market apk archives
process(apk-file-path, market = MARKET_PATH, output = OUTPUT_PATH)
'''
print ("************START PACKING********************")
markets = parse_markets(market)
counter = 0
for market in markets:
apk_file = write_market(file, market, output)
verified = verify_market(apk_file, market)
if not verified:
print('apk',apk_file,'for market',market,'verify failed')
# break
else:
print('processed apk',apk_file)
++counter
print('all',counter,'apks saved to',os.path.abspath(output))
print ("************PACKING COMPLETE********************")
def run_test(file,times):
'''
run market packer performance test
'''
print('start to run market packaging testing...')
t0 = time.time()
for i in xrange(1,times):
write_market(file,'%i Test Market' % i, 'temp')
print('run',times,'using',(time.time() - t0), 'seconds')
pass
def check(file):
'''
check apk file exists, check arguments, check market file exists
'''
market = MARKET_PATH
output = OUTPUT_PATH
if not os.path.exists(file):
print('apk file',file,'not exists or not readable')
return
if not os.path.exists(market):
print('market file',market,'not exists or not readable')
return
old_market = read_market(file)
if old_market:
print('apk file',file,'already had market:',old_market,
'please using original release apk file')
return
process(file,market,output)
def getapkFilePat():
for root, dirs, files in os.walk("./"):
for f in files:
try:
if f.index('.apk') > 0:
print ("find apk file:", f)
return f
else:
pass
except:
pass
print ("can not find APK file")
return None
if __name__ == '__main__':
print("start to look for apk file Make Sure There is only one apk file!!!!!!!")
apkpath = getapkFilePat()
if apkpath:
try:
shutil.rmtree('./archives')
print ("delete archives doc successsful!")
except Exception, e:
pass
check(apkpath)
pass
else:
print("Make Sure There is only one apk file!!!!!!!")
# print("START TIME:", datetime.datetime.now())
# check(**vars(parse_args()))
# print("END TIME:", datetime.datetime.now())
windows下的打包脚本 需要跟pack.py在同一目录下
python pack.py
pause
markets.txt
QIHOO360#360
CHANGHONG#changhong
XIAOMI#xiaomi
pack.py,pack.bat,markets.txt,xx.apk必须在同一目录下,apk文件保证只有一个。双击pack.bat即可批量打包。
在APP中获取渠道:
把PackerNg.java文件复制到自己的工程里面
在代码中获取并设置渠道
// 如果没有使用PackerNg打包添加渠道,默认返回的是""
final String market = PackerNg.getMarket(Context)
// 或者使用 PackerNg.getMarket(Context,defaultValue)
// 之后就可以使用了,比如友盟可以这样设置
AnalyticsConfig.setChannel(market)