增量更新

栏目: C++ · 发布时间: 5年前

内容简介:增量更新现在大多数热门应用中都使用了增量更新来更新新的功能。比如解压微信或者抖音的apk,在其lib文件夹下都能找到类似 libbspatch.so的动态库,这个就是用来增量更新的库。Android NDK中为我们提供了一个工具可以查看动态库中的方法,工具在\sdk\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin

增量更新

现在大多数热门应用中都使用了增量更新来更新新的功能。比如解压微信或者抖音的apk,在其lib文件夹下都能找到类似 libbspatch.so的动态库,这个就是用来增量更新的库。

Android NDK中为我们提供了一个 工具 可以查看动态库中的方法,工具在\sdk\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin

进入到此文件夹下面执行下面的方法就能看到so中的方法了。

arm-linux-androideabi-nm.exe  -D  so的路径

增量更新使用到一个开源库bsdiff,bsdiff是一个查分算法,原理是旧文件跟新文件对比,尽可能多的利用old文件中已经有的内容,尽可能少的加入新的内容来构建new文件。

通常的做法是对旧文件和新文件做字符串匹配或者使用hash技术提取公共部分,然后把新文件的剩余部分打成patch包(差分包中记录着新内容相对旧内容的偏移地址),在Patch阶段中用copying和insertion两个操作把旧文件和patch文件合成新文件。

增量更新的流程:在服务器端,使用bsdiff工具把旧的apk和新的apk进行比对得到差分包patch包,通过网络下载到本地,通过bspatch工具把本地旧的apk和patch包合成新的apk包。最后安装新的apk

bsdiff 下载地址: http://www.daemonology.net/bsdiff/ 现在最新的是bsdiff-4.3

将下载的文件上传到服务器,解压进入bsdiff-4.3文件夹,执行make命令编译文件,发现会出错。

是因为Makefile文件中的格式不正确

CFLAGS          +=      -O3 -lbz2

PREFIX          ?=      /usr/local
INSTALL_PROGRAM ?=      ${INSTALL} -c -s -m 555
INSTALL_MAN     ?=      ${INSTALL} -c -m 444

all:            bsdiff bspatch
bsdiff:         bsdiff.c
bspatch:        bspatch.c
install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
.ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
.endif

//上面是错误的 .ifndef和.endif前面需要TAB键缩进一下

CFLAGS          +=      -O3 -lbz2

PREFIX          ?=      /usr/local
INSTALL_PROGRAM ?=      ${INSTALL} -c -s -m 555
INSTALL_MAN     ?=      ${INSTALL} -c -m 444

all:            bsdiff bspatch
bsdiff:         bsdiff.c
bspatch:        bspatch.c

install:
        ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
        .ifndef WITHOUT_MAN
        ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
        .endif

修改完后继续编译还是报错,找不到<bzlib.h>,因为bsdiff依赖了bzip库

fatal error: bzlib.h: No such file or directory
 #include <bzlib.h>

下安装bzip2

//Linux
yum install  bzip2-devel.x86_64
//Ubuntu
apt install libbz2-dev
//Mac
brew install bzip2

然后在执行make命令,成功,bsdiff-4.3文件夹下面生成了两个可执行文件bsdiff和bspatch

把old.apk和new.apk,上传到此文件夹,执行下面命令就可以生成差分包

bsdiff old.apk new.apk patch

将patch差分包下载到手机中跟旧的apk合并成新的安装包。

手机方面需要把bspatch继承到我们的项目中才能合并

AndroidStudio中新建一个C++文件,前面解压缩的bsdiff-4.3中有bspatch.c文件,将他拷贝到cpp文件夹下面。

编译之后会报错,因为前面我们知道bsdiff依赖了bzip库,linux系统中我们可以直接安装,AndroidStudio中,我们需要自己下载编译,可以在 Linux 中变异成静态文件导入,不过由于它的文件比较少,我们可以直接导入源码。

bzip2的地址: https://sourceforge.net/projects/bzip2/

http://www.bzip.org/downloads.html

下载之后解压,我们看到里面的文件也是挺多的,我们并不需要全部的文件,那需要哪些呢。我们可以看到它有一个Makefile文件,打开它,从代码中可以看到

libbz2.a: $(OBJS)

OBJS= blocksort.o  \
      huffman.o    \
      crctable.o   \
      randtable.o  \
      compress.o   \
      decompress.o \
      bzlib.o

libbz2.a 这个静态文件可以通过OBJS中的这些文件编译成,所以我们只需要这几个c文件就好了。cpp下新建一个bzip文件夹。把他们也复制到该文件夹加下

下一步配置CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

