2019独角兽企业重金招聘Python工程师标准>>>

本文代码的原理基于 git@github.com:mcxiaoke/packer-ng-plugin.git 项目。

该项目用于向打好的 APK 包快速写入渠道信息,因为是直接在 APK 的尾巴上加数据而没有解包打包的过程,故速度较快。本文实现的是另一种需求,即后台实时给 APK 加数据,然后返回给前端下载。

比如邀请码这样没法提前写入的数据,就适合实时生成。得到的好处就是用户在注册时可以免去填邀请码这一步。

但显然这会带来带宽的压力,依旧去实现它是考虑到了实际分享应用的场景:绝大多数是在 微信、QQ 等平台内传播,都会跳转到应用宝等市场上去。因此这种实时打包流量其实很低,除非有人非要别扭着在微信里使用【在浏览器中打开】按钮来下载,否则本接口是不会被访问到的。

所以这个实现其实一点实际用处都没有 →_→

原理

APK 使用的是 ZIP 的打包格式,所以它的结尾定义了两个字段 —— Comment Len, Comment,Comment 是个变长字段,长度写在 Comment Len 里,占两个字节。

ZIP 的详细解释参见:

https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html

APK 在安装后,还会在系统里留下一个副本,安装过的应用通过读取这个副本的 Comment,就可以获得这个数据。

PackerNg

开头的 github 项目便实现了这个功能,通过 Java 和 Python 两种语言。本文重写了 Python 的版本,使其可以更改 Comment,以便实现动态加尾巴的功能。

PackerNg 的 comment 格式 是 data + data length(2 bytes) + !ZXK!。最后的 !ZXK! 是个 magic 字段,用于判断该文件是否有有效的尾巴,再通过这个字段之前的两个字节来获取偏移量,读取尾巴。

但我感觉这个字段是可选的,如果直接以偏移量结尾,我们便可以通过检查最后两个字节是不是 "\x00\x00" 来判断该文件有没有加尾巴。可能的混淆情景仅为,该文件有一个非我们加的尾巴,而这可以通过人为约束来控制。

加尾巴代码

APK_COMM_LEN = 2  # bytes
APK_PACK_FMT = '<H'  # little endian unsigned short
APK_MAGIC_WD = b'!ZXK!'
APK_MAGIC_LEN = 5
# stream 相关
APK_BASE_CHUNK_SIZE = 1024 * 1024
APK_COMM_SAFE_LEN = 1024  # 保证最后一个 apk chunk 的尾部有这么多字节# ------------------------ APK ------------------------ #
def get_apk_comment(f):"""获取 apk 包尾部的 comment 字节, 含 MAGIC"""# read magicf.seek(-APK_MAGIC_LEN, 2)tail = f.read(APK_MAGIC_LEN)if tail[-APK_COMM_LEN:] == b'\x00\x00':return b''if tail != APK_MAGIC_WD:raise ValueError("invalid apk comment format, it's not ended with MAGIC.")# read comment lenf.seek(-APK_MAGIC_LEN - APK_COMM_LEN, 2)comm_len = struct.unpack(APK_PACK_FMT, f.read(APK_COMM_LEN))[0]f.seek(-APK_MAGIC_LEN - APK_COMM_LEN - comm_len, 2)# read commentcomment = f.read(comm_len + APK_COMM_LEN + APK_MAGIC_LEN)return commentdef get_apk_comm_info(f):"""获取 apk 包尾部的 json 数据"""comment = get_apk_comment(f)if not comment:return {}comm_info = comment[:-APK_COMM_LEN - APK_MAGIC_LEN]if comm_info:return json.loads(comm_info.decode())def gen_apk_comment(info={}):"""通过字典生成 comm_len 和 comment,均为 bytes 类型,含 MAGIC"""if not info:return b'\x00\x00', b''comment = json.dumps(info).encode()comment += struct.pack('<H', len(comment))comment += APK_MAGIC_WDreturn struct.pack('<H', len(comment)), commentdef set_apk_comment(f, cur_comm_len=b'\x00\x00', new_comm_len=b'\x00\x00', new_comment=b''):"""设置 apk 包尾部的 comment 字节,全量更新"""# check cur_comm_lencur_comm_len = struct.unpack(APK_PACK_FMT, cur_comm_len)[0]if cur_comm_len == 0:f.seek(-APK_COMM_LEN, 2)if f.read(APK_COMM_LEN) != b'\x00\x00':cur_comm = get_apk_comment(f)cur_comm_len = len(cur_comm)# write newf.seek(-cur_comm_len - APK_COMM_LEN, 2)f.write(new_comm_len)f.write(new_comment)f.truncate()f.seek(0, 0)return f

流式加尾巴代码

因为 Comment 信息位于 APK 的尾部,而我司渠道包又非常多且迭代较快,我没有选择在服务器本地保存 APK 包,而是实时从 cdn 取数据流,然后在最后一段上进行更改,即实现成了一个代理。

def inv_apk_wrapper(url, inv_code):r = requests.get(url, timeout=300, stream=True)# 生成符合要求的 chunk_sizecontent_length = json.loads(r.headers['content-length'])tail_length = content_length % APK_BASE_CHUNK_SIZEif tail_length < APK_COMM_SAFE_LEN:less = APK_COMM_SAFE_LEN - tail_lengthchunk_num = int(math.ceil(content_length / float(APK_BASE_CHUNK_SIZE)))chunk_size = APK_BASE_CHUNK_SIZE - int(math.ceil(less / (chunk_num - 1.0)))else:chunk_size = APK_BASE_CHUNK_SIZEnew_apk_gen = inv_apk_generator(r.iter_content(chunk_size), inv_code)# update content-lengthnew_headers = dict(r.headers)new_headers['content-length'] = str(content_length + len(', "inv_code": "{inv_code}"'.format(inv_code=inv_code)))return Response(new_apk_gen, headers=new_headers)def inv_apk_generator(apk_iterator, inv_code):data = []while True:try:data.append(next(apk_iterator))except StopIteration:  # 确认到尾部, 添加 inv_codeif data:content = BytesIO(data[0])comm_info = get_apk_comm_info(content)comm_info['inv_code'] = inv_codecomm_len, comment = gen_apk_comment(comm_info)content = set_apk_comment(content, new_comm_len=comm_len, new_comment=comment)content.seek(0, 0)yield content.read()raise StopIterationif len(data) >= 2:yield data[0]data = data[1:]

