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 <oldfile path> <newfile path> <patchfile path>
#bspatch 合成命令
bspatch <oldfile path> <newfile path> <patchfile path>

​ 虽然命令用起来省事,但是我们每次都手动都去调用命令这显然不可取。所以都会在服务器开发一套工具链,这个根据每个人的使用技术的不同做法也不同,所要做的其实就是为了解放双手,实现自动化拆分以及文件的版本管理。仁者见仁智者见智,这个方面就不详细介绍了。

如果要自己编译源码又要怎么处理呢?请查看我写的另一篇文章: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 <bzip2/bzlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <unistd.h>
#include <fcntl.h>
#include <android/log.h>
#include <jni.h>

#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(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)))
                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 < ctrl[0]) ||
            ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
            errx(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)
            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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容