乐趣区

详解Android增量更新

一、Android 增量更新简介

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

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

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

增量更新原理简单来讲,就是通过二进制算法找出新版本和旧版本安装包的差异,并将差异提取出来生成所谓的补丁包(差分包)。移动端检查更新时,只需要向服务器申请下载相对应的补丁包,然后将差分包与自身旧版本 apk 合并生成最新版本的 apk。最终安装合并生成的 apk 即可完成版本更新。

功能的实现主要是借助第三方差分工具,相当于做记录,方便以后回忆。

二、增量更新过程的演示

增量更新主要有两个过程,第一是后台将最新的 apk(v1.0.1)与旧版本 (v1.0.0) 做对比,并 生成差分包。第二步是移动端下载相对应的差分包,并在本地合并生成完整的最新 apk,并引导用户安装。

对于 apk 包的差分和合并,需要借助 bsdiff 工具来实现

下载 bsdiff_win_exe.zip 并解压到本地

其中 bsdiff.exe 是 window 用来生成差分包的工具,而 bspatch 是用来合成差分包的工具

2.1 生成差分包

首先,分别打一个旧的 apk(v1.0.0.apk),和一个新的 apk(v1.0.1.apk),并存放在之前解压的目录。

然后打开 Windows 命令行工具,切换到该目录,打入命令 bsdiff v1.0.0.apk v1.0.1.apk patch.patch

就能在目录下看见生成的差分包 patch.patch

2.2 合成差分包

保持在当前目录,命令行输入 bspatch v1.0.0.apk new.apk patch.patch

就能在当前目录下看见合成的完整安装包 new.apk

生成的 new.apk 与 v1.0.1.apk 是完全相同的,安装即可。

三、Android 端应用增量更新

上述过程是为了演示整个流程,现在要应用到 Android 端。Android 移动端在增量更新过程中,只负责下载和合并差分包,并引导用户安装。因此假设已经下载好对应的差分包。主要需要三步:第一是获取本身旧 apk 路径;第二是将差分工具集成到 Android 端;第三是调用方法合并差分包并引导用户安装。

3.1 获取已安装的旧 apk 路径

// 获取现在运行的 apk 路径
String oldApk = getApplicationInfo().sourceDir;

3.2 集成差分合并功能到 Android 端

bsdiff 开源工具源码为.c 文件,即 Android 端需要配置 JNI(以下是 基于 CMake)。

为方便集成,已将所需功能打成 so 包,可以直接导入 so 包,跳过此步骤。百度网盘链接(提取码:8ia7)

bsdiff 源码依赖 bszip,所以需要同时下载两者的源码;或者百度网盘下载(提取码:psbu)

3.2.1 导入所需源文件

  • 解压 bsdiff-4.3.tar.gz 文件,将 bspatch.c 文件复制到 Android 的 cpp 目录中

  • 解压 bzip2-1.0.6.tar.gz 文件,并按图将所需源文件复制到 Android 的 cpp/bzip 目录中

  • 修改 bspatch.c 文件内容

(1)修改 bzlib.h 的导入声明: 打开 bspatch.c 文件,修改头文件

(2)找到 main 方法,并将其改名为 execute_patch

3.2.2 配置 CMakLists.txt 文件

cmake_minimum_required(VERSION 3.4.1)
# 查找文件系统中指定模式的路径,如 /* 匹配根目录的文件(注意路径)file(GLOB bzip_source ${CMAKE_SOURCE_DIR}/bzip/*.c)
# 设置本地动态库 编译生成动态库
add_library(
 #模块名
 native-lib
 # 动态库 / 分享可以
 SHARED
 #源文件
 native-lib.cpp
 #配置相应的文件引用
 bspatch.c
 ${bzip_source}
)
find_library(
 log-lib
 log)
target_link_libraries(
 native-lib
 ${log-lib})

3.2.3 往 native-lib.cpp 里添加调用方法

extern "C" {extern int execute_patch(int argc, char *argv[]);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_utilities_BsPatchUtil_patch(JNIEnv *env, jobject instance, jstring oldApk_,
 jstring patch_, jstring output_) {
 // 将 java 字符串转换成 char 指针
 const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
 const char *patch = env->GetStringUTFChars(patch_, 0);
 const char *output = env->GetStringUTFChars(output_, 0);
 //bspatch ,oldfile ,newfile ,patchfile
 char *argv[] = {"", const_cast<char *>(oldApk), const_cast<char *>(output),
 const_cast<char *>(patch)};
 execute_patch(4, argv);
 // 释放相应的指针 gc
 env->ReleaseStringUTFChars(oldApk_, oldApk);
 env->ReleaseStringUTFChars(patch_, patch);
 env->ReleaseStringUTFChars(output_, output);
}

注意:Java_com_example_myapplication_utilities_BsPatchUtil_patch 方法名需要根据实际定义更改

3.3 定义方法:Java 层使用合成功能

定义个 BsPatchUtil 类,调用 c 代码:(该方法名因与上面提到的 Java_com_example_myapplication_utilities_BsPatchUtil_patch 方法名相对应)public class BsPatchUtil {
 static {System.loadLibrary("native-lib");
 }
 /**
 * @param oldApkPath 旧 apk 文件路径
 * @param newApkPath 新 apk 文件路径
 * @param patchPath 生成的差分包的存储路径
 */
 public static native void patch(String oldApkPath, String newApkPath, String patchPath);
}

3.4 合成差分包

// 已安装的旧 apk 的路径
val oldApk = applicationInfo.sourceDir
// 差分包的路径
val patch = ...
// 合成之后的新 apk 的存储路径
val output = ...
// 合成 apk 包
BsPatchUtil.patch(oldApk, patch, output)

合成差分包需要在子线程中运行,防止阻塞主线程。最终引导用户安装新生成的 apk 即可。

Android 学习 PDF+ 架构视频 + 面试文档 + 源码笔记

最后

感谢大家能耐着性子,看完这篇文章。

在这里我也分享一份自己收录整理的 Android 学习 PDF+ 架构视频 + 面试文档 + 源码笔记,还有 高级架构技术进阶脑图、Android 开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

如果你有需要的话,可以 点赞 关注我,然后加入Android 开发交流群(820198451)免费领取

退出移动版