Android 增量更新彻底解析 是增量不是热修复

1、概述

最近一直关注热修复的东西,偶尔聊天谈到了增量更新,固然了两个彻底不是一个东西。借此找了一些资料,收集整理了一下,原本是不想写博客的,由于主要都是工具的实现,可是昨晚在整理资料的时候,突然发现,我快要忘了这玩意,又要从头找一圈工具。javascript

So,权当一个记录,也方便之后本身查找。html

首先要明确的是,什么是增量更新:java

相信你们都见过在应用市场省流量更新软件,一个几百M的软件可能只须要下载一个20M的增量包就能完成更新。那么它是如何作的呢?android

就是本篇博客的主题了。git

增量更新的流程是:用户手机上安装着某个应用,下载了增量包,手机上的apk和增量包合并造成新的包,而后再次安装(注意这个过程是要从新安装的,固然部分应用市场有root权限你可能感知不到)。github

ok,那么把整个流程细化为几个关键点:微信

  1. 用户手机上提取当前安装应用的apk
  2. 如何利用old.apk和new.apk生成增量文件
  3. 增长文件与1.中的old.apk合并,而后安装

解决了上述3个问题,就ok了。app

下面开始解决,首先咱们看下增量文件的生成与合并,这个环节能够说是整个流程的核心,也是技术难点,值得开心的是,这个技术难点已经有工具替咱们实现了。ide

2、增量文件的生成与合并

这个其实就是利用工具作二进制的一个diff和patch了。工具

网址:

下载地址:

对了,本文环境为mac,其余系统若是阻碍,慢慢搜索解决便可。

下载好了,解压,切到对应的目录,而后执行make:

aaa:bsdiff-4.3 zhy$ make
Makefile:13: *** missing separator.  Stop.复制代码

恩,你没看错,报错了,这个错误还比较好解决。

解压文件里面有个文件:Makefile,以文本的形式打开,将install:下面的if,endif添加一个缩进。

修改完成是这个样子的:

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复制代码

而后,从新执行make:

aaa:bsdiff-4.3 zhy$ make
cc -O3 -lbz2    bsdiff.c   -o bsdiff
cc -O3 -lbz2    bspatch.c   -o bspatch
bspatch.c:39:21: error: unknown type name 'u_char'; did you mean 'char'?
static off_t offtin(u_char *buf)
                    ^~~~~~
                    char复制代码

此次比上次好点,此次生成了一个bsdiff,不过在生成bspatch的时候报错了,好在其实咱们只须要使用bsdiff,为何这么说呢?

由于生成增量文件确定是在服务端,或者是咱们本地pc上作的,使用的就是bsdiff这个工具;

另一个bspatch,合并old.apk和增量文件确定是在咱们应用内部作的。

固然这个问题也是能够解决的,搜索下,不少解决方案,咱们这里就不继续在这个上面浪费篇幅了。

我这里提供个下载地址:

github.com/hymanAndroi…

下载完成,直接make,bsdiff和bspatch都会生成(mac环境下)。

=============神奇的分割线==============

ok,假设到这里,无论你使用何种手段,我们已经有了bsdiff和bspacth,下面演示下这个工具的使用:

首先咱们准备两个apk,old.apk和new.apk,你能够本身随便写个项目,先运行一次拿到生成的apk做为old.apk;而后修改些代码,或者加一些功能,再运行一次生成new.apk;

  • 生成增量文件
./bsdiff old.apk new.apk old-to-new.patch复制代码

这样就生成了一个增量文件old-to-new.patch

  • 增量文件和old.apk合并成新的apk
./bspatch old.apk new2.apk old-to-new.patch复制代码

这样就生成一个new2.apk

那么怎么证实这个生成的new2.apk和咱们的new.apk如出一辙呢?

咱们能够查看下md5的值,若是两个文件md5值一致,那么几乎能够确定两个文件时如出一辙的(不要跟我较真说什么碰撞能够产生同样的md5的值~~)。

aaa:bsdiff-4.3 zhy$ md5 new.apk 
MD5 (new.apk) = 0900d0d65f49a0cc3b472e14da11bde7
aaa:bsdiff-4.3 zhy$ md5 new2.apk 
MD5 (new2.apk) = 0900d0d65f49a0cc3b472e14da11bde7复制代码

能够看到两个文件的md5果真同样~~

恩,假设你不是mac,怎么获取一个文件的md5呢?(本身写代码,下载工具,不要遇到这样的问题,还弹窗我,我会被扣工资的...)

那么到这里咱们就已经知道了如何生成增量文件和将patch与旧的文件合并为新的文件。那么咱们再次梳理下整个流程:

  1. 服务端已经作好了增量文件(本节完成)
  2. 客户端下载增量文件+提取该应用的apk,使用bspatch合并
  3. 产生的新的apk,调用安装程序

