Apk动态写入信息

发布时间:2021-07-31 17:25:30

曾几何时有这么一个需求,将apk(游戏或者应用)投放不同的*台倒量,服务端需要统计用户来自于某个*台的apk,由于*台是动态变化的,传统的方式,会根据不同的*台分配不同的参数,然后重新打包生成apk,这样本身来说没有问题。但是,在有限的*台数量下我们是可以做到的,假如*台数量成千上万的话,则非人力可为。所以是否有一种可以动态往apk中写入信息,而不用重新打包的方法呢?答案是肯定的:


第一种方案:apk在经过签名后,会在apk中生成一个叫META-INF的文件夹,里面存储着一些签名后的文件,经过探寻发现apk中所有文件夹(除了META-INF)都是经过签名的,所以往他们任何一个文件夹中写入数据后,apk都会被破坏,需要重新签名,然而存储签名信息的META-INF确实没有经过签名,所以可以把一些*台信息放入该目录下,然后在apk中动态获取该目录下的文件信息,达到效果(应用案列:积分墙)。


第二种方案:apk本身就是一个zip格式的文件,所以我们从zip格式下手,在zip文件的某一个特殊的区域放入我们需要的信息,而不损坏apk本身,经过探寻发现,在 zip 文件的末尾有一个 Central Directory Record 区域,其末尾包含一个 File comment 区域,可以存放一些数据,所以 File comment 是 zip 文件一部分,如果可以正确的修改这个部分,就可以在不破坏压缩包、不用重新打包的的前提下快速的给 Apk 文件写入自己想要的数据。


这里主要讲一下第二种方案:


comment 是在 Central Directory Record 末尾储存的,可以将数据直接写在这里,下表是 header 末尾的结构。



从表中可以看到定义 comment 长度的字段位于 comment 之前,当我们从文件最后开始读取时,由于 comment 数据是不确定的,我们无法知道它的长度,从而也无法从 zip 中直接获取 Comment length。


这里我们需要自定义 comment,在自定义 comment 内容的后面添加一个区域储存 comment 的长度,结构如下图。



这里可以将一个固定的结构写在 comment 中,然后根据自定义的长度分区获取每个部分的内容,还可以添加其它数据,如校验码、版本等。




1、服务端将数据写入apk的comment区域:


这一部分可以在本地或服务端进行,需要定义一个长度为 2 的 byte[] 来储存 comment 的长度,直接使用 Java 的 api 就可以把 comment 和 comment 的长度写到 Apk 的末尾,代码如下:



