GithubHelp home page GithubHelp logo

nebsdiff's Introduction

BSDiff实现增量更新

[TOC]

一、增量更新

1.1 增量更新的好处

  • 对用户来说,省流量,下载时间快,提升用户体验;
  • 对服务器来说,减少服务器带宽压力。

1.2 BSDiff算法

BSDiff是一个差量更新算法,它在服务器端运行BSDiff算法产生patch包,在客户端运行BSPatch算法,将旧文件和patch包合并成新文件。

1.2.1 差量更新算法的核心**

尽可能多地利用old文件中已有的内容,尽可能少地加入新的内容来构建new文件。通常的做法是对old文件和new文件做子字符串匹配或使用Hash技术,提取公共部分,将new文件中剩余的部分打包成patch包;在patch阶段中,用copyinginsertion两个基本操作即可将old文件和patch包合成new文件。

1.2.2 BSDiff算法的改进

Insertion操作会引起大量的指针变动和修改,要记录这些值才能在patch阶段给修改过的区域重新定位,由于这些指针控制字段必须在BSDiff阶段加入patch包,产生的patch包会较大。BSDiff通过引入diff string的概念,大大减少了要记录的指针控制字的数目,从而使patch包更小。

image

1.2.3 BSDiff算法基本步骤

  1. old文件中所有子字符串形成一个字典;
  2. 对比old文件和new文件,产生diff stringextra string
  3. diff stringextra string以及相应的控制字用zip压缩成一个patch包。

步骤1是所有差量更新算法的瓶颈,时间复杂度为O(nlogn),空间复杂度为O(n)nold文件的长度。BSDiff采用Faster suffix sorting方法获得一个字典序,使用了类似于快速排序的二分**,使用了bucketIV三个辅助数组。最终获得到一个数组I,记录了以前缀分组的各个字符串组的最后一个字符串在old中的开始位置。

步骤2BSDiff产生patch包的核心部分,详细描述如下:

image

image

image

步骤3diff stringextra string以及相应的控制字用zip压缩成一个patch包。

image

可以看出在用zip压缩之前的patch包时没有节约任何字符的,但diff strings可以被高效地压缩,因此BSDiff是一个和依赖于压缩和解压的算法。

1.3 BSPatch算法

客户端合成patch的基本步骤如下:

  1. 接收patch包;
  2. 解压patch包;
  3. 还原new文件。

三个步骤同时在O(m)时间内完成,但在时间常数上更依赖于解压patch包的部分,m为新文件的长度。

1.4 对比

1.4.1 时间空间复杂度

BSDiffBSPatch的时间与空间复杂度如下表所示:

复杂度类型\算法名称 BSDiff BSPatch
时间复杂度 O(nlogN) O(n+m)
空间复杂度 O(n) O(n+m)

1.4.1 BSDiff压缩效率实验数据

image

参考博客:[差量更新系列1]BSDiff算法学习笔记

二、实现

2.1 服务器端生成差量包

Windows端可以直接使用别人生成好的程序。

下载地址:https://github.com/cnSchwarzer/bsdiff-win/releases/tag/v4.3

Linux系统服务器可以通过下载源码编译生成。

bsdiff 下载地址: http://www.daemonology.net/bsdiff/

2.1.1 下载bsdiff-4.3

wget http://www.daemonology.net/bsdiff/bsdiff-4.3.tar.gz

本文尝试下载时提示如下:

Resolving www.daemonology.net (www.daemonology.net)... 34.218.139.66, 2600:1f14:4e4:8c01:55b4:c24e:cc6e:c5c3
Connecting to www.daemonology.net (www.daemonology.net)|34.218.139.66|:80... connected.
HTTP request sent, awaiting response... 403 Forbidden
ERROR 403: Forbidden.

无奈只好使用别人下载好的文件bsdiff-4.3.tar.gz(在resources目录下)。

2.1.2 上传bsdiff-4.3到服务器

本地物理机终端上使用如下命令:

scp [$File_name] [$Username]@[$IP]:[$File]
  • [$File_name]指的是本地文件的名字。
  • [$Username]指的是服务器的用户名字(eg: root)。
  • [$IP]指的是服务器的IP地址。
  • [$File]指的是指定的服务器目录。

2.1.3 解压

tar zxvf bsdiff-4.3.tar.gz

2.1.4 编译

进入解压目录尝试编译:

cd bsdiff-4.3
make

报错如下:

Makefile:13: *** missing separator.  Stop.

解决方法:

修改Makdefile文件,在13、15行之前添加TAB键作为分隔符。之后重新编译,报错如下:

bsdiff.c:33:10: fatal error: bzlib.h: No such file or directory
 #include <bzlib.h>
          ^~~~~~~~~

解决方法:

