sisong / apkdiffpatch Goto Github PK
View Code? Open in Web Editor NEWa C++ library and command-line tools for Zip(Jar,Apk) file Diff & Patch; create minimal delta/differential; support Jar sign(apk v1 sign) & apk v2,v3 sign .
License: MIT License
a C++ library and command-line tools for Zip(Jar,Apk) file Diff & Patch; create minimal delta/differential; support Jar sign(apk v1 sign) & apk v2,v3 sign .
License: MIT License
我们都是**人,那我就不写英语了……xD
我发现 ApkNormalized 有两点问题:
我以release
中已编译好的hdiffpatch_v3.0.8_ApkDiffPatch_v1.3.1_bin_beta.zip
为例
第一个问题出现在 Win64 版本ApkNormalized中,处理到最后会报错
UnZipper_fileData_decompressTo(srcZip,srcIndex,&stream) ERROR!
ApkNormalized result file check ERROR!
Win32版本和Linux64版本的执行结果相同,也不报错,其他版本没测试
我还没找出出问题的地方,不过我觉得这个问题不太大…应该是不同平台的数据类型长度变化导致的,改用定长类型int8_t
,uint32_t
等等应该就可以解决了
第二个问题 的现象就是ZIP文件损坏
用十六进制编辑器查看normalized的zip文件,会发现在PK头部之前多出来了4-6个空字节
把那几个00字节删掉之后,zip文件也不正确,但是具体哪里不正确我也没找出来(我不熟悉zip结构)
反正我用7-zip打开压缩包后无法修改或删除文件(7-zip提示“只读”,那意味着文件结构损坏,只能够读取,不能修改)
要求有apkV2签名
测试当前实现和研究技术说明文档,并处理可能的兼容性问题:
https://source.android.google.cn/security/apksigning/v3
Released newZip :=ApkNormalized(newZip) before ZipDiff这样得出来的newZip试了好多遍还是MD5不一致,是什么原因呢?
win64命令步骤:
1、ApkNormalized.exe new.apk outnew.apk;
2、ZipDiff.exe old.apk outnew.apk patch.diff;
3、ZipPatch.exe old.apk patch.diff outToNew.apk;
但是这样得出来的apk和new.apk或者outnew.apk这三个的MD5都不一致
您好,请问客户端进行patch过程的返回码是否如下含义呢?
PATCH_SUCCESS=0, //0. 成功
PATCH_OPENREAD_ERROR, //1. 输入流打开失败
PATCH_OPENWRITE_ERROR, //2. 输出流打开失败
PATCH_CLOSEFILE_ERROR, //3. 关闭文件失败
PATCH_MEM_ERROR, //4. 运存空见不足(malloc失败)
PATCH_HPATCH_ERROR, //5. hpatch执行过程出错
PATCH_HDIFFINFO_ERROR, //6. diff文件信息错误(文件损坏?
PATCH_COMPRESSTYPE_ERROR, //7. 解压缩出错
PATCH_ZIPPATCH_ERROR, //8. (不确定)zip输出流写入异常
PATCH_ZIPDIFFINFO_ERROR, //9. zip格式下的diff文件读入失败(不可读)
PATCH_OLDDATA_ERROR, //10. 旧文件(旧apk)异常,跟diff文件里对不上
PATCH_OLDDECOMPRESS_ERROR, //11. 旧文件(旧apk)解压异常
PATCH_OLDSTREAM_ERROR, //12. 旧文件(旧apk)输入流异常
PATCH_NEWSTREAM_ERROR, //13. 新文件(新apk)输出流异常
谢谢~~~
ps: 广告,我正在开发一个不需要重新签名的方案sfpatcher,
PC上用你的重新签名后的2个apk测试:diff用时23.49秒创建补丁:486325字节;多线程执行patch打补丁时间0.39秒。
Originally posted by @sisong in #74 (comment)
当前每个渠道必须要单独生成其补丁包;
库提供一个工具,允许生成某种基础补丁包,并提供另外一个工具允许快速修改该补丁包来生成适合不同渠道的补丁包;
你好,请问ZipDiff的空间复杂度为多少呢?好像只看到了ZipPatch的
now defualt used lzma,can edit define to select zlib, and must rebuild code;
change to: ZipDiff cmline can select lzma or zlib;
在安卓使用so文件,对于patch方法:
public static native int patch(String oldApkPath,String patchFilePath,String outNewApkPath,
long maxUncompressMemory,String tempUncompressFilePath,int threadNum);
1、请问 maxUncompressMemory、tempUncompressFilePath这两个参数是用于解压的时候使用的临时最大存储空间和该存储空间的路径吗?对于这个的空间分配和进行oldZip,diffZip,newZip文件的大小有最优的映射吗?这个问题和上面的那个线程数量的问题类似。
2、我在使用的合成1G的文件的时候分配了1024 *10空间,但是在合成的整个过程中(运行中和运行后)并没有看到我指定的路径下面有该临时文件的生成,这是什么原因呢?
apkV2Sign的检查必须明确指明才能跳过
理论上,只要当前zlib库(或者收集的其他常见兼容库)能够匹配上部分newApk的压缩编码输出(反算出压缩参数),就能降低部分补丁大小;
old apk 分为是否V2签名的情况分别处理(无v2签名的不能够过于信任其不会简单变动);
new apk对每个文件区分匹配压缩参数;
补丁大小最差情况接近HDiffPatch或BsDiff的结果,最好结果接近ApkDiffPatch中ZipDiff的结果;
patch速度因为压缩参数的原因,可能会比较慢;
有该残留但又没有v2签名块的阻止进行diff;
需要处理签名文件格式,产生耦合
比如两个apk
apk1.0
apk2.0 diff文件 diff1-2
./ZipPatch apk1.0 diff1-2 newapk2.0
得到的 newapk2.0 和 apk2.0 MD5 是一样的吗?
have 4 byte code error; caused by align after resign?
当前默认引用了oldZip中的所有文件,不利于patch阶段的执行性能;
优化:从中去除对newZip没有太多参考价值的文件;减少patch时的解压文件数;
I test with build-tools\31.0.0\apksigner
, is ok
only patch code
线上环境有一定概率出来这个问题,看起来是空指针,野指针的问题,具体的堆栈为:
0 #00 pc 000000000001ac5c /xxx/libapkpatch.so
1 #01 pc 000000000001ac7c /xxx/libapkpatch.so
2 #02 pc 000000000001ac48 /xxx/libapkpatch.so
3 #03 pc 000000000001a268 /xxx/libapkpatch.so
4 #04 pc 000000000001a0ec /xxx/libapkpatch.so
5 #05 pc 000000000001a09c /xxx/libapkpatch.so
6 #06 pc 0000000000012ec8 /xxx/libapkpatch.so
7 #07 pc 000000000001451c /xxx/libapkpatch.so
8 #08 pc 00000000000144a0 /xxx/libapkpatch.so
9 #09 pc 0000000000018404 /xxx/libapkpatch.so
10 #10 pc 0000000000015fa8 /xxx/libapkpatch.so
11 #11 pc 0000000000016424 /xxx/libapkpatch.so
12 #12 pc 00000000000074ac /xxx/libapkpatch.so (Java_com_github_sisong_ApkPatch_patch+208)
@sisong 请问遇到过这个问题吗?或者能查到原因吗?
使用ndk将patch功能(只需要包含相关部分的代码)编译成*.so来使用,类似jni方式;
大佬你好,
我在libs里添加了armea-v7a/libapkpatch.so.加载so文件也成功。
在Activity oncreate()调用
ApkPatch.patch(oldApkPath, oldDiffpath, newApkpath,maxUncompressMemory,tempUncompressFilePath,1)
参数:
String root = Environment.getExternalStorageDirectory().getAbsolutePath();
String oldApkPath = root+"/1/old.apk";
String oldDiffpath = root+"/1/add.diff";
String newApkpath = root+"/1/new.apk";
long maxUncompressMemory = 810241024;//8mb
String tempUncompressFilePath=root+"/360/";
没有生成新包,返回值1。
我在Android上使用patch合成一个new.apk,但是这个new.apk却在android中读取不出来所有的ZipEntry:
使用如下程序读取不出来:
ZipFile zipFile = new ZipFile(filePath);
InputStream inputStream = new BufferedInputStream(new FileInputStream(filePath));
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(filePath));
ZipEntry zipEntry;
HashMap<String, InputStream> inputStreamHashMap = new HashMap<>();
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.isDirectory()) {
continue;
} else {
String name = zipEntry.getName();
Log.d(TAG, "-------" + name);
}
请问是什么原因呢?
您好,请问:
- 如果我在对APK进行差分和合成的过程中是否能够指定对某些文件进行忽略呢?
- 如果没有这个参数的话我是否能够在源码中能够有对应的处理的逻辑进行修改源码呢?例如我需要忽略APK文件中的Zip Comment的内容,或者忽略META-INF文件夹中指定的文件。有办法可以做到吗?
感谢~
对速度和补丁大小应该没有影响,只是代码重构
如题
Hi @sisong,
I am trying to create diff for android apk and patch. I want to generate the diff in my local macos machine.
Please help me to do the above or any lead would be appreciated. Thanks in advance.
这对某些极限环境的编译和使用可能有用处
工程量较大
if your apk used Apk v2 sign(or Apk v3 sign), Released newZip := AndroidSDK#apksigner(ApkNormalized(newZip)) before ZipDiff;
AndroidSDK#apksigner 是指的哪个方法吗?
出现原因:当apk中的assets中存在一个空文件(可以自己随便创建一个文本为空的文件),这时候使用ApkNormalized进行正则化之后,再使用7 zip工具进行解压,会出现错误,错误日志为:数据错误assets/nullfile
请问出现该错误的原因是什么呢?您那边方便分析一下吗?
备注:自己随便造一个apk都会出现该错误
根据readme中看到空间复杂度为 4*解压流内存 + 引用旧文件解压内存 + O(1):
请问一般解压流大概是多少的呢?比如对1G的文件进行操作大概会占用多少内存呢?
我在Android上使用的时候进行合成一个1G的文件竟然没有发现占用的内存没有明显的变化,感到很奇怪
在readme中看到ZipPatch在编写zip文件时支持多线程并行压缩模式,只是会需要更多的内存;
请问对于这个线程数量和需要patch的文件大小是否有一个最优的映射呢?例如对于1G文件的操作大概分配多少线程能够达到空间和时间的最优平衡呢?
simonking200:
您好,首先非常感谢您能够分享HDiffPatch这个非常棒的库,我现在使用的是HDiffPatch,目前我也遇到的这样的实际问题,一般渠道会从新组包生成很多子渠道。组包过程原包又是从安装目录中获取的,这时候获取的包已经是子渠道包了,所以这个Patch理论上应该是不成功的,不知道这个问题如何解决,还请解惑
Originally posted by @simonking200 in #12 (comment)
hi,同一份old.apk,new.apk数据,我在PC上ZipDiff ZipPatch 操作测试一切正常,可在android设备安装old.apk,获取diff数据后进行合并每次都返回5,麻烦能帮忙分析下这个code产生的原因吗(已测试将android设备下载的diff文件拷贝到PC进行ZipPatch,结果正常)
ApkPatch.patch(String oldApkPath, String patchFilePath, String outNewApkPath, long maxUncompressMemory, String tempUncompressFilePath, int threadNum)
参数如下:
oldApkPath: /data/app/package-PkGyHOmlXf2mGU8Ln6LzrQ==/base.apk
patchFilePath: /storage/emulated/0/Android/data/package/cache/patch.diff
outNewApkPath: /storage/emulated/0/Android/data/package/cache/patch.apk
maxUncompressMemory: 1024*512
tempUncompressFilePath: /storage/emulated/0/Android/data/package/cache/patch.temp
threadNum: 3
zip_diff: used lzma recompress zip file to lzma data;
zip_patch: decompress lzma data & compress to zip file;
您好,如您所说:if your need newZip(patch result) file byte by byte equal, Released newZip := ApkNormalized(newZip) before run ZipDiff, AND You should not modify the zlib version (unless it is certified compatible);
if your apk(or jar) file used Jar sign(Apk v1 sign), is same as zip file;
if your apk used Apk v2 sign(or Apk v3 sign), Released newZip := AndroidSDK#apksigner(ApkNormalized(newZip)) before ZipDiff;
更有利于扩展
可能会使用更多内存;
您好,如您所说:if your need newZip(patch result) file byte by byte equal, Released newZip := ApkNormalized(newZip) before run ZipDiff, AND You should not modify the zlib version (unless it is certified compatible);
if your apk(or jar) file used Jar sign(Apk v1 sign), is same as zip file;
if your apk used Apk v2 sign(or Apk v3 sign), Released newZip := AndroidSDK#apksigner(ApkNormalized(newZip)) before ZipDiff;
问题:
1、我这边需要在v2签名的情况下,在apk中进行了patch之后在进行ApkNormalized吗?
2、在v1签名的情况下,是需要对old.apk,new.apk都进行了ApkNormalized之后,在apk合成的之后中需要进行ApkNormalized吗?
3、如果上述问题是需要在apk合成的之后进行ApkNormalized的话,那这个过程是在android手机中进行的,那有这部分的库吗?
thx~~~
输出部分日志如下:
oldZip :"2_3_nor.apk"
newZip :"1_2_nor.apk"
outDiff :"patch.diff"
NOTE: oldZip found JarSign(ApkV1Sign)
NOTE: newZip maybe normalized
NOTE: newZip found JarSign(ApkV1Sign)
其中2_3_nor.apk和1_2_nor.apk都是进过normalized之后的文件,但是只有一个newZip maybe normalized,我看源码中有如下源码:
static bool checkZipInfo(UnZipper* oldZip,UnZipper* newZip){
bool isOk=true;
if (oldZip->_isDataNormalized)
printf(" NOTE: oldZip maybe normalized\n");
if (UnZipper_isHaveApkV1_or_jarSign(oldZip))
printf(" NOTE: oldZip found JarSign(ApkV1Sign)\n");
if (UnZipper_isHaveApkV2Sign(oldZip))
printf(" NOTE: oldZip found ApkV2Sign\n");
if (UnZipper_isHaveApkV3Sign(oldZip))
printf(" NOTE: oldZip found ApkV3Sign\n");
if (newZip->_isDataNormalized)
printf(" NOTE: newZip maybe normalized\n");
if (UnZipper_isHaveApkV1_or_jarSign(newZip))
printf(" NOTE: newZip found JarSign(ApkV1Sign)\n");
bool newIsV2Sign=UnZipper_isHaveApkV2Sign(newZip);
if (newIsV2Sign)
printf(" NOTE: newZip found ApkV2Sign\n");
if (UnZipper_isHaveApkV3Sign(newZip))
printf(" NOTE: newZip found ApkV3Sign\n");
if (newIsV2Sign&(!newZip->_isDataNormalized)){
//maybe bring apk can't install ERROR!
printf(" ERROR: newZip not Normalized, need do "
"newZip=AndroidSDK#apksigner(ApkNormalized(newZip)) before running ZipDiff!\n");
isOk=false;
}
if ((!newIsV2Sign)&&UnZipper_isHaveApkV2orV3SignTag_in_ApkV1SignFile(newZip)){
//maybe bring apk can't install ERROR!
printf(" ERROR: newZip fond \"X-Android-APK-Signed: 2(or 3...)\" in ApkV1Sign file, need re sign "
"newZip:=AndroidSDK#apksigner(newZip) before running ZipDiff!\n");
isOk=false;
}
printf("\n");
return isOk;
}
按照源码应该是有NOTE: oldZip maybe normalized输出的才是,这是什么原因呢?
ZipDiff with compress plugin: "lzma"
ZipDiff same file count: 1184 (all 1977)
diff new file count: 793
ref old file count: 1778 (all 1833)
ref old decompress: 19 file (16931246 byte!)
run hdiffz:
oldDataSize : 25320411
newDataSize : 70138231
(used one lzma dictSize: 393216 (input data: 300626))
(used one lzma dictSize: 3145728 (input data: 2414172))
(used one lzma dictSize: 3145728 (input data: 2934300))
(used one lzma dictSize: 4194304 (input data: 49753616))
diffDataSize: 25113120
diff time: 34.109 s
hpatchz check hdiffz result ok!
patch time: 1.297 s
(used one lzma dictSize: 6144 (input data: 4236))
ZipDiff size: 25113495
ZipDiff time: 40.047 s
run ZipPatch:
check ZipPatch result Same Like ok! (but not Byte By Byte Equal)
patch time: 5.078 s
问题一:
请问其中4个dictSize:
(used one lzma dictSize: 393216 (input data: 300626))
(used one lzma dictSize: 3145728 (input data: 2414172))
(used one lzma dictSize: 3145728 (input data: 2934300))
(used one lzma dictSize: 4194304 (input data: 49753616))
这4个的大小是怎么确定的?和什么有关?我这边是否可以自行定义大小?
问题二:
请问在出现了4个dictSize并显示diff完成之后还出现了:
(used one lzma dictSize: 6144 (input data: 4236))
这个的作用是什么呢?是否会计入patch时候的空间复杂度计算中?大小是由什么决定的呢?
我有看到issues #17,但是上面提供的mk文件跟现在的项目根本对不上,不知道具体指向的是哪些文件
手机上怎么签名呢?需要秘钥签名文件内置到Apk中吗
你好:
我下了hdiffpatch_v3.0.8_ApkDiffPatch_v1.3.2_bin_beta 这个包,
运行 ZipDiff 和 ZipPatch 之后,V2签名的apk 不能正常安装。但是
我试了
hdiffz.exe
hpatchz.exe
可以!
所以想问下, 这两个文件是怎么通过源码编译生成的?
需要调查,并权衡是否处理
ApkDiffPatch 针对的场景中,都是不太大的zip包文件;
支持的必要性不强;
Or use some other tools for that purpose?
请问下,重新签名后patch反而变大了。没有重新签名直接生成的patch是400多k,重新签名后生成的反而是1.2M。比google的archive-patcher 的patch包大,与您这边的测试结果好像不符合。使用的是v2签名的Apk
Originally posted by @super-h-c in #73 (comment)
Hello,
I'm trying to use ZipDiff for the Twitter app.
When downloading for an arm64 device from the German Google Play Store, this comes in 5 .apk files:
base.apk
split_config.arm64_v8a.apk
split_config.de.apk
split_config.en.apk
split_config.xxhdpi.apk
Unfortunately, if I apply AndroidSDK#apksigner(ApkNormalized(AndroidSDK#apksigner(newZip)))
to split_config.arm64_v8a.apk
I get the following error when installing with adb install-multiple *.apk
:
adb: failed to finalize session
Failure [INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2]
Installing works without problems if I only apply AndroidSDK#apksigner(newZip)
to split_config.arm64_v8a.apk
and AndroidSDK#apksigner(ApkNormalized(AndroidSDK#apksigner(newZip)))
tothe other .apk files.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.