Android增量更新原理和实践

demo地址
https://github.com/po1arbear/bsdiff-android

已验证过与tinker的兼容性,支持manifest修改,支持activity新增,如有其它风险和隐藏漏洞,欢迎告知 ^ ^

最近apk的更新有些频繁,各个系统发版都要向用户推送一波更新,每次都要全量下载,流量消耗大,用户等待时间长,打开小米应用商店,发现大部分app更新包都比实际的包要小,于是研究了一下,发现是使用的增量更新,了解了其原理并运用到项目中实践

一、 什么是增量更新?

首先需要明确,Android增量更新热修复不同的技术概念。

热修复一般是用于当已经发布的app有Bug需要修复的时候,开发者修改代码并发布补丁,让应用能够在不需要重新安装的情况下实现更新,主流方案有Tinker、AndFix等。

而增量更新的目的是为了减少更新app所需要下载的包体积大小,常见如手机端游戏,apk包体积为几百M,但有时更新只需下载十几M的安装包即可完成更新。

二、增量更新原理

image.png

自从 Android 4.1 开始, Google Play 引入了应用程序的增量更新功能,App使用该升级方式,可节省约2/3的流量。

Smart app updates is a new feature of Google Play that introduces a better way of delivering app updates to devices. When developers publish an update, Google Play now delivers only the bits that have changed to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about 1/3 the sizeof a full APK update.

三、应用市场现状

笔者使用的小米手机,可以看到,小米的应用商店已经开始支持增量更新,会比原有的方式节省超过一半的流量

image.png

四、实现方案

  • 服务端

服务端的同学拿到客户端同学开发的新版本A,跟已发布的旧版本B1,B2,B3...做了差分生成相应的差分包C1,C2,C3...,并生成相应差分包的MD5值

  • 客户端
  1. 客户端用版本号作为参数向服务端请求更新数据,若服务端没有差分包或者差分包大小比全量包大时,则返回全量包下载URL、MD5值

  2. 若服务端存在相应的差分包则返回差分包下载URL,全量包和差分包MD5值,全量包签名值和MD5值。把差分包下载到本地之后(C1),先做MD5值校验,确保下载的差分包数据的完整性,校验失败则走全量更新逻辑,校验无误和本地现有安装的旧版本(B1)进行差分合并生成新版本(A),之后进行合成版本的MD5值校验和签名校验,确保合成文件的完整性和签名信息的正确性。校验无误后再进行安装。

五、操作步骤

macOS操作验证
  1. 安装
    brew install bsdiff
  2. 准备oldfile和newfie
  3. 生成差量文件
    bsdiff oldfile newfile patchfile
  4. 合成新包
    bspatch oldfile newfile patchfile
Android上的实现

因为差量包是从接口获取的,所以客户端只需要处理bspatch的过程,合成新的apk文件然后安装即可

1.bsdiff下载

bsdiff下载后,解压bsdiff-4.3.tar.gz,取出目录中的bspatch.c文件,我们要用的就是这个文件中的bspatch_main方法。

  1. bzip2下载
    取出文件blocksort.cbzip2.cbzlib.cbzlib.hbzlib_private.hcompress.ccrctable.cdecompress.chuffman.crandtable.c,因为bsdiff的编译需要依赖bzip2,所以需要这些c文件。

  2. 将bspatch.c以及bzip的相关代码拷贝到jni目录下

image.png
  1. 编写update-lib.cpp

    
    #include <jni.h>
    #include <string>
    #include <android/log.h>
    #include <exception>
    
    #include "patchUtils.h"
    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_fcbox_hivebox_update_PatchUtils_patch(JNIEnv *env, jclass type, jstring oldApkPath_,
                                               jstring newApkPath_, jstring patchPath_) {
    
        int argc = 4;
        char *ch[argc];
        ch[0] = (char *) "bspatch";
        ch[1] = const_cast<char *>(env->GetStringUTFChars(oldApkPath_, 0));
        ch[2] = const_cast<char *>(env->GetStringUTFChars(newApkPath_, 0));
        ch[3] = const_cast<char *>(env->GetStringUTFChars(patchPath_, 0));
    
    
        int ret = applypatch(argc, ch);
        __android_log_print(ANDROID_LOG_INFO, "ApkPatchLibrary", "applypatch result = %d ", ret);
    
    
        env->ReleaseStringUTFChars(oldApkPath_, ch[1]);
        env->ReleaseStringUTFChars(newApkPath_, ch[2]);
        env->ReleaseStringUTFChars(patchPath_, ch[3]);
    
    
        return ret;
    }
    
  2. 编写PatchUtils.java

public class PatchUtils {

    // Used to load the 'native-lib' library on application startup.
    static {
      System.loadLibrary("update-lib");
    }

    /**
     * native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
     *
     * 返回:0,说明操作成功
     *
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param outputApkPath 示例:/sdcard/output.apk
     * @param patchPath  示例:/sdcard/xx.patch
     * @return
     */
    public static native int patch(String oldApkPath, String outputApkPath,
        String patchPath);

  }
  1. 调用bspatch生成新的apk
 private void genNewApk() {
    String oldpath = getApplicationInfo().sourceDir;
    String newpath = (Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
        + "composed_hivebox_apk.apk");

    String patchpath = (Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
        + "bs_patch");
    PatchUtils.patch(oldpath, newpath, patchpath);

  }

五、与Tinker的差异

首先简单了解下Dex文件,大家在反编译的时候,都清楚apk中会包含一个或者多个*.dex文件,该文件中存储了我们编写的代码,一般情况下我们还会通过工具转化为jar,然后通过一些工具反编译查看。

jar文件大家应该都清楚,类似于class文件的压缩包,一般情况下,我们直接解压就可以看到一个个class文件。而dex文件我们无法通过解压获取内部的一个个class文件,说明dex文件拥有自己特定的格式:

dex对JAVA类文件重新排列,将所有JAVA类文件中的常量池分解,消除其中的冗余信息,重新组合形成一个常量池,所有的类文件共享同一个常量池,使得相同的字符串、常量在DEX文件中只出现一次,从而减小了文件的体积。

微信通过深入Dex格式,实现一套diff差异小,内存占用少以及支持增删改的算法

它格式无关,但对Dex效果不是特别好,当前微信对于so与部分资源,依然使用bsdiff算法

image.png

核心思想:

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

推荐阅读更多精彩内容

  • 增量更新在Android开发中是一种很常见的技术。 增量更新的原理 增量更新的原理非常简单,就是将本地apk与服务...
    re冷星阅读 1,525评论 3 3
  • 首先增量更新应该了解个概念:增量更新:在版本较近的apk升级的时候,根据两个apk之间的差异(生成差异包),合成新...
    笔墨Android阅读 1,015评论 3 8
  • 增量更新 概述 简单点来说就是两个文件:old和new,可通过差分工具生成它们的差分包patch,当我们需要时,再...
    DoubleD_谱阅读 861评论 0 0
  • 上一节我们学习了NDK来处理文件的拆分和合并操作,那时候我们纯手工来敲C语言的代码,今天我们来用C语言代码搞搞ND...
    Lypop阅读 292评论 0 0
  • 一、概述 增量更新相较于全量更新的好处不言而喻,利用差分算法获得1.0版本到2.0版本的差分包,这样在安装了1.0...
    咸鱼Jay阅读 2,412评论 0 3