file(GLOB bzip_source ${CMAKE_SOURCE_DIR}/bzip/*.c)
add_library(
        bspatcher

        SHARED

        ${CMAKE_SOURCE_DIR}/bspatcher.cpp
        ${CMAKE_SOURCE_DIR}/bspatch.c
        ${bzip_source})

find_library(
        log-lib
        log)


target_link_libraries(
        bspatcher
        ${log-lib})

bspatcher是我们自己的cpp文件

下面开始编写自己的 java 文件和bspatcher这个cpp文件

public class BsPatcher {

    static {
        System.loadLibrary("bspatcher");
    }

    /**
     * 合成安装包
     *
     * @param oldApk 旧版本安装包,如1.1.1版本
     * @param patch  差分包,Patch文件
     * @param output 合成后新版本apk的输出路径
     */
    public static native void bsPatch(String oldApk, String patch, String output);

}

bspatcher文件

#include <jni.h>
#include <string>
#include<android/log.h>
// extern 声明在 bspatch.c
extern "C" {
extern int p_main(int argc, const char *argv[]);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_chs_bsdiff_BsPatcher_bsPatch(JNIEnv *env, jclass type,
                                      jstring oldApk_, jstring patch_,
                                      jstring output_) {
    // 将Java字符串转为C/C++的字符串,转换为UTF-8格式的char指针
    const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
    const char *patch = env->GetStringUTFChars(patch_, 0);
    const char *output = env->GetStringUTFChars(output_, 0);
    __android_log_print(ANDROID_LOG_ERROR,"BSPATCH",oldApk,patch,output);
    // bspatch, oldfile, newfile, patchfile
    const char *argv[] = {"", oldApk, output, patch};
    p_main(4, argv);

    // 释放指向Unicode格式的char指针
    env->ReleaseStringUTFChars(oldApk_, oldApk);
    env->ReleaseStringUTFChars(patch_, patch);
    env->ReleaseStringUTFChars(output_, output);
}

非常简单,从java层把old.apk的路径,patch包的路径,new.apk的说出路径传进来然后传入bspatch.c的main方法中即可完成合并。我们把p_main中的main方法改个名字改成p_main,方便和main方法区分。

最后在Activity中开启线程下载patch包到本地,合成新包,并安装新包,比如使用AsyncTask下载

   new AsyncTask<Void, Void, File>() {
            @Override
            protected File doInBackground(Void... voids) {
                String patch = new File(Environment.getExternalStorageDirectory(), "patch").getAbsolutePath();
                 // 获取旧版本路径(正在运行的apk路径)
                String oldApk = getApplicationInfo().sourceDir;
                String output = createNewApk().getAbsolutePath();
                if (!new File(patch).exists()) {
                    return null;
                }
                BsPatcher.bsPatch(oldApk, patch, output);
                return new File(output);
            }

            @Override
            protected void onPostExecute(File file) {
                super.onPostExecute(file);
                Log.e("output---->>", "onPostExecute");
                // 已经合成了,调用该方法,安装新版本apk
                if (file != null) {
                    if (!file.exists()) return;
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        Uri fileUri = FileProvider.getUriForFile(MainActivity.this, MainActivity.this.getApplicationInfo().packageName + ".fileprovider", file);
                        intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
                    } else {
                        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                    }
                    MainActivity.this.startActivity(intent);
                } else {
                    Toast.makeText(MainActivity.this, "差分包不存在!", Toast.LENGTH_LONG).show();
                }
            }
        }.execute();
        
private File createNewApk() {
        File newApk = new File(Environment.getExternalStorageDirectory(), "new.apk");
        if (!newApk.exists()) {
            try {
                newApk.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return newApk;
    }

到这里就更新完成了。

缺点:

我们不能保证所有用户都能升级完成,比如我们最新的patch包是2.0版本和3.0版本差分出来的,如果用户此时用的1.0版本,那就无法升级成功,所以还要做一个1.0和3.0之间的差分包。随着版本的越来越多,需要做的差分包也越来越多。可以在Linux中写一个自动的脚本来完成。

如果差分包在下载的过程中被篡改也无法合成成功,可以下载完后通过md5 或者其他方式对patch包进行完整性的校验。


以上所述就是小编给大家介绍的《增量更新》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

快速转行做产品经理

快速转行做产品经理

李三科 / 华中科技大学出版社 / 2018-6-1 / 39.90

互联网已经进入以产品为中心的时代,不懂技术一样做高薪产品经理。本书将满足你转行、就业、加薪的愿望。 . 作者李三科,互联网资深产品经理。2011年离开传统销售行业进入互联网行业工作,从对产品经理的工作一无所知,到成长为一名年薪几十万的资深产品经理,他对产品经理职业有着深刻的理解,也积累了丰富的学习、工作经验。本书以作者亲身经历为线索,讲解学习产品经理相关知识和工作方法的经验,同时介绍求......一起来看看 《快速转行做产品经理》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试