核心的内容是bsdiff和bspatch
源码根目录/bootable/recovery/applypatch下找到,bsdiff官网同样也是可以的,编出来的二进制文件可以在源码根目录out/host/linux-x86/bin下找到
bsdiff
bsdiff oldfile newfile patchfile
bspatch
bspatch oldfile newfile patchfile
比如用ES文件浏览器的3.1.7.1和3.1.8版本来做例子
./bsdiff es_3.1.7.1.apk es_3.1.8.apk diff.patch
./bspatch es_3.1.7.1.apk es_file.apk diff.patch
然后查看生成的es新的apk和最新版本的md5比较,发现一模一样,说明成功
md5sum es_3.1.8.apk
c67a6ceb5c2da587da352be9b226a5df es_3.1.8.apk
md5sum es_file.apk
c67a6ceb5c2da587da352be9b226a5df es_file.apk
这样我们Android客户端只需要对bspatch封装一个jni的库出来就可以通过旧的apk和差分文件合成新的apk,然后进行安装升级
移植过程:
1.下载bsdiff源文件
http://www.daemonology.net/bsdiff/,目前最新为4.3,下载下来以后发现里面包含这些文件
├── bsdiff.1
├── bsdiff.c
├── bspatch.1
├── bspatch.c
└── Makefile
我们客户端sdk需要的是bspatch.c
创建jni文件夹,拷贝bspatch.c文件,创建Android.mk文件
目录如下:
jni/
├── Android.mk
└── bsdiff
└── bspatch.c
Android.mk的内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
bsdiff := bsdiff
LOCAL_C_INCLUDES += \
$(LOCAL_PATH)/$(bsdiff)/
bsdiff_source_list := $(wildcard $(LOCAL_PATH)/$(bsdiff)/*.c)
my_source_list := $(wildcard $(LOCAL_PATH)/*.c)
LOCAL_SRC_FILES += \
$(bsdiff_source_list:$(LOCAL_PATH)/%=%) \
$(my_source_list:$(LOCAL_PATH)/%=%)
LOCAL_MODULE := bspatch
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
ndk-build编译一下试试火气,我去,果然不出所尿
jni/bsdiff/bspatch.c:31:19: fatal error: bzlib.h: No such file or directory
原来差bzlib
赶紧的下
此为搜索bzlib官网和下载解压时间
============================================================================================================
============================================================================================================
好了,下载下来了(http://www.bzip.org/)
解压发现文件好多,是不是都需要咧,慢慢来试,反正bzlib.h bzlib.c肯定需要,先创建bzlib文件夹,拷贝这两个文件进去,对应修改Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
bzlib := bzlib
bsdiff := bsdiff
LOCAL_C_INCLUDES += \
$(LOCAL_PATH)/$(bzlib)/ \
$(LOCAL_PATH)/$(bsdiff)/
bzlib_source_list := $(wildcard $(LOCAL_PATH)/$(bzlib)/*.c)
bsdiff_source_list := $(wildcard $(LOCAL_PATH)/$(bsdiff)/*.c)
my_source_list := $(wildcard $(LOCAL_PATH)/*.c)
LOCAL_SRC_FILES += \
$(bzlib_source_list:$(LOCAL_PATH)/%=%) \
$(bsdiff_source_list:$(LOCAL_PATH)/%=%) \
$(my_source_list:$(LOCAL_PATH)/%=%)
LOCAL_MODULE := bspatch
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
ndk-build一下,不出所尿,肯定会出错。。。。。。。。。。。
果然:
jni/bzlib/bzlib.c:31:27: fatal error: bzlib_private.h: No such file or directory
继续坑。。。。
差哪坨加哪坨
最后加到一定剂量终于ok,bzlib最后定稿的样子
jni/bzlib/
├── blocksort.c
├── bzlib.c
├── bzlib.h
├── bzlib_private.h
├── compress.c
├── crctable.c
├── decompress.c
├── huffman.c
└── randtable.c
下面开始写jni调用bspatch
根据需求写了一个工具类
packagecom.cvte.update.utils;
publicclassDeltaUpdate {
publicstaticnativevoidbspatch(String oldFilePath, String newFilePath, String patchFilePath);
static{
System.loadLibrary("bspatch");
}
}
然后用javah -jni 生成jni头文件和源文件
cd bin/classes
执行
javah -jni com.cvte.update.utils.DeltaUpdate会生成com_cvte_update_utils_DeltaUpdate.h文件
移动到jni目录
对应编写com_cvte_update_utils_DeltaUpdate.c文件
#include "com_cvte_update_utils_DeltaUpdate.h"
JNIEXPORT voidJNICALL Java_com_cvte_update_utils_DeltaUpdate_bspatch
(JNIEnv *env, jclass jcls, jstring oldApkPath, jstring newApkPath, jstring patchFilePath) {
constchar*old_apk_path = (*env)->GetStringUTFChars(env, oldApkPath, NULL);
constchar*new_apk_path = (*env)->GetStringUTFChars(env, newApkPath, NULL);
constchar*patch_file_path = (*env)->GetStringUTFChars(env, patchFilePath, NULL);
// do bspatch
(*env)->ReleaseStringUTFChars(env, oldApkPath, old_apk_path);
(*env)->ReleaseStringUTFChars(env, newApkPath, new_apk_path);
(*env)->ReleaseStringUTFChars(env, patchFilePath, patch_file_path);
}
基本成形,就差核心实现
jni库移植的做法其实很简单:就是把main函数改为jni的调用
找到
bspatch.c的main函数,把命令行传过来的argv[]参数换为我们jni传递过来的参数就可以了
修改效果如下:
intbspatch(constchar*old_apk_path,constchar*new_apk_path,constchar*patch_file_path) {
FILE* f, * cpf, * dpf, * epf;
BZFILE * cpfbz2, * dpfbz2, * epfbz2;
intcbz2err, dbz2err, ebz2err;
intfd;
ssize_t oldsize,newsize;
ssize_t bzctrllen,bzdatalen;
u_char header[32],buf[8];
u_char *old, *new;
off_t oldpos,newpos;
off_t ctrl[3];
off_t lenread;
off_t i;
/* Open patch file */
if((f = fopen(patch_file_path,"r")) == NULL)
err(1, "fopen(%s)", patch_file_path);
/*
File format:
0 8 "BSDIFF40"
8 8 X
16 8 Y
24 8 sizeof(newfile)
32 X bzip2(control block)
32+X Y bzip2(diff block)
32+X+Y ??? bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add x bytes
from oldfile to x bytes from the diff block; copy y bytes from the
extra block; seek forwards in oldfile by z bytes".
*/
/* Read header */
if(fread(header, 1, 32, f)
if(feof(f))
errx(1, "Corrupt patch\n");
err(1, "fread(%s)", patch_file_path);
}
/* Check for appropriate magic */
if(memcmp(header,"BSDIFF40", 8) != 0)
errx(1, "Corrupt patch\n");
/* Read lengths from header */
bzctrllen=offtin(header+8);
bzdatalen=offtin(header+16);
newsize=offtin(header+24);
if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))
errx(1,"Corrupt patch\n");
/* Close patch file and re-open it via libbzip2 at the right places */
if(fclose(f))
err(1, "fclose(%s)", patch_file_path);
if((cpf = fopen(patch_file_path,"r")) == NULL)
err(1, "fopen(%s)", patch_file_path);
if(fseeko(cpf, 32, SEEK_SET))
err(1, "fseeko(%s, %lld)", patch_file_path,
(longlong)32);
if((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
if((dpf = fopen(patch_file_path,"r")) == NULL)
err(1, "fopen(%s)", patch_file_path);
if(fseeko(dpf, 32 + bzctrllen, SEEK_SET))
err(1, "fseeko(%s, %lld)", patch_file_path,
(longlong)(32 + bzctrllen));
if((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
if((epf = fopen(patch_file_path,"r")) == NULL)
err(1, "fopen(%s)", patch_file_path);
if(fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
err(1, "fseeko(%s, %lld)", patch_file_path,
(longlong)(32 + bzctrllen + bzdatalen));
if((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
if(((fd=open(old_apk_path,O_RDONLY,0))<0) ||
((oldsize=lseek(fd,0,SEEK_END))==-1) ||
((old=malloc(oldsize+1))==NULL) ||
(lseek(fd,0,SEEK_SET)!=0) ||
(read(fd,old,oldsize)!=oldsize) ||
(close(fd)==-1)) err(1,"%s",old_apk_path);
if((new=malloc(newsize+1))==NULL) err(1,NULL);
oldpos=0;newpos=0;
while(newpos
/* Read control data */
for(i=0;i<=2;i++) {
lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
if((lenread
(cbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
ctrl[i]=offtin(buf);
};
/* Sanity-check */
if(newpos+ctrl[0]>newsize)
errx(1,"Corrupt patch\n");
/* Read diff string */
lenread = BZ2_bzRead(&dbz2err, dpfbz2, new+ newpos, ctrl[0]);
if((lenread
((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Add old data to diff string */
for(i=0;i
if((oldpos+i>=0) && (oldpos+i
new[newpos+i]+=old[oldpos+i];
/* Adjust pointers */
newpos+=ctrl[0];
oldpos+=ctrl[0];
/* Sanity-check */
if(newpos+ctrl[1]>newsize)
errx(1,"Corrupt patch\n");
/* Read extra string */
lenread = BZ2_bzRead(&ebz2err, epfbz2, new+ newpos, ctrl[1]);
if((lenread
((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Adjust pointers */
newpos+=ctrl[1];
oldpos+=ctrl[2];
};
/* Clean up the bzip2 reads */
BZ2_bzReadClose(&cbz2err, cpfbz2);
BZ2_bzReadClose(&dbz2err, dpfbz2);
BZ2_bzReadClose(&ebz2err, epfbz2);
if(fclose(cpf) || fclose(dpf) || fclose(epf))
err(1, "fclose(%s)", patch_file_path);
/* Write the new file */
if(((fd=open(new_apk_path,O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||
(write(fd,new,newsize)!=newsize) || (close(fd)==-1))
err(1,"%s",new_apk_path);
free(new);
free(old);
return0;
}
原main函数为:
intmain(intargc,char* argv[])
{
FILE* f, * cpf, * dpf, * epf;
BZFILE * cpfbz2, * dpfbz2, * epfbz2;
intcbz2err, dbz2err, ebz2err;
intfd;
ssize_t oldsize,newsize;
ssize_t bzctrllen,bzdatalen;
u_char header[32],buf[8];
u_char *old, *new;
off_t oldpos,newpos;
off_t ctrl[3];
off_t lenread;
off_t i;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Open patch file */
if((f = fopen(argv[3],"r")) == NULL)
err(1, "fopen(%s)", argv[3]);
/*
File format:
0 8 "BSDIFF40"
8 8 X
16 8 Y
24 8 sizeof(newfile)
32 X bzip2(control block)
32+X Y bzip2(diff block)
32+X+Y ??? bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add x bytes
from oldfile to x bytes from the diff block; copy y bytes from the
extra block; seek forwards in oldfile by z bytes".
*/
/* Read header */
if(fread(header, 1, 32, f)
if(feof(f))
errx(1, "Corrupt patch\n");
err(1, "fread(%s)", argv[3]);
}
/* Check for appropriate magic */
if(memcmp(header,"BSDIFF40", 8) != 0)
errx(1, "Corrupt patch\n");
/* Read lengths from header */
bzctrllen=offtin(header+8);
bzdatalen=offtin(header+16);
newsize=offtin(header+24);
if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))
errx(1,"Corrupt patch\n");
/* Close patch file and re-open it via libbzip2 at the right places */
if(fclose(f))
err(1, "fclose(%s)", argv[3]);
if((cpf = fopen(argv[3],"r")) == NULL)
err(1, "fopen(%s)", argv[3]);
if(fseeko(cpf, 32, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(longlong)32);
if((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
if((dpf = fopen(argv[3],"r")) == NULL)
err(1, "fopen(%s)", argv[3]);
if(fseeko(dpf, 32 + bzctrllen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(longlong)(32 + bzctrllen));
if((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
if((epf = fopen(argv[3],"r")) == NULL)
err(1, "fopen(%s)", argv[3]);
if(fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(longlong)(32 + bzctrllen + bzdatalen));
if((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
if(((fd=open(argv[1],O_RDONLY,0))<0) ||
((oldsize=lseek(fd,0,SEEK_END))==-1) ||
((old=malloc(oldsize+1))==NULL) ||
(lseek(fd,0,SEEK_SET)!=0) ||
(read(fd,old,oldsize)!=oldsize) ||
(close(fd)==-1)) err(1,"%s",argv[1]);
if((new=malloc(newsize+1))==NULL) err(1,NULL);
oldpos=0;newpos=0;
while(newpos
/* Read control data */
for(i=0;i<=2;i++) {
lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
if((lenread
(cbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
ctrl[i]=offtin(buf);
};
/* Sanity-check */
if(newpos+ctrl[0]>newsize)
errx(1,"Corrupt patch\n");
/* Read diff string */
lenread = BZ2_bzRead(&dbz2err, dpfbz2, new+ newpos, ctrl[0]);
if((lenread
((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Add old data to diff string */
for(i=0;i
if((oldpos+i>=0) && (oldpos+i
new[newpos+i]+=old[oldpos+i];
/* Adjust pointers */
newpos+=ctrl[0];
oldpos+=ctrl[0];
/* Sanity-check */
if(newpos+ctrl[1]>newsize)
errx(1,"Corrupt patch\n");
/* Read extra string */
lenread = BZ2_bzRead(&ebz2err, epfbz2, new+ newpos, ctrl[1]);
if((lenread
((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Adjust pointers */
newpos+=ctrl[1];
oldpos+=ctrl[2];
};
/* Clean up the bzip2 reads */
BZ2_bzReadClose(&cbz2err, cpfbz2);
BZ2_bzReadClose(&dbz2err, dpfbz2);
BZ2_bzReadClose(&ebz2err, epfbz2);
if(fclose(cpf) || fclose(dpf) || fclose(epf))
err(1, "fclose(%s)", argv[3]);
/* Write the new file */
if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||
(write(fd,new,newsize)!=newsize) || (close(fd)==-1))
err(1,"%s",argv[2]);
free(new);
free(old);
return0;
}
对比一下,是不是发现。。。。。。。。。。。。。真的很简单
基本大功告成,剩下的自己解决
======================要割一下才会健康========================
======================不能割,还要留着用========================
node.js服务器这里偷懒一下,就通过调用编译好的bsdiff二进制文件来实现差分包的产生
为此做了一个npm的包,地址: https://www.npmjs.org/package/node-bsdiff-android
先介绍使用方法:
varbsdiffApk = require('node-bsdiff-android');
varnewApkFile ='./files/new.apk';
varoldApkFile ='./files/old.apk';
varpatchFile ='./files/es.patch';
varbsdiffFile ='./files/bsdiff';
bsdiffApk(oldApkFile, newApkFile, patchFile, function(err, data) {
if(err) {
console.log("Error");
} else{
console.log("data: "+ data);
}
});
node.js源码在github上https://github.com/arthur-zhang/node-bsdiff-android
varexec, os, bsdiffApk;
os = require('os');
exec = require('child_process').execFile;
bsdiffApk = function(oldApkFilePath, newApkFilePath, patchFilePath, cb) {
returnexec(""+ __dirname +"/bsdiff", [oldApkFilePath, newApkFilePath, patchFilePath], {
maxBuffer: 1024 * 1024
}, function(err, out) {
if(err) {
returncb(err);
}
cb(null, out);
});
};
module.exports = bsdiffApk;
下面比较重要的是,安装时下载bsdiff文件,新建install.js
varhttp = require('http');
varfs = require('fs');
varos = require('os');
varplatform =null;
if(os.type() =='Darwin') {
platform = 'macosx';
} elseif(os.type() =='Linux') {
platform = 'linux';
} else{
thrownewError('Unknown OS!');
}
functionattemptDownload(attemptsLeft) {
varurl ="http://download.cvte.cn/downfile.php?action=view&file_id=42254&file_key=Iel8FprG";
varfile = fs.createWriteStream("bsdiff");
http.get(url, function(res) {
res.on('data',function(data) {
file.write(data);
}).on('end',function() {
file.end();
fs.chmodSync('bsdiff','755');
});
});
}
attemptDownload(3);
然后在package.json中添加
"scripts": {
"install":"node install.js"
},
然后npm addUser(不知道是什么的这部分可以跳过了)
然后npm publish 发布应用