浅析android应用增量升级
很久没有更新博客了,真是堕落啊,几次想提起笔,却总是被各种琐事耽搁,以后会多写文章记录点滴。
背景
随着android应用体积的不断增大,以及应用版本发布的不断更迭,用户的升级成了一个问题,google也意识到不断更新应用对用户流量的损耗,在Google I/O 上提及的 Smart App update,即应用增量升级,或者叫做差分升级的做法,并在新版本的Google Play中得到支持,某天在和群友聊天是扯到这方面的话题,好奇就稍微研究了一下。
增量升级的原理
今天我们就来实现类似的应用的增量升级。其实增量升级的原理很简单,即首先将应用的旧版本Apk与新版本Apk做差分,得到更新的部分的补丁,例如旧版本的APK有5M,新版的有8M,更新的部分则可能只有3M左右(这里需要说明的是,得到的差分包大小并不是简单的相减,因为其实需要包含一些上下文相关的东西),使用差分升级的好处显而易见,那么你不需要下载完整的8M文件,只需要下载更新部分就可以,而更新部分可能只有3、4M,可以很大程度上减少流量的损失。
在用户下载了差分包之后,需要在手机端将他们组合起来。可以参考的做法是先将手机端的旧版本软件(多半在/data/下),复制到SD卡或者cache中,将它们和之前的差分patch进行组合,得到一个新版本的apk应用,如果不出意外的话,这个生成的apk和你之前做差分的apk是一致的。
增量升级的操作
在了解基本的原理之后,我们来逐步解决其中的各个难点。首先是差分包patch的生成。如果做过android手机OTA升级的同学应该注意到,在update.zip中的patch文件夹中有需要与系统文件同名但是以xxx.p 为后缀的文件,他们就是生成的差分patch文件。我们可以借鉴OTA系统升级的差分生成工具来生成我们单个应用apk的差分patch文件。
OTA系统差分包的制作,使用命令:
[java]
./build/tools/releasetools/ota_from_target_files -n -i <旧包> <新包> <差分包名>
./build/tools/releasetools/ota_from_target_files -n -i <旧包> <新包> <差分包名>
小实验
多说无益,实践才是王道。下面就来简单实践一下,检测之前理论的正确性。下载实验包 (http://download.csdn.net/detail/by317966834/5260384),解压后文件如下
[html]
├── bsdiff-4.3 //bsdiff的源码路径,官网获取
│ ├── bsdiff.1
│ ├── bsdiff.c
│ ├── bspatch.1
│ ├── bspatch.c
│ └── Makefile
├── bsdiff-4.3.tar.gz
├── bsdiff4.3-win32 //windows PC端的测试工具
│ ├── Binary diff.txt
│ ├── bsdiff.exe
│ ├── bspatch.exe
│ └── LICENSE
├── bspatch //手机端的测试工具
├── iReader1.6.2.0(v35).apk // 旧版本的apk
└── iReader1.8.0.1(v40).apk //新版本的apk
以附带的iReader来做测试,在shell进入test\bsdiff4.3-win32文件夹,并下运行命令:
[html]
bsdiff.exe ../iReader1.6.2.0(v35).apk ../iReader1.8.0.1(v40).apk ../ireader.patch
原来的apk(2.94M),新版本的(3.24M),得到的patch文件为1.77M,用户需要下载的就只是1.77M,流量节省了很多。
下面先在电脑端将他们合并。
[html] view plaincopybspatch.exe ../iReader1.6.2.0(v35).apk ../new.apk ../ireader.patch
执行后得到名为new.apk 的合成版本应用,我在ubuntu下进行校验可以看出他们是一样的。
下面我们在手机端合成看看,将根目录下的bspatch(此为手机端运行的)、iReader1.6.2.0(v35).apk 和ireader.patch ,通过adb push到你有权限操作的目录,最好是在/sdcard/下,然后设置bspatch的执行权限,重复操作上述命令,可以合成新版本的apk,稍后安装查看验证版本即可,不详述。
扩展阅读
使用bsdiff 进行差分升级,还并不是最优的方式,google在它的Chromium项目中,对这个差分算法进行了优化,优化后的版本叫做小胡瓜Courgette,据说性能优化了很多不是一个数量级了,官方的一个例子:
Full update 10,385,920
bsdiff update 704,512
Courgette update 78,848
大牛们可以去研究下。
最近有些小忙,稍后有时间会对增量升级进行封装下,将合成的代码弄成一个lib库,供java调用。有兴趣的童鞋可以自己操作一下~~~
补充:
很多人不知道怎么进行封装为lib,其实这个和一般的android的C库是一样的,不明白的看看jni相关的知识,原来的测试工程已经找不到了,下边我给出个简单的例子,抛砖引玉,给大家参考下。
[java]
#include <stdio.h>
#include "com_hmg25_newstart_BSpatch.h"
#include <bzlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <unistd.h>
#include <fcntl.h>
#include <android/log.h>
static off_t offtin(u_char *buf)
{
off_t y;
y=buf[7]&0x7F;
y=y*256;y+=buf[6];
y=y*256;y+=buf[5];
y=y*256;y+=buf[4];
y=y*256;y+=buf[3];
y=y*256;y+=buf[2];
y=y*256;y+=buf[1];
y=y*256;y+=buf[0];
if(buf[7]&0x80) y=-y;
return y;
}
int applypatch(int argc,char * argv[])
{
FILE * f, * cpf, * dpf, * epf;
BZFILE * cpfbz2, * dpfbz2, * epfbz2;
int cbz2err, dbz2err, ebz2err;
int fd;
ssize_t oldsize,newsize;
ssize_t bzctrllen,bzdatalen;
u_char header[32],buf[8];
u_char *old, *new;
off_t oldpos,newpos;
off_t ctrl[3];
off_t lenread;
off_t i;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Open patch file */
if ((f = fopen(argv[3], "r")) == NULL)
err(1, "fopen(%s)", argv[3]);
/*
File format:
0 8 "BSDIFF40"
8 8 X
16 8 Y
24 8 sizeof(newfile)
32 X bzip2(control block)
32+X Y bzip2(diff block)
32+X+Y ??? bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add
补充:移动开发 , Android ,