仍是蛮清晰的,那么主要是第二点,第二点有两件事,一个是提取应用的apk;一个是使用bspatch合并,那么这个合并确定是须要native方法和so文件去作的,也就是说咱们要本身打个so出来;

3、客户端的行为

(1)提取应用的apk文件

其实提取当前应用的apk很是简单,以下代码:

public class ApkExtract {
    public static String extract(Context context) {
        context = context.getApplicationContext();
        ApplicationInfo applicationInfo = context.getApplicationInfo();
        String apkPath = applicationInfo.sourceDir;
        Log.d("hongyang", apkPath);
        return apkPath;
    }
}复制代码

(2)制做bspatch so

首先声明一个类,写个native方法,以下:

public class BsPatch {

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

    public static native int bspatch(String oldApk, String newApk, String patch);

}复制代码

三个参数已经很明确了;

同时别忘了在module的build.gradle下面:

defaultConfig {
    ndk {
        moduleName = 'bsdiff'
    }
}复制代码

注意该步骤须要你配置过ndk的环境(下载ndk,设置ndk.dir)~

ok,接下来就是去完成c的代码的编写了;

首先在app/main目录下新建一个文件夹jni,把以前下载的bsdiff中的bspatch.c拷贝进去;

而后按照jni的规则,在里面新建一个方法:

JNIEXPORT jint JNICALL Java_com_zhy_utils_BsPatch_bspatch
        (JNIEnv *env, jclass cls,
         jstring old, jstring new, jstring patch){
    int argc = 4;
    char * argv[argc];
    argv[0] = "bspatch";
    argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
    argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0));
    argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));


    int ret = patchMethod(argc, argv);

    (*env)->ReleaseStringUTFChars(env, old, argv[1]);
    (*env)->ReleaseStringUTFChars(env, new, argv[2]);
    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);
    return ret;
}复制代码

方法名是有规律的,这个规律不用提了吧~~

注意bsdiff.c中并无patchMethod方法,这个方法其实是main方法,直接修改成patchMethod便可,以为复杂不要紧,文末有源码。

ok,此时你能够尝试运行,会提示依赖bzlib,其实从文件顶部的include中也能看出来。

既然依赖,那咱们就导入吧:

首先下载:

下载完成后,解压:

将其中的.h和.c文件提取出来,而后能够选择连文件夹copy到咱们module的app/main/jni下,结果以下:

记得修改bsdiff中的include:

#include "bzip2/bzlib.h"复制代码

再次运行;

而后会发现报一堆相似下面的错误:

Error:(70) multiple definition of `main'复制代码

提示main方法重复定义了,在出错信息中会给出哪些类中包含main方法,能够选择直接将这些类中的main方法直接删除。

删除之后,就ok了~~

那么到这里,咱们就完成了JNI的编写,固然文件时bsdiff提供的c源码。

4、增量更新后安装

上面的操做完成后,最后一步就简单了,首先准备两个apk:

old.apk new.apk复制代码

而后制做一个patch,下面代码中的PATCH.patch;

将old.apk安装,而后将new.apk以及PATCH.patch放置到存储卡;

最后在Activity中触发调用:

private void doBspatch() {
    final File destApk = new File(Environment.getExternalStorageDirectory(), "dest.apk");
    final File patch = new File(Environment.getExternalStorageDirectory(), "PATCH.patch");

    //必定要检查文件都存在

    BsPatch.bspatch(ApkExtract.extract(this),
            destApk.getAbsolutePath(),
            patch.getAbsolutePath());

    if (destApk.exists())
        ApkExtract.install(this, destApk.getAbsolutePath());
    }复制代码

记得开启读写SDCard权限,记得在代码中校验须要的文件都存在。

install实际就是经过Intent去安装了:

public static void install(Context context, String apkPath) {
        Intent i = new Intent(Intent.ACTION_VIEW);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        i.setDataAndType(Uri.fromFile(new File(apkPath)),
                "application/vnd.android.package-archive");
        context.startActivity(i);

    }复制代码

这里7.0可能会有问题,把路径暴露给别的app了,应该须要FileProvider去实现(未实验,猜想可能有可能)。

大体的效果图以下:

5、总结

若是你只是单纯的要使用该功能,大能够直接将生成的so文件拷入,直接loadLibrary使用便可。

其次,在作增量更新的时候,patch确定是根据你当前的版本号与最新(或者目标)版本apk,比对下发diff文件,于此同时应该也把目标apk的md5下发,再作完合并后,不要忘记校验下md5;

博客结束,虽然很简单,主要利用工具实现,可是仍是建议本身去实现一次,想一次性跑通仍是须要一些时间的,可能过程当中也会发现一些坑,也能提高本身对JNI的熟练度。

源码:

也能够选择直接使用so


欢迎关注个人微信公众号:hongyangAndroid
(欢迎关注,不要错过每一篇干货,支持投稿)

参考以及相关连接

相关文章
相关标签/搜索