【前言】
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 install
8、上述完成之后,#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_WARNINGS
10、上述的报错,大概是说你用的那个函数可能不安全,已经是弃用的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
二、调用bspatch.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自取吧~