/**
*
* @param file
* 需要写入数据的apk文件
* @param comment
* 需要写入的数据
*/
public static void writeApk(File file, String comment) {
ZipFile zipFile = null;
ByteArrayOutputStream outputStream = null;
RandomAccessFile accessFile = null;
try {
zipFile = new ZipFile(file);
String zipComment = zipFile.getComment();
// 判断comment区域是否已经有数据了
if (zipComment != null)
return;
byte[] byteComment = comment.getBytes();
outputStream = new ByteArrayOutputStream();
// 将数据写入输出流
outputStream.write(byteComment);
// 紧接着写入数据大小
outputStream.write(short2Stream((short) byteComment.length));

byte[] data = outputStream.toByteArray();
accessFile = new RandomAccessFile(file, "rw");
// 跳到comment区域
accessFile.seek(file.length() - 2);
// 先写入数据大小
accessFile.write(short2Stream((short) data.length));
// 写入数据
accessFile.write(data);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (zipFile != null)
zipFile.close();
if (outputStream != null)
outputStream.close();
if (accessFile != null)
accessFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

/**
* short转换成字节数组
*
* @param data
* @return
*/
private static byte[] short2Stream(short data) {
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putShort(data);
buffer.flip();
return buffer.array();
}




2、apk中读取comment数据:


代码如下:



/**
* 读取comment区域数据
*
* @param context
* @return
*/
private static String readApk(Context context) {
// 获取文件路径
File file = new File(context.getPackageCodePath());
byte[] bytes = null;
RandomAccessFile accessFile = null;
try {
accessFile = new RandomAccessFile(file, "r");
long index = accessFile.length();

bytes = new byte[2];
// 获取comment文件的位置
index = index - bytes.length;
accessFile.seek(index);
// 获取comment中写入数据的大小byte类型
accessFile.readFully(bytes);
// 将byte转换成大小
int contentLength = stream2Short(bytes, 0);
// 创建byte[]数据大小来存储写入的数据
bytes = new byte[contentLength];
index = index - bytes.length;
accessFile.seek(index);
// 读取数据
accessFile.readFully(bytes);
return new String(bytes, "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (accessFile != null) {
try {
accessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}

/**
* 字节数组转换成short
*
* @param stream
* @param offset
* @return
*/
private static short stream2Short(byte[] stream, int offset) {
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.put(stream[offset]);
buffer.put(stream[offset + 1]);
return buffer.getShort(0);
}
3、结论


通过修改 comment 将数据传递给 App 的方案是可行的,由于是修改 Apk 自有的数据,并不会对 Apk 造成破坏,修改后可以正常安装。


这种方案不用重新打包 Apk,并且在服务端只是写文件的操作,效率很高(毫秒级别),可以适用于动态生成 Apk 的场景。


4、参考


https://github.com/mcxiaoke/packer-ng-plugin
https://github.com/seven456/MultiChannelPackageTool



相关文档

  • 1958 Problem D abc
  • 如何快速地从银行获得贷款
  • 中国古代爱国名言
  • 叫小度小度怎么不回应
  • 判断结构体是否为空_数据结构与算法系列之链表操作全集(一)(GO)
  • 帮助孩子提高记忆能力的方法
  • 形容女人手漂亮的句子
  • 无人手机有电怎么办
  • 眼部烧伤的急救措施有哪些
  • 我的梦想与追求演讲稿3篇
  • 2017初级经济师金融专业考点:货币流通的概念和形式
  • 人际关系句子
  • 基于草图的图像检索的文献综述
  • Kafka 基础架构以及原理
  • Android Studio开发学习(十三)??Fragment
  • 《我这一辈子》
  • HAL库的学习 ??内部EEPROM的使用
  • 本地主机客户端访问不了VMware虚拟机里的服务器
  • 英语介词的用法口诀
  • 有关于谐音的歇后语大全
  • APP测试常见面试题及ADB常用命令
  • 关于开学了作文600字5篇
  • Ubuntu18.04,CUDA 10.0,cuDNN 7.4,tensorflow1.13.1环境配置
  • “深度学习”促深度反思
  • SAP S4/HANA BP屏幕增强添加自定义字段(BDT方式)
  • 最新元宵节谜语(本站会员原创)
  • 医生工作总结
  • 用温柔的心态去生活散文
  • 里斯本大学研究生留学费用
  • 2020年普通高中课改实验省美术学科远程培训工作总结
  • 猜你喜欢

  • 结膜炎眼睛肿了怎么办?结膜炎眼睛红几天能好?
  • 2017年广东教育教学成果奖特等奖项目共9项
  • 电脑电源各线电压
  • 【经典汇编】2019-2020年湖南省长沙市三模:长沙市2019届高三第三次模拟考试数学(理)试题-含答案
  • 广东省德庆县孔子中学高考化学选择题专项训练三物质的性质与用途电化学
  • 深圳市香深贸易有限公司(企业信用报告)- 天眼查
  • 驻马店市九星机械有限公司企业信息报告-天眼查
  • 四年级想象作文:我想有只神笔200字
  • 中考物理总复*第13课时光的折射透镜成像及其应用光的
  • 吊兰的含义是什么
  • 狗狗咳嗽的很厉害怎么办
  • 汽车车身焊接夹具设计
  • 2019年党建工作计划表 2019年秋季学校党建工作计划 精品
  • 贵州中禾汽车贸易有限公司中曹分公司企业信用报告-天眼查
  • 污水处理厂设计说明与计算书 secret
  • 2010年南昌大学马克思主义学院思想政治教育专业研究生入学考试马克思主义哲学试题
  • 福建省南*市浦城县八年级生物上学期期中试题(扫描版) 新人教版
  • 我国安全生产事故频发的原因
  • 品尝美味作文600字(高分作文)
  • 滁州开发区华绿草坪苗木园艺经营部(企业信用报告)- 天眼查
  • 【文库新品】2019-2020学年度第一学期★精选★ 青岛版三年级上册数学期末检测卷及答案(5)--推荐练*
  • 【精编范文】我的中国梦手抄报资料:大的梦想-实用word文档 (2页)
  • import numpy出错的一个问题
  • 温州市苍南县六校2019年3月中考第一次联合模拟数学试卷(有答案)
  • 苏州博斯宇五金有限公司企业信用报告-天眼查
  • 中耳炎护理查房ppt模式模板
  • 兰州市永登县2015-2016学年八年级下数学竞赛试题含答案
  • 精编高考30天冲刺家长会课件
  • 核外电子排布的初步知识(_一〕
  • 常规水稻杀虫剂混用配方
  • 技术支持&&用户隐私协议
  • 哈国家石油天然气公司收购曼吉斯套油气公司的股份
  • 怎么设苹果手机id密码忘了怎么办
  • 第一章 SQL Server2000概述.ppt.Convertor
  • 浙江鼎清环境检测技术有限公司(企业信用报告)- 天眼查
  • 暖心爱情说说短语 只要能收获甜蜜荆棘丛中也会有蜜蜂忙碌的身
  • 联通光纤猫接路由器上不了网
  • 六年级体育课件青春期生长发育特点 全国通用(共19张ppt)
  • 郑州和顺机械设备租赁有限公司(企业信用报告)- 天眼查
  • 广东省德庆县孔子中学高考化学选择题专项训练三物质的性质与用途电化学
  • 【优质部编】2019-2020学年高一语文上学期期中试题人教版新版
  • 推荐-人教版高中英语必修四Module 3《Unit 4 Astronomy the science of the stars》(reading)ppt课件
  • 电脑版