bsdiff php,Apk差分升级Android客户端和Node.js服务端实现

核心的内容是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 发布应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值