React Native 飞行日记——增量更新

一、前言:

​ 本文介绍一种用于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

你可能感兴趣的:(React Native 飞行日记——增量更新)