1、普通更新和增量更新
首先了解一下应用普通更新的逻辑:
新版本发布后将APK文件上传到服务器。然后由安卓客户端下载新的APK文件并安装。
但是如果APK过大,普通更新的弊端就出现了。
比如:一个游戏的APK,老版本有480M。新版本添加了一个模块APK增加到500M。按照普通更新的逻辑,用户需要下载500M的APK,很显然比较费流量!这个并不是只针对用户,对服务器也是如此,服务器也需要节省带宽。
假如有一种机制:
只需要下载新版APK与老版APK多出的不同的部分,然后再由安卓客户端将老版的APK和下载的部分合并成新版APK,再进行安装更新。
例如这个例子,用户只需要下载20M左右的更新包就能进行更新操作,就能非常愉快的解决流量的这个问题了。
以上的这个例子就是增量更新的基本逻辑,用图形来表示:
根据增量更新的逻辑,可以知道增量更新重要的几个知识点。
- 服务器根据新旧APK生成的增量包。(本文重点)
- 客户端下载差分包(本文不做介绍)。
- 客户端根据旧APK和下载的差分包生成新的APK。(本文重点,需要一点点NDK的基础)
- 客户端安装新的APK。
2、增量更新的依赖
增量更新需要差分APK和合并APK,这里并不需要应用的开发者去写算法,直接可用三方提供的工具。
官方地址: www.daemonology.net/bsdiff/
不过Windows无法下载,可能被墙的缘故。我从第三方弄到的工具:
链接:拆分合并工具百度云地址
3、差分APK
差分APK的操作有多种,我这里介绍Windows平台和Linux平台两种。
3.1、Windows下的差分
将下载的工具中Windows平台下的文件bsdiff4.3-win32-src.zip解压。
bsdiff.exe 旧APK地址 新APK地址 增量包地址
差分的效果:
3.2、Linux下的差分
Linux系统同样是提供差分支持的。我这边是装了一个Ubuntu的虚拟机。由于Linux不太熟练,我这里只展示Linux差分的核心部分。
将bsdiff-4.3.tar.gz和bzip2-1.0.6.tar.gz解压。
将bsdiff-4.3.tar.gz中的bsdiff.c和bzip2-1.0.6.tar.gz中所有的.c和.h文件全部复制到一个目录中。(我这里是用windows解压的,所以放的是windows的截图)
用linux的编译命令gcc将这些文件编译成Linux中可执行的文件。(gcc是什么命令我就不介绍了)。接下来会踩几个坑。 执行:
gcc -g -o axe_bsdiff blocksort.c decompress.c bsdiff.c randtable.c bzip2.c huffman.c compress.c bzlib.c crctable.c
里面的axe_bsdiff是Linux端可运行文件的文件名,可修改。执行这段命令就会遇到以下问题:
include "bzlib.h"
然后保存!
解决这个问题后继续执行gcc命令。
客户端合并也需要这些修改后的源文件,已上传git。最后有链接
修改完成之后,继续执行gcc编译命令。生成了axe_bsdiff的可执行文件!
接下来的步骤就和windows端差分增量包操作差不多了。执行命令:
./axe_bsdiff 旧APK路径 新APK路径 增量包路径
将新旧APK都复制到了这个文件夹,然后执行命令:
总体来说Linux差分增量包要比Windows差分增量包麻烦些。还可能会遇到权限不够的问题,这个时候需要chmod命令等等。
4、客户端的合并操作
这里还是需要用到bsdiff-4.3.tar.gz和bzip2-1.0.6.tar.gz的解压文件。这里需要用到bsdiff-4.3中的bspatch.c还有bzip2-1.0.6所有的.c和.h。上面讲到的Linux中差分增量包的文件,这里的可以直接使用。
4.1、新建NDK项目 (这个就不讲解了)
4.2、将所需要的文件导入并且配置好CMakeLists.txt。
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
# 加载指定文件夹的所有文件
file(GLOB bzip2 src/main/cpp/bzip2/*.c)
find_library( log-lib
log )
# 配置AxeBsPatch动态库
add_library( AxeBsPatch
SHARED
src/main/cpp/bspatch.c
${bzip2}
)
# 链接AxeBsPatch动态库
target_link_libraries( AxeBsPatch
${log-lib}
我这边就稍微截个图。
4.3、新建native方法。
这边生成的native方法需要传入三个参数,老的APK地址、新的APK地址、下载的增量包的地址。
public class BsPatch {
public native static int patch(String oldFile,String newFile,String patchFile);
static {
System.loadLibrary("AxeBsPatch");
}
}
然后用javah命令生成.h文件。
4.4、实现.h文件中的方法
在bspatch.c中include生成的头文件。并实现.h中的方法。
#include "com_mg_axechen_bspatch_BsPatch.h"
// ... ... 省略若干代码
/*
* Class: com_mg_axechen_bspatch_BsPatch
* Method: patch
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
* params: old_path:The path for old APK.
* new_path:The path for new APK.
* patch_path:The path for patch package.
*/
JNIEXPORT jint JNICALL Java_com_mg_axechen_bspatch_BsPatch_patch
(JNIEnv *env, jclass jclz, jstring old_path, jstring new_path, jstring patch_path) {
// 先将jstring转化为char
char *oldPath = (char *) (*env)->GetStringUTFChars(env, old_path, NULL);
char *newPath = (char *) (*env)->GetStringUTFChars(env, new_path, NULL);
char *patchPath = (char *) (*env)->GetStringUTFChars(env, patch_path, NULL);
char *argv[4];
int ret = -1;
// 第一个参数随便填写
argv[0] = "AXE_BSPATCH";
argv[1] = oldPath;
argv[2] = newPath;
argv[3] = patchPath;
// 第一个参数必须为4.
ret = main(4, argv);
// 回收资源
(*env)->ReleaseStringUTFChars(env, old_path, oldPath);
(*env)->ReleaseStringUTFChars(env, new_path, newPath);
(*env)->ReleaseStringUTFChars(env, patch_path, patchPath);
return ret;
}
这边也比较简单,将传入的参数放入数组中,然后调用bspatch中的main方法即可。
4.5、更新的逻辑判断
为了模拟得更像更新的逻辑,我加入了简单的VersionCode的判断。 老版APK的VersionCode是1 ,新版的版本VersionCode是2。如果是老版才更新。
因为更新耗时,里面包含了下载和合并。所以放在线程中。 如果合并成功,就通过Hanlder发送通知去安装新的APK。 我这里没有下载的逻辑,所以先将增量包放在了安装客户端sdcard的指定路径!
/**
* 更新操作
*
* @param view
*/
public void upload(View view) {
if (APKUtils.getVersionCode(MainActivity.this, getPackageName()) >= 2) {
//
new AlertDialog.Builder(MainActivity.this)
.setMessage("已经是最新的版本了,无需更新!")
.setNegativeButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
return;
}
new Thread(new Runnable() {
@Override
public void run() {
// 下载就省略。 由于我没有服务器进行下载
// 1、拿到老的APK
String oldFile = APKUtils.getSourceApkPath(MainActivity.this, getPackageName());
// 2、拿增量包的路径
String patchFile = Contants.PATCH_FILE_PATH;
Log.i("AXE", patchFile);
// 3、开始执行合并操作
int ret = BsPatch.patch(oldFile, Contants.NEW_APK_PATH, patchFile);
// 4、合并的成功和失败
if (ret == 0) {
Log.i("AXE", "合并成功");
handler.sendEmptyMessage(0);
} else {
Log.i("AXE", "合并失败");
}
}
}).start();
}
4.6、安装APK
7.0后的APK安装有很大的改变。看下官方的解释吧。
具体实现我也不贴出来了,这里直接贴出其他人的博客链接。
blog.csdn.net/czhpxl007/a…
5、增量更新的优缺点
优点:
- 很明显,能节约流量,节省服务器开支。
缺点:
- 客户端和服务端需要加入相应的支持。每次发布新版本,服务端都需要为以前所有的老版本生成对应的差分包,并根据客户端端请求返回对应的更新包,维护过程将会变得相对复杂。客户端需要对差分包做更为详细的验证,防止出错,除此之外,客户端应该可以根据服务端更新开关来确定当前是使用完整更新还是增量更新。
- apk包之间的差异过小时,比如2m以下,此时生成的差分包仍然有几百k,此时使用增量更新得不偿失,毕竟形成差分包和合并的过程都非常耗时。另外,但版本之间变化非常大的时候,通常是是大版本好变化的时候,比如从v 1.0.0到2.0.0,此时使用完整更新也不错。
以上缺点引用CSDN博客:
http://blog.csdn.net/dd864140130/article/details/52928419
作者:江湖人称小白哥
6、最后
本文讲解了增量更新的基本逻辑、差分操作(Windows/Linux)、合并的操作(客户端)、7.0后安装APK的改变。
客户端合并的源代码: 源码传送门