注意这种接口因为太占 I/O,最好不要和正常业务部署在一起,或者,最好不要部署。。。

转载于:https://my.oschina.net/lionets/blog/718930

Python 后台基于 PackerNg 格式动态生成 APK 渠道包相关推荐

  1. php动态生成apk渠道包,Android自动生成渠道包

    承接上文Android应用的自动化构建,我们已经通过ANT自动构建了应用,那接下来的问题是,如何自动构建渠道包,这里强烈推荐一篇文章美团Android自动化之旅-生成渠道包. 美团提到的第三种方式,截 ...

  2. Android 渠道包 动态生成 apk 名称

    Android 渠道包 动态生成 apk 名称 低版本打包配置 buildTypes {release {minifyEnabled trueshrinkResources truezipAlignE ...

  3. Django 权限管理-后台根据用户权限动态生成菜单

    Django权限管理 实现目标: 1.管理用户,添加角色,用户关联角色 2.添加权限.角色关联权限 3.添加动作.权限关联动作 4.添加菜单.权限关联菜单 实现动态生成用户权限菜单(可设置多级菜单嵌套 ...

  4. java基于word模板动态生成word及转pdf实践

    在项目中很容易会遇到需要动态生成pdf的应用场景,其实现方式也比较多 由于项目的关系,对于这种组件性的开发方式我想的是怎么方便怎么来,怎么快就怎么来 在咨询了之前做政务系统的同学后,他们都一致推荐我使 ...

  5. Android批量打包 如何一秒内打完几百个apk渠道包

    在国内Android常用渠道可能多达几十个,如:  谷歌市场.腾讯应用宝.百度手机助手.91手机商城.360应用平台.豌豆荚.安卓市场.小米.魅族商店.oppo手机.联想乐商.中兴汇天地.华为.安智. ...

  6. C# - 基于LinkLabel可动态生成多超链接信息的自定义控件

    2019独角兽企业重金招聘Python工程师标准>>> 一.效果示意 今天设计了一个自定义控件,用于满足下面的场景: 有一句话,有多个子句组成,每一个子句都要设置对应的超链接.举个例 ...

  7. 【MFAC】基于偏格式动态线性化的无模型自适应控制

    来源:侯忠生教授的<无模型自适应控制:理论与应用>(2013年科学出版社).

  8. 【MFAC】基于全格式动态线性化的无模型自适应控制

    来源:侯忠生教授的<无模型自适应控制:理论与应用>(2013年科学出版社).

  9. python解析log文件_python解析基于xml格式的日志文件

    大家中午好,由于过年一直还没回到状态,好久没分享一波小知识了,今天,继续给大家分享一波Python解析日志的小脚本. 首先,同样的先看看日志是个啥样. 都是xml格式的,是不是看着就头晕了??没事,我 ...

最新文章

  1. Linux命令之top
  2. 普适的GPIO引脚操作方法
  3. SAP basis事务代码笔记
  4. 我的电脑不联网,很安全!黑客:你还有风扇呢
  5. rhel5 给grub 加密,亲测!
  6. 如何对付vc6的疑难杂症
  7. python怎么打出pi_随手写了段 Python,微信的地球居然转起来
  8. generator config_springboot集成mybatis+Generator代码生成
  9. rar压缩包加密以及rar密码破解的教程
  10. 公司 | 四年狂奔,少儿编程准独角兽小码王的盈利逻辑是什么?
  11. 拨号上网和宽带上网的区别分析
  12. Arcgis使用教程(十)ARCGIS地图制图之颜色样式选择设计与保存
  13. 为什么五笔输入法打字那么快,现在使用的人却越来越少了?
  14. SptingBoot构建电商基础秒杀项目时遇到的问题
  15. 推荐几款牛逼Chrome网页翻译插件,功能强大内容惊艳,务必低调使用
  16. debian10 安装ffmpeg
  17. Hive小咖,是时候穿上你的振金战衣! 与职场高阶雷神之锤High-Five了!!!
  18. wince支持多线程编程吗_WinCE 多线程下绣花机运动控制的实现
  19. intel NVME SSD 性能 P5600 P5510 P5520
  20. 【MM32F032 eMiniBoard】简易示波器

热门文章

  1. 鼻炎舒宁治过敏性鼻炎
  2. 如何用css3做一个旋转的魔方
  3. 网易数帆深度参编中国信通院《低代码发展白皮书(2022年)》
  4. 乐视贾跃亭:FF工厂不会缩水,力保FF 91按时交付
  5. 如何节省计算机系统盘的空间?(千万不要删除C:\WINDOWS\ServicePackFiles文件夹)...
  6. Oracle12C安装配置
  7. 继欧洲之后,工信部推出强硬新规,苹果如不遵从或被逐出中国市场
  8. 人类百米赛跑能跑进9秒吗
  9. 归一法的计算方法讲解_数学归一法是什么能举个具体例子吗
  10. 天龙八部TLBB系列 - 关于技能冷却和攻击范围数量的问题