在当代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;
}
如果我的文章对你有帮助,"一键三连"给个鼓励,让我更新文章更有动力,“关注”我会有更多干货不定时的更新哦!
欢迎关注微信公众号!你的每个赞和在看,都是对我的支持!👍