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 發布應用
總結
以上是生活随笔為你收集整理的bsdiff php,Apk差分升级Android客户端和Node.js服务端实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql cpu占用率过高,MySQL
- 下一篇: matlab三角定位分析程序,Matla