一、前言:
本文介绍一种用于React Native增量升级的方案,类似与Apk增量更新方案。在React Native出现问题或者需要更新时,无需下载全量包,只需下载增加部分便可体验新版本的完整功能。测试环境为 Ubuntu 16.04 。
二、原理:
增量更新的原理就是将旧版的全量zip文件和新版的全量zip文件进行对比,得到一个差异包,当用户需要升级时,从服务器上下载所对应的差异包,使用本地的Base版本与差异包生成新版文件。全量zip文件包含全量bundle与全量assets。
三、实现:
(1)服务器
文件的拆分和合并使用的是二进制比较工具bsdiff, 而bsdiff还需要依赖bzip2。我们需要将源码编译为.so文件或其他形式的文件。但是我比较懒,如何快速使用bsdiff呢?如下:
#Ubuntu 安装命令
sudo apt-get install bsdiff
#CentOS 安装命令
yum install bsdiff
#Mac OS X 安装命令
brew install bsdiff
当我们成功安装bsdiff后,就可以愉快的使用命令来拆分和合成文件了。
#bsdiff 拆分命令
bsdiff
#bspatch 合成命令
bspatch
虽然命令用起来省事,但是我们每次都手动都去调用命令这显然不可取。所以都会在服务器开发一套工具链,这个根据每个人的使用技术的不同做法也不同,所要做的其实就是为了解放双手,实现自动化拆分以及文件的版本管理。仁者见仁智者见智,这个方面就不详细介绍了。
如果要自己编译源码又要怎么处理呢?请查看我写的另一篇文章:React Native 飞行日记——bsdiff源码编译
(2)客户端
知晓了服务器的基础实现思路,我们再来看一下客户端如何去实现合并流程。同样我们需要使用到bsdiff,将c文件编译成.so动态库。开始动手使用NDK编译。
准备工作:
配置NDK交叉编译环境,推荐一篇很赞的文章介绍:如何优雅地使用NDK|家杰的博客 。
完整bsdiff代码(服务器使用的是同一套)下载地址:bsdiff 4.3 。
编译步骤:
1.新建一个java类,在类中声明一个Native方法。
/**示例代码*/
public class PatchUtil {
/**
* 合成方法
* @param oldFilePath 旧包路径
* @param newFilePath 新包路径
* @param patchPath 补丁包路径
* @return
*/
public native static int bspatch(String oldFilePath, String newFilePath, String patchPath);
}
2.使用javah命令生成.h头文件。
3.拷贝bsdiff中bspatch.c相关源码到jni目录下,修改bspatch名称与.h文件同名(强迫症)。
4.bspatch核心代码:
/**示例代码*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bzip2/bzlib.c"
#include "bzip2/crctable.c"
#include "bzip2/compress.c"
#include "bzip2/decompress.c"
#include "bzip2/randtable.c"
#include "bzip2/blocksort.c"
#include "bzip2/huffman.c"
#include "com_benlai_prototype_bsdiff_PatchUtil.h"
static off_t offtin(u_char *buf)
{
off_t y;
y=buf[7]&0x7F;
y=y*256;y+=buf[6];
y=y*256;y+=buf[5];
y=y*256;y+=buf[4];
y=y*256;y+=buf[3];
y=y*256;y+=buf[2];
y=y*256;y+=buf[1];
y=y*256;y+=buf[0];
if(buf[7]&0x80) y=-y;
return y;
}
int mergepatch(int argc,char * argv[])
{
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;
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) < 32) {
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],
(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)
err(1, "fopen(%s)", argv[3]);
if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(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)
err(1, "fopen(%s)", argv[3]);
if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(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)) err(1,"%s",argv[1]);
if((new=malloc(newsize+1))==NULL) err(1,NULL);
oldpos=0;newpos=0;
while(newposnewsize)
errx(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)))
errx(1, "Corrupt patch\n");
/* Add old data to diff string */
for(i=0;i=0) && (oldpos+inewsize)
errx(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)))
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);
return 0;
}
/*
* Class: com_benlai_prototype_bsdiff_PatchUtil
* Method: bspatch
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint Java_com_benlai_prototype_bsdiff_PatchUtil_bspatch
(JNIEnv *env, jclass obj, jstring old_file, jstring new_file, jstring patch_file){
char * ch[4];
ch[0] = "bspatch";
ch[1] = (char*) ((*env)->GetStringUTFChars(env, old_file, 0));
ch[2] = (char*) ((*env)->GetStringUTFChars(env, new_file, 0));
ch[3] = (char*) ((*env)->GetStringUTFChars(env, patch_file, 0));
__android_log_print(ANDROID_LOG_INFO, "BsPatch", "old = %s ", ch[1]);
__android_log_print(ANDROID_LOG_INFO, "BsPatch", "new = %s ", ch[2]);
__android_log_print(ANDROID_LOG_INFO, "BsPatch", "patch = %s ", ch[3]);
int ret = mergepatch(4, ch);
__android_log_print(ANDROID_LOG_INFO, "BsPatch", "mergepatch result = %d ", ret);
######
(*env)->ReleaseStringUTFChars(env, old_file, ch[1]);
(*env)->ReleaseStringUTFChars(env, new_file, ch[2]);
(*env)->ReleaseStringUTFChars(env, patch_file, ch[3]);
return ret;
}
5.编写Android.mk和Application.mk
#Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := BsPatch
LOCAL_SRC_FILES := com_benlai_prototype_bsdiff_PatchUtil.c
LOCAL_LDLIBS := -lz -llog
include $(BUILD_SHARED_LIBRARY)
#Application.mk
APP_ABI := all
APP_PLATFORM := android-16
APP_STL := gnustl_static
6.通过Ndk-build编译输出so文件,大功告成!
四、代码:
源码已经上传到我的Github,下载链接BsdiffDemo
五、Native流程:
六、参考资料:
1.http://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA...
2.http://blog.majiajie.me/2016/03/27/...
3.https://github.com/cnsnake11/blog/blob/master/ReactNative...
4.https://github.com/cundong/SmartAppUpdates