在當代App應用大小不斷增大的情況下增量更新代替全量更新已是趨勢,可以節省許多用戶流量,還是老樣子先上效果圖,由於上傳圖片限制壓縮有點嚴重湊合看吧:
如果大家想直接使用,我這裏已經爲大家封裝成library庫了,大家直接依賴庫就可以直接使用了非常簡單:
allprojects {
repositories {
maven { url('https://oranges.bintray.com/DiffPatch') }
}
}
implementation 'com.xhiston.diffpatch:diffpatch:1.0.0'
DiffPatchUtil diffPatchUtil = new DiffPatchUtil(context);
diffPatchUtil.setOnDiffClickListener(new DiffPatchUtil.OnDiffClickListener() {
@Override
public void onStart() {
}
@Override
public void onSuccess() {
diffPatchUtil.showMessage("Success");
}
@Override
public void onError(String msg) {
diffPatchUtil.showMessage(msg);
}
});
diffPatchUtil.setOnPatchClickListener(new DiffPatchUtil.OnPatchClickListener() {
@Override
public void onStart() {
}
@Override
public void onSuccess() {
diffPatchUtil.showMessage("Success");
}
@Override
public void onError(String msg) {
diffPatchUtil.showMessage(msg);
}
});
File file = new File(newApk);
//打出差分補丁包路徑爲patch
diffPatchUtil.diff(getApplicationInfo().sourceDir, newApk, patch);
File file = new File(patch);
//根據補丁包路徑patch合成新的apk路徑爲patchApk
diffPatchUtil.patch(getApplicationInfo().sourceDir, patchApk, patch);
當然還有不能忘記加入權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
最後別忘了下載so庫導入項目,如果項目中發現有問題隨時歡迎告知我及時修正。
如果準備自己手寫一個增量更新注意你的Android Api Level 爲29以上的話請在manifest中加入以下一行代碼,否則就算打開了讀寫權限也會無法讀寫本地文件,如果依賴的是我的lib就不用寫了因爲我已經加入了本行代碼:
<application android:requestLegacyExternalStorage="true" />
如果大家想了解原理準備自己擼一個就往下看吧具體實現方式吧:
首先使用差分算法bsdiff計算出差分包,感興趣的可以自己點擊進去下載源碼,然後就是使用bzip2壓縮工具打包生成補丁差分包文件和合並補丁包文件;由於這裏提供的都是C語言程序所以我們需要藉助NDK/JNI實現增量更新了。
我們先去bsdiff地址下載bsdiff.c和bspatch.c這兩個文件,然後去bzip2下載源碼包解壓複製粘貼出我們需要的文件:
bzip2/blocksort.c\
bzip2/bzip2.c\
bzip2/bzip2recover.c\
bzip2/bzlib.c\
bzip2/bzlib.h\
bzip2/bzlib_private.h\
bzip2/compress.c\
bzip2/crctable.c\
bzip2/decompress.c\
bzip2/huffman.c\
bzip2/randtable.c
好了準備工作我們都做好了就開始創建JNI文件吧:
1.創建一個java文件例如:DiffPatchUtil,然後使用命令進入該文件目錄下用命令編譯“javac DiffPatchUtil.java”生成DiffPatchUtil.class文件,再執行“javah com.xhiston.diffpatch
.DiffPatchUtil”生成com_xhiston_diffpatch_DiffPatchUtil.h文件這一步大家需要注意一下命令目錄回退一下到DiffPatchUtil的最外層包名下面不然命令提示找不包名下文件。DiffPatchUtil.java創建的時候可以簡單的只放JNI回調的相關方法,生成com_xhiston_diffpatch_DiffPatchUtil.h文件後再修改添加其他方法。
package com.xhiston.diffpatch;
import android.content.Context;
import android.os.Looper;
import android.widget.Toast;
/**
* Created by xie on 2020/10/20.
*/
public class DiffPatchUtil {
static {
System.loadLibrary("diffpatch");
}
/**
* 採用差分算法將當前包與新包打patch補丁包,生成xxx.patch文件
**/
public native int diff(String oldApk, String newApk, String patch);
/**
* 採用差分算法將patch補丁包與當前包合併生成新包,生成apk文件
**/
public native int patch(String oldApk, String newApk, String patch);
}
com_xhiston_diffpatch_DiffPatchUtil.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xhiston_diffpatch_DiffPatchUtil */
#ifndef _Included_com_xhiston_diffpatch_DiffPatchUtil
#define _Included_com_xhiston_diffpatch_DiffPatchUtil
#ifdef __cplusplus
extern "C" {
#endif
int mybspatch(JNIEnv *env, jobject clazz,int argc, char *argv[]);
int mybsdiff(JNIEnv *env, jobject clazz, int argc,char *argv[]);
JNIEXPORT jint JNICALL Java_com_xhiston_diffpatch_DiffPatchUtil_patch
(JNIEnv *, jobject, jstring, jstring, jstring);
JNIEXPORT jint JNICALL Java_com_xhiston_diffpatch_DiffPatchUtil_diff
(JNIEnv *, jobject, jstring, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
2.創建com_xhiston_diffpatch_DiffPatchUtil.c以及JNI配置文件Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
APP_ABI := All
APP_PLATFORM := android-16
LOCAL_C_INCLUDES :=bzip2
LOCAL_MODULE := diffpatch
LOCAL_SRC_FILES := com_xhiston_diffpatch_DiffPatchUtil.h\
com_xhiston_diffpatch_DiffPatchUtil.c\
bspatch.c\
bsdiff.c\
myerr.h\
myerr.c\
bzip2/blocksort.c\
bzip2/bzip2.c\
bzip2/bzip2recover.c\
bzip2/bzlib.c\
bzip2/bzlib.h\
bzip2/bzlib_private.h\
bzip2/compress.c\
bzip2/crctable.c\
bzip2/decompress.c\
bzip2/huffman.c\
bzip2/randtable.c\
include $(BUILD_SHARED_LIBRARY)
修改一下bspatch.c、bsdiff.c裏的main方法名然後com_xhiston_diffpatch_DiffPatchUtil中就可以重新調用了,可以參考我的源碼進行修改,確保代碼無誤後便可以ndk-buildd編譯生成so庫了,當然編譯的時候也會檢查代碼報錯的需要自行修改,不過這個就要求大家有一定的C語言基礎了,沒基礎的話可以現學一下不是多難。
com_xhiston_diffpatch_DiffPatchUtil.c:
#include <com_xhiston_diffpatch_DiffPatchUtil.h>
jint
Java_com_xhiston_diffpatch_DiffPatchUtil_patch (JNIEnv *env, jclass clazz, jstring old, jstring new, jstring patch) {
int args=4;
int resutlt = args;
char *argv[args];
argv[0] = "bspatch";
argv[1] = (char*)((*env)->GetStringUTFChars(env, old, 0));
argv[2] = (char*)((*env)->GetStringUTFChars(env, new, 0));
argv[3] = (char*)((*env)->GetStringUTFChars(env, patch, 0));
resutlt = mybspatch(env,clazz,args,argv);
(*env)->ReleaseStringUTFChars(env,old, argv[1]);
(*env)->ReleaseStringUTFChars(env,new, argv[2]);
(*env)->ReleaseStringUTFChars(env,patch,argv[3]);
return resutlt;
}
jint
Java_com_xhiston_diffpatch_DiffPatchUtil_diff (JNIEnv *env, jobject clazz, jstring old, jstring new, jstring patch) {
int args=4;
int resutlt = args;
char *argv[args];
argv[0] = "bsdiff";
argv[1] = (char*)((*env)->GetStringUTFChars(env, old, 0));
argv[2] = (char*)((*env)->GetStringUTFChars(env, new, 0));
argv[3] = (char*)((*env)->GetStringUTFChars(env, patch, 0));
resutlt= mybsdiff(env,clazz,args,argv);
(*env)->ReleaseStringUTFChars(env,old, argv[1]);
(*env)->ReleaseStringUTFChars(env,new, argv[2]);
(*env)->ReleaseStringUTFChars(env,patch,argv[3]);
return resutlt;
}
bspatch.c:
int mybspatch(JNIEnv *env, jobject clazz,int argc,char * argv[])
{
onPatchStart(env,clazz);
FILE * f, * cpf, * dpf, * epf;
BZFILE * cpfbz2, * dpfbz2, * epfbz2;
int cbz2err, dbz2err, ebz2err;
int fd;
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 ;
errno =0;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Open patch file */
if ((f = fopen(argv[3], "r")) == NULL){
errx(1, "fopen(%s)", argv[3]);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
onPatchError(env,clazz,strerror(errno));
return -1;
}
/* Read header */
if (fread(header, 1, 32, f) < 32) {
if (feof(f))
err(1, "Corrupt patch\n");
errx(1, "fread(%s)", argv[3]);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
onPatchError(env,clazz,strerror(errno));
return -1;
}
/* Check for appropriate magic */
if (memcmp(header, "BSDIFF40", 8) != 0)
err(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))
err(1,"Corrupt patch\n");
/* Close patch file and re-open it via libbzip2 at the right places */
if (fclose(f))
errx(1, "fclose(%s)", argv[3]);
if ((cpf = fopen(argv[3], "r")) == NULL){
errx(1, "fopen(%s)", argv[3]);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
onPatchError(env,clazz,strerror(errno));
return -1;
}
if (fseeko(cpf, 32, SEEK_SET))
errx(1, "fseeko( %lld)", (long long)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){
errx(1, "fopen(%s)", argv[3]);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
onPatchError(env,clazz,strerror(errno));
return -1;
}
if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
errx(1, "fseeko( %lld)", (long long)(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){
errx(1, "fopen(%s)", argv[3]);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
onPatchError(env,clazz,strerror(errno));
return -1;
}
if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
errx(1, "fseeko(%lld)", (long long)(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)) errx(1,"當前文件 %s",argv[1]);
if((new=malloc(newsize+1))==NULL) err(1,NULL);
oldpos=0;newpos=0;
while(newpos<newsize) {
/* Read control data */
for(i=0;i<=2;i++) {
lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
if ((lenread < 8) || ((cbz2err != BZ_OK) &&
(cbz2err != BZ_STREAM_END)))
err(1, "Corrupt patch\n");
ctrl[i]=offtin(buf);
};
/* Sanity-check */
if(newpos+ctrl[0]>newsize)
err(1,"Corrupt patch\n");
/* Read diff string */
lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]);
if ((lenread < ctrl[0]) ||
((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
err(1, "Corrupt patch\n");
/* Add old data to diff string */
for(i=0;i<ctrl[0];i++)
if((oldpos+i>=0) && (oldpos+i<oldsize))
new[newpos+i]+=old[oldpos+i];
/* Adjust pointers */
newpos+=ctrl[0];
oldpos+=ctrl[0];
/* Sanity-check */
if(newpos+ctrl[1]>newsize)
err(1,"Corrupt patch\n");
/* Read extra string */
lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]);
if ((lenread < ctrl[1]) ||
((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
err(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))
errx(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))
errx(1,"%s",argv[2]);
free(new);
free(old);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
if(errno == 0){
onPatchSuccess(env,clazz);
}else{
onPatchError(env,clazz,strerror(errno));
return -1;
}
return 0;
}
bsdiff.c:
int mybsdiff(JNIEnv *env,jobject clazz,int argc,char *argv[])
{
onDiffStart(env,clazz);
int fd;
u_char *old,*new;
off_t oldsize,newsize;
off_t *I,*V;
off_t scan,pos,len;
off_t lastscan,lastpos,lastoffset;
off_t oldscore,scsc;
off_t s,Sf,lenf,Sb,lenb;
off_t overlap,Ss,lens;
off_t i;
off_t dblen,eblen;
u_char *db,*eb;
u_char buf[8];
u_char header[32];
FILE * pf;
BZFILE * pfbz2;
int bz2err;
errno =0;
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)) errx(1,"%s 非設備文件",argv[1]);
LOG_E("設備文件 %s",argv[1]);
if(((I=malloc((oldsize+1)*sizeof(off_t)))==NULL) ||
((V=malloc((oldsize+1)*sizeof(off_t)))==NULL)) err(1,"設備文件長度爲0");
qsufsort(I,V,old,oldsize);
free(V);
if(((fd=open(argv[2],O_RDONLY,0))<0) ||
((newsize=lseek(fd,0,SEEK_END))==-1) ||
((new=malloc(newsize+1))==NULL) ||
(lseek(fd,0,SEEK_SET)!=0) ||
(read(fd,new,newsize)!=newsize) ||
(close(fd)==-1)) errx(1,"%s 非存儲卡文件",argv[2]);
if(((db=malloc(newsize+1))==NULL) ||
((eb=malloc(newsize+1))==NULL)) err(1,"存儲卡文件長度爲0");
LOG_E("存儲卡文件 %s",argv[2]);
dblen=0;
eblen=0;
if ((pf = fopen(argv[3], "w")) == NULL){
errx(1, "創建 %s 失敗", argv[3]);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
onPatchError(env,clazz,strerror(errno));
return -1;
}
memcpy(header,"BSDIFF40",8);
offtout(0, header + 8);
offtout(0, header + 16);
offtout(newsize, header + 24);
if (fwrite(header, 32, 1, pf) != 1)
errx(1, "fwrite(%s) 寫入失敗", argv[3]);
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
scan=0;len=0;
lastscan=0;lastpos=0;lastoffset=0;
while(scan<newsize) {
oldscore=0;
for(scsc=scan+=len;scan<newsize;scan++) {
len=search(I,old,oldsize,new+scan,newsize-scan,
0,oldsize,&pos);
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<oldsize) &&
(old[scsc+lastoffset] == new[scsc]))
oldscore++;
if(((len==oldscore) && (len!=0)) ||
(len>oldscore+8)) break;
if((scan+lastoffset<oldsize) &&
(old[scan+lastoffset] == new[scan]))
oldscore--;
};
if((len!=oldscore) || (scan==newsize)) {
s=0;Sf=0;lenf=0;
for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
if(old[lastpos+i]==new[lastscan+i]) s++;
i++;
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
};
lenb=0;
if(scan<newsize) {
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
if(old[pos-i]==new[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
};
if(lastscan+lenf>scan-lenb) {
overlap=(lastscan+lenf)-(scan-lenb);
s=0;Ss=0;lens=0;
for(i=0;i<overlap;i++) {
if(new[lastscan+lenf-overlap+i]==
old[lastpos+lenf-overlap+i]) s++;
if(new[scan-lenb+i]==
old[pos-lenb+i]) s--;
if(s>Ss) { Ss=s; lens=i+1; };
};
lenf+=lens-overlap;
lenb-=lens;
};
for(i=0;i<lenf;i++)
db[dblen+i]=new[lastscan+i]-old[lastpos+i];
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
eb[eblen+i]=new[lastscan+lenf+i];
dblen+=lenf;
eblen+=(scan-lenb)-(lastscan+lenf);
offtout(lenf,buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((scan-lenb)-(lastscan+lenf),buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((pos-lenb)-(lastpos+lenf),buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
lastscan=scan-lenb;
lastpos=pos-lenb;
lastoffset=pos-scan;
};
};
BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Compute size of compressed ctrl data */
if ((len = ftello(pf)) == -1)
err(1, "ftello");
offtout(len-32, header + 8);
/* Write compressed diff data */
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Compute size of compressed diff data */
if ((newsize = ftello(pf)) == -1)
err(1, "ftello");
offtout(newsize - len, header + 16);
/* Write compressed extra data */
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Seek to the beginning, write the header, and close the file */
if (fseeko(pf, 0, SEEK_SET))
err(1, "fseeko");
if (fwrite(header, 32, 1, pf) != 1)
errx(1, "fwrite(%s)", argv[3]);
if (fclose(pf))
err(1, "fclose");
/* Free the memory we used */
free(db);
free(eb);
free(I);
free(old);
free(new);
LOG_E("errno is %d,strerror is %s\n",errno,strerror(errno));
if(errno == 0){
onPatchSuccess(env,clazz);
}else{
onPatchError(env,clazz,strerror(errno));
return -1;
}
return 0;
}
如果我的文章對你有幫助,"一鍵三連"給個鼓勵,讓我更新文章更有動力,“關注”我會有更多幹貨不定時的更新哦!