1、自动化打包方案
1)友盟多渠道多渠道打包
2)gradle productFlavors系统的条件编译
3)美团打包
4)APK文件注释写入渠道号
2、各打包方案简介
1)友盟多渠道多渠道打包(window 绿色版,gradle版本 现在过时)
原理:拆包分解apk,修改AndroidManifest.xml二进制文件后,再重新打包
耗时:较短(多渠道打包时,避免了多次dex过程,aapt过程)
渠道号保存方式:保存在AndroidManifest.xml 文 件 meta 数据中
不足:较难保证AndroidManifest.xml二进制修改正确,可能会出现兼容性问题;多渠道apk 命名不能预定,需要额外处理重命名apk文件。
2).gradle productFlavors系统的条件编译
原理: 每个渠道包,都走完整个打包(appt、混淆、dex,签名)流程
耗时:长(跟渠道包量成正比)
渠道号保存方式:保存在AndroidManifest.xml 文 件 meta 数据中
3)美团方案
原理:在APK文件的META-INF目里增加渠道文件
耗时:快(多个渠道包,只有一次完整打包流程)
渠道号保存方式:mtchannel_yybcpd文件名中
4)APK文件注释写入渠道号
原理:Android 使用的apk包的压缩方式是zip,与zip有相同的文件结构,正确写入 File Comment 部分,不破坏压缩包、不用重新打包前提下,写入自己想要的数据。
耗时:快
渠道号保存方式:保存在zip 注释中。
3、android 打包流程和签名机制:
1)android 打包流程
ps:代码混淆在dx 生成dex 之前进行。
2)签名机制
(1)生成MANIFEST.MF文件 遍历apk包中的所有文件(entry),对非文件夹非签名 文件的文件,逐个生成SHA1的数字签名信息,再用 Base64进行编码
(2)生成CERT.SF文件 对生成的Manifest,使用SHA1-RSA算法,用私钥进 行签名
(3)生成CERT.RSA文件 对CERT.SF文件做签名,内容存档(公钥、所采用的加 密算法)到中CERT.RSA
ps:从第一步规则源码,/build/tools/signapk/SignApk.java ,关键源码代码如下:
从源码中,可以看出空文件夹免签名,并看不出来美团方案怎样规避签名机制。
从apk安装过程签名校验关键类:PackageParser.java collectCertificates()方法:
从上图可以看空文件夹和META-INFO文件夹中的文件、AndroidManifest.xml免签名检验的,美团就是通过在META-INFO文件夹
新增渠道号文件,实现多渠道打包,当然不需要进行zipalign操作(通过zipalign -c -v 4 application.apk检验包是否已经对齐)。
4、python实现方案3和方案4(python2.7 windos环境):
1)思路:1.执行gradle 打包命令
2.监听gradle 打包进程,打包结束后
方案3:拷贝apk,对apk进行zipfile 文件操作,在META-INFO文件夹中新增多渠道文件。
方案4:拷贝apk,对apk添加zip comment.
2)python代码实现:
(1)执行gradle 打包命令
cmd = 'gradle assembleInnerRelease ' #假设 builde.gradle productFlavors 中配置inner pro = subprocess.Popen(cmd,shell = True)
(2)监听进程(做了简单的封装)
#监听进程 用来监听gradle 打包进程,后文微信资源混淆进程等 def func_listen_process(Process,listener): if listener == None or not isinstance(listener,ProcessListener): print 'func_listen_process: ' + process + ' listener is null or not processListener interface' return listener.start() while (True): time.sleep(1) retCode = subprocess.Popen.poll(pro) if retCode is not None: listener.end() break else: listener.doing() #抽象类加抽象方法就等于面向对象编程中的接口 from abc import ABCMeta,abstractmethod #定义接口 class ProcessListener: __metaclass__ = ABCMeta #指定这是一个抽象类 @abstractmethod #抽象方法 def start(self): pass @abstractmethod #抽象方法 def doing(self): pass @abstractmethod #抽象方法 def end(self): pass
(3)监听gradlle打包进程
#监听gradle 打包进程 func_listen_process(pro,GradlePackProcessListener()) #gradle打包进程监听器 class GradlePackProcessListener(ProcessListener): curTime = 0 apkCount = 0 def __init__(self): return def start(self): self.curTime = time.time() def doing(self): return def end(self): apkCount = func_channelsReleasePack(workSpace) self.curTime = time.time() - self.curTime; print "\nandroid channels pack cost total time : " + str(int(self.curTime)) +'s , ' + str(apkCount) + ' apks' #多渠道打正式包 channelsFile.txt 每行一个渠道号 def func_channelsReleasePack(workSpace): apkPath = workSpace + '/build/outputs/apk' channelsFile = open(workSpace + '/channelsFile.txt', "r") apks = [] countApk = 0 global version print u"\nstart channle release pack\n" while True: channelName = channelsFile.readline() channelName = channelName.strip('\n').strip('\t') if channelName: channelApkName = apkPath + '/' + apkPrefix + '_' + channelName + '_' + version +'.apk' shutil.copyfile(apkPath+'/' + apkPrefix + '_inner_' + ''+ version +'.apk',channelApkName) apks.append(apkPrefix + '_' + channelName + '_' + version +'.apk') zipped = zipfile.ZipFile(channelApkName, 'a', zipfile.ZIP_DEFLATED) empty_channel_file = "META-INF/channel_{channel}".format(channel = channelName) newChannelFile = open(apkPath+'/channel_'+ channelName, 'w') newChannelFile.close() zipped.write(apkPath+'/channel_'+ channelName,empty_channel_file) zipped.close(); os.remove(apkPath+'/channel_'+ channelName) print '---> /' + apkPrefix +'_' + channelName + '_' + version +'.apk' countApk = countApk + 1; else: channelsFile.close() #删除出多余的文件 func_delFiles(apkPath,apks) break print u"\nend channle release pack" #func_zipalignApks(apks) return countApk;
(4)获取应用获取渠道号(这里只给出方案3渠道号获取方法,方案四渠道号获取方法见一种为 Apk 动态写入信息的方案):
/** * 获取友盟渠道 * #app安装后,拷贝保存在/data/app/目录下 * @return */ public static String getChannel(Context mContext) { ApplicationInfo appinfo = mContext.getApplicationInfo(); String sourceDir = appinfo.sourceDir; String ret = ""; ZipFile zipfile = null; try { Log.d("sihaixuan", "app source dir : " + sourceDir); zipfile = new ZipFile(sourceDir); Enumeration<?> entries = zipfile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); String entryName = entry.getName(); Log.d("sihaixuan",entryName + ""); if (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 ""; } }
(5)打包测试
5、总结
ps:gradle 定义 task 也可以实现以上方案,新一代Android渠道打包工具:1000个渠道包只需要5秒实现了apk 注释添加渠道号
github:AndroidPackCi
参考资料:
新一代Android渠道打包工具:1000个渠道包只需要5秒
Android源码查看网址http://androidxref.com/
来源:https://www.cnblogs.com/sihaixuan/p/5320840.html