bsdiff/bspatch是对二进制文件进行差分与合并的开源库,这个开源库的特点就是以空间换时间,打包效率比较高,但是比较耗内存,不过现在大部分机器的内存都不是问题啦,效率高才是硬道理,我们接下来盘它! bsdiff/bspatch的源码地址在这里,不过这个源码是适合在linux系统编译的,因为里面依赖到linux系统下的一些api,比如:#include <unistd.h> 、open、lseek等,这些在window系统都是找不到,为了方便,我们需要另一个修改好的适合window系统的 bsdiff/bspatch源码,这里有编译好的exe文件,可以直接用,不过我们需要给Java调用,需要把它编译成dll动态链接库才行
1、下载安装好visual studio 2019,创建新项目,选择空项目,下一步 2、配置好项目名,选择好存放位置,点创建即可 3、接着,我们把下载好的bsdiff-win-master.zip解压,从bsdiff-win目录下拷贝bsdiff.c,从bspatch-win目录下拷贝bspatch.c,都是放到源文件目录下 【注意】这里有一点需要特别留意一下,拷贝时候记得先把sln解决方案视图切换为文件夹视图,拷贝完成之后再切换回去,手动添加现有项到解决方案中,当然也可以不用来回切换视图,你直接把文件拷贝到项目目录下,再直接手动添加现有项也可以
4、添加完成之后,默认情况下的话,VS本地是找不到#include <bzlib.h>,会出现红色下划线,鼠标放上去有提示让你去下载vcpkg这个工具,然后通过这个工具下载所需的依赖库 5、下载完成之后,双击运行bootstrap-vcpkg.bat,会自动生成vcpkg.exe这个文件 6、接下来,我们可以通过这个vcpkg.exe下载bzip2,下载命令是:
vcpkg install bzip2但是,这个默认下载的是x86-windows版本,也就是32位window版本,现在一般都是64位的机器了,所以,我们指定下载64位的
vcpkg install bzip2:x64-windows慢慢等待下载完成即可 7、然后,运行以下命令应用到所有项目即可
vcpkg integrate install8、上述完成之后,#include <bzlib.h>找不到问题解决了,但是点开bsdiff.c或者bspatch.c会提示未定义标识符u_char,这个在unix系统是定义在#include<sys/types.h>头文件下,而window系统是定义在#include <winsock.h>头文件下,添加一下即可 9、然后,我们点击“本地window调试器”,运行一下,会出现以下错误:
error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS10、上述的报错,大概是说你用的那个函数可能不安全,已经是弃用的api了,假如需要去掉这个报错就在文件头添加#define _CRT_SECURE_NO_WARNINGS,这样是可以解决这个报错的,不过我们这里使用另一种解决办法,选中项目-点击工具栏的项目-属性 11、由于我们是想要编译成dll的,所以我们先顺便在上面那个界面,选择常规 - 配置类型,选择动态库(.dll)即可 12、然后点击C/C++ - 常规 - SDL检查,设置否,确定即可(注意,每次切换配置类型都需要重新配置一下这个) 13、重新点击“本地window调试器”运行一下,会出现以下错误: 14、上述错误主要是因为bspatch.c跟bsdiff.c都有main函数定义,由于对于C来说,mian函数就是执行的入口函数,是不允许有两个的,那么,我们把它分别改为bsdiff_main、bspatch_main
15、重新执行一下,发现又有出现一个新的错误:fatal error LNK1169: 找到一个或多个多重定义的符号 16、上述错误主要是因为本来这个项目作者是让bsdiff.c跟bspatch.c单独编译的,我们强制放一块,当然就会出现重复定义问题,那么咱们把提示重复定义的err函数提取出头文件跟实现的C文件 err.h
#pragma once void err(int exitcode, const char* fmt, ...);err.c
#include<stdarg.h> #include <stdio.h> #include <stdlib.h> #include "err.h" void err(int exitcode, const char* fmt, ...) { va_list valist; va_start(valist, fmt); vprintf(fmt, valist); va_end(valist); exit(exitcode); }17、选中项目,点击生成 - 重新生成解决方案,看一下日志,已经生成成功 18、不过单纯这样子的.dll,没啥用,Java是没法调用到C实现的函数,还需要定义JNI接口才行 1)把你jdk安装目录的jni.h依赖拷贝过来,由于jni.h还依赖到jni_md.h,在win32目录下,一起拷贝过去(记得不是解决方案视图,要切换到文件夹视图再拷贝) 2)重新生成解决方案看看,编译通过 19.接下来,咱们得写个jni对外接口类:main.c
#include <stdlib.h> #include "jni_md.h" #include "jni.h" extern int bspatch_main(int argc, char *argv[]); extern int bsdiff_main(int argc, char *argv[]); int main_exec(int (*mainFunc)(int, char **), int argc, char **argv) { return mainFunc(argc, argv); } JNIEXPORT jboolean JNICALL Java_com_sswl_BsDiff_make(JNIEnv *env, jclass type, jstring oldFilePath_, jstring newFilePath_, jstring patchPath_) { const char *ch[5] = {0}; ch[0] = "bspatch"; ch[1] = (*env)->GetStringUTFChars(env, oldFilePath_, 0); ch[2] = (*env)->GetStringUTFChars(env, newFilePath_, 0); ch[3] = (*env)->GetStringUTFChars(env, patchPath_, 0); int ret = main_exec(bspatch_main, 4, ch); (*env)->ReleaseStringUTFChars(env, oldFilePath_, ch[1]); (*env)->ReleaseStringUTFChars(env, newFilePath_, ch[2]); (*env)->ReleaseStringUTFChars(env, patchPath_, ch[3]); return !ret; } JNIEXPORT jboolean JNICALL Java_com_sswl_BsDiff_diff(JNIEnv *env, jclass type, jstring oldFilePath_, jstring newFilePath_, jstring patchPath_) { const char *ch[5] = {0}; ch[0] = "bsdiff"; ch[1] = (*env)->GetStringUTFChars(env, oldFilePath_, 0); ch[2] = (*env)->GetStringUTFChars(env, newFilePath_, 0); ch[3] = (*env)->GetStringUTFChars(env, patchPath_, 0); int ret = main_exec(bsdiff_main, 4, ch); (*env)->ReleaseStringUTFChars(env, oldFilePath_, ch[1]); (*env)->ReleaseStringUTFChars(env, newFilePath_, ch[2]); (*env)->ReleaseStringUTFChars(env, patchPath_, ch[3]); return !ret; }20.重新生成解决方案,编译成功 【备注】细心的小伙伴可能发现在上述日志中,其实还有个小警告
Previous IPDB not found, fall back to full compilation. 1>All 1 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.解决方案是:选中项目,依次点击项目- 属性 - 链接器 - 优化 - 链接时间代码生成,选择使用链接时间代码生成(/LTCG),重新编译就没有警告了 21.咱们打开框起来的目录看看bspatch.dll,同时还有一个依赖到的bz2.dll
1、先在jni接口对应的com.sswl包名下,创建BsDiff.java,加载生成的.dll,注意一定要跟上面的jni接口一一对应
package com.sswl; public class BsDiff { static { //这个bz2.dll是bspatch.dll所依赖到的,必须要写上来,而且必须在load bspatch.dll之前,否则会报错: //Exception in thread "main" java.lang.UnsatisfiedLinkError: ..\bspatch.dll: Can't find dependent libraries System.load("E:\\AS-workspace\\bspatch\\x64\\Release\\bz2.dll"); System.load("E:\\AS-workspace\\bspatch\\x64\\Release\\bspatch.dll"); } public static native boolean make(String oldFilePath, String newFilePath, String patchPath); public static native boolean diff(String oldFilePath, String newFilePath, String patchPath); }2、写一个类去调用一下,看看是否正常生成差分包
package com.sswl; /** * @Description: 打差分包 * @Author: jimmyliang * @CreateDate: 2020/9/4 */ public class Main { public static void main(String[] args) { BsDiff.diff("C:\\Users\\Administrator\\Desktop\\old.apk", "C:\\Users\\Administrator\\Desktop\\new.apk", "C:\\Users\\Administrator\\Desktop\\patch.apk"); // BsDiff.make("C:\\Users\\Administrator\\Desktop\\old.apk", // "C:\\Users\\Administrator\\Desktop\\new12.apk", // "C:\\Users\\Administrator\\Desktop\\patch.apk"); } }3、运行一下上述main函数,可以顺利得到一个patch.apk 4.重新用生成的patch.apk合成之前new.apk,看看是否一致 对比一下MD5, 确实完全一致,那没问题了,到此结束啦,喜欢的去github自取吧~