安装yum源中的bzip2包( 参考:centos boost fatal error: bzlib.h: No such file or directory #include "bzlib.h" )

yum install  bzip2-devel.x86_64

重新执行make命令,则成功编译,生成如下文件:

bsdiff  bsdiff.1  bsdiff.c  bspatch  bspatch.1  bspatch.c  Makefile

2.1.5 使用BSDiff生成差量包

首先Android端生成新旧new.apkold.apk并通过2.1.2所述方法上传到服务器,然后用bsdiff命令生成差量包patch.diff

# 查看bsdiff的用法
./bsdiff --help
# bsdiff: usage: ./bsdiff oldfile newfile patchfile
./bsdiff old.apk new.apk patch.diff

同样采用2.1.2所述方法(交换scp后面两参数的位置),将生成的差量包patch.diff下载到手机SD卡上。

2.2 Android端合成新apk文件

2.2.1 导入c文件

导入bspatch.c文件和bzip2目录文件,可以从 https://github.com/cnSchwarzer/bsdiff-win 获取。

2.2.2 修改CMakeList.txt文件

cmake_minimum_required(VERSION 3.4.1)
include_directories(${CMAKE_SOURCE_DIR}/bzip2)
aux_source_directory(${CMAKE_SOURCE_DIR}/bzip2/ bzip2_srcs)

add_library( # Sets the name of the library.
        native-lib
        SHARED
        native-lib.cpp
        bspatch.c
        ${bzip2_srcs})

target_link_libraries( # Specifies the target library.
        native-lib
        log)

2.2.3 下载(模拟)差量包&合成新apk

@Override
protected File doInBackground(Void... voids) {
  //下载更新补丁包(省略)
  String patchPath = new File(APK_PATH, "patch.diff").getAbsolutePath();
  //合成:旧版本apk文件(当前运行的apk)+ 从服务器下载的补丁包文件 = 新版本的apk安装包文件
  String oldApkPath = getApplicationInfo().sourceDir;
  File newApk = new File(APK_PATH, "new.apk");
  Log.e("sty", newApk.getAbsolutePath());
  if(!newApk.exists()) {
    try {
      newApk.createNewFile();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  doPatchNative(oldApkPath, newApk.getAbsolutePath(), patchPath);
  return newApk;
}

调用BSPatch算法合成新的apk:

extern "C"
JNIEXPORT void JNICALL
Java_com_sty_ne_bsdiff_MainActivity_doPatchNative(JNIEnv *env, jobject thiz, jstring old_apk_path_,
                                                  jstring new_apk_path_, jstring patch_path_) {
    const char* old_apk_path = env->GetStringUTFChars(old_apk_path_, 0);
    const char* new_apk_path = env->GetStringUTFChars(new_apk_path_, 0);
    const char* patch_path = env->GetStringUTFChars(patch_path_, 0);

    // ./bspatch oldfile newfile patchfile
    char* argv[4] = {
            "bspatch",
            const_cast<char *>(old_apk_path),
            const_cast<char *>(new_apk_path),
            const_cast<char *>(patch_path)
    };

    bspatch_main(4, argv);

    env->ReleaseStringUTFChars(old_apk_path_, old_apk_path);
    env->ReleaseStringUTFChars(new_apk_path_, new_apk_path);
    env->ReleaseStringUTFChars(patch_path_, patch_path);
}

2.2.4 安装新的apk

@Override
protected void onPostExecute(File newApk) {
  //安装
  if(!newApk.exists()) {
    return;
  }
  Intent intent = new Intent(Intent.ACTION_VIEW);
  intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  if(Build.VERSION.SDK_INT >= 24) { //Android7.0以上
    //参数2:清单文件中provider节点里面的authorities  参数3:共享的文件,即apk包的file类
    Uri apkUri = FileProvider.getUriForFile(MainActivity.this,
                                            getApplicationInfo().packageName + ".provider", newApk);
    //对目标应用临时授权该URI所代表的文件
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
  }else {
    intent.setDataAndType(Uri.fromFile(newApk), "application/vnd.android.package-archive");
  }
  startActivity(intent);
}

AndroidManifest.xml文件定义provider:

<provider
          android:authorities="${applicationId}.provider"
          android:name="androidx.core.content.FileProvider"
          android:grantUriPermissions="true"
          android:exported="false">
  <meta-data
             android:name="android.support.FILE_PROVIDER_PATHS"
             android:resource="@xml/file_paths"/>
</provider>

2.2.5 采坑

编译运行时报错如下:

/Users/tian/NeCloud/NDKWorkspace/NeBsdiff/app/src/main/cpp/bzip2/bzlib.c:1431:12: warning: implicit declaration of function '_fdopen' is invalid in C99 [-Wimplicit-function-declaration]

bzip2/bzlib.c文件中的1431行改为:

//fp = _fdopen(fd,mode2);  //1431行
fp = fdopen(fd,mode2);

nebsdiff's People

Contributors

tianyalu avatar

Stargazers

 avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.