听说Google play快来中国了,不知道这个消息对于奋战在一线的Android开发者来说是不是个福音,终于可以不用翻墙下载Android SDK、下载Android Studio(避免XCodeGhost闹剧),不过也有一个威胁,对国内的引用市场什么360、百度、豌豆荚等渠道市场是不是会有影响呢?就让我们拭目以待吧。
其实这篇博客应该在上周就要写的,因为本人搬家的缘故一直此次往后退,今天终于可以腾出时间来写这篇博客了—-Android的多渠道打包。
多渠道打包
插件打包法:
Android的productFlavors打包法
不过现在你在product Flavor这个打包仍旧很慢,网易财经30多个渠道一个小时,这个是我们难以忍受的,怎么办怎么办?
快速打包方案
这个问题也困扰着美团的一群工程师们,美团的多渠道打包。
第一种是maven打包和Gradle的product Flavor差不多,替换一次渠道名称,打一个包,100渠道,100*3分钟。
第二种其实就是现在有些黑心公司,利用反编译技术改变一些文件、重新签名,再打包,很是不正规。
第三种就是今天的重头戏,就是将mate-inf下新建一个空文件,这文件的名称中有渠道名称,每次只需要读取渠道名称即可,这个脚本用的是Python。
说起来简单,就来一步步分析吧。
渠道
准备好各个渠道名,放到一个文件里,每个渠道一行:
1 2 3 4 5 6 7
| wandoujia hiapk 91 QQ 360 miliao goapk
|
Python脚本
创建一个空文件
1 2 3 4 5
| 空文件 便于写入此空文件到apk包中作为channel文件 src_empty_file = 'info/imoney.txt'
f = open(src_empty_file, 'w') f.close()
|
1 2 3 4 5 6 7 8 9
|
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)
|
1 2 3 4 5 6 7
|
channel_file = 'info/channel.txt' f = open(channel_file) lines = f.readlines() f.close() `
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| for src_apk in src_apks: src_apk_file_name = os.path.basename(src_apk) temp_list = os.path.splitext(src_apk_file_name) src_apk_name = temp_list[0] src_apk_extension = temp_list[1]
output_dir = 'output_' + src_apk_name + '/' if not os.path.exists(output_dir): os.mkdir(output_dir)
for line in lines: target_channel = line.strip() target_apk = output_dir + src_apk_name + "_" + target_channel + src_apk_extension shutil.copy(src_apk, target_apk) zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED) empty_channel_file = "META-INF/channel_{channel}".format(channel = target_channel) zipped.write(src_empty_file, empty_channel_file) zipped.close()
|
**
通过python可以直接运行,20s 30多个渠道就出来。
读取渠道名称
由于我们的将渠道信息放到了meta-inf下,而不是以前的那个放到Manifest文件,所以需要在meta-inf获取渠道名称,并将这个渠道手动设置到对应统计的SDK(如Umeng,百度等)中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
public class MutiChannelConfig {
public static final String Version = "version";
public static final String Channel = "channel";
public static final String DEFAULT_CHANNEL = "netease";
public static final String Channel_File = "channel";
public static String getChannelFromMeta(Context context) { ApplicationInfo appinfo = context.getApplicationInfo(); String sourceDir = appinfo.sourceDir; 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("META-INF") && entryName.contains("channel_")) { ret = entryName; break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (zipfile != null) { try { zipfile.close(); } catch (IOException e) { e.printStackTrace(); } } } String[] split = ret.split("_"); if (split != null && split.length >= 2) { return ret.substring(split[0].length() + 1); } else { return DEFAULT_CHANNEL; } }
public static String getChannel(Context mContext) { String channel = ""; if (isNewVersion(mContext)) { LayzLog.e("isNewVersion %s", "isNewVersion"); saveChannel(mContext); channel = getChannelFromMeta(mContext); } else { channel = getCachChannel(mContext); } return channel; }
public static void saveChannel(Context mContext) { SharedPreferences mSettinsSP = mContext.getSharedPreferences(Channel_File, Activity.MODE_PRIVATE); SharedPreferences.Editor mSettinsEd = mSettinsSP.edit(); mSettinsEd.putString(Version, APPUtils.getAppVersion(mContext)); mSettinsEd.putString(Channel, getChannelFromMeta(mContext)); mSettinsEd.commit(); }
private static boolean isNewVersion(Context mContext) { SharedPreferences mSettinsSP = mContext.getSharedPreferences(Channel_File, Activity.MODE_PRIVATE); String version = APPUtils.getAppVersion(mContext); LayzLog.e("version%s", version); return !mSettinsSP.getString(Version, "").equals(version); }
private static String getCachChannel(Context mContext) { SharedPreferences mSettinsSP = mContext.getSharedPreferences(Channel_File, Activity.MODE_PRIVATE); return mSettinsSP.getString(Channel, DEFAULT_CHANNEL); } }
|
具体的项目脚本请参考:ownwell/AndroidMultiChannelBuild
博文更新
2016年9月22日
之前发现以前的脚本需要zipalign对齐,否则在有些商店会报这个错误,我们需要用到zipalign,需要老版本打好的包再进行zipalign对齐。7-9行即为新加的关键代码。
其中zg是我为zipalign
1 2 3 4 5 6 7 8 9 10 11 12
| zipped.write(src_empty_file, empty_channel_file)
zipped.close()
zipalign = os.environ.get('zg') zipAlign = "%s -v 4 %s %s" % (zipalign,target_apk, last_apk) subprocess.call(zipAlign, shell=True)
zipshell = "zip apks.zip %s"%last_dir subprocess.call(zipshell, shell=True)
|