随着现在手机硬件不断的提升,分辨率提高手机的安装包也是越来越大了。当年NOKIA,MOTO时代,一个手机APP如果有1MB那都是算大 的,2MB已经不得了了。虽然网络、存储都已经大大提升,但是流量还不至于廉价到APP改了一个标题要去下载一个几兆的程序安装包。今天就介绍安卓增量下 载的实现。有耐心的先看原理,后面实践!
增量升级的原理
今天我们就来实现类似的应用的增量升级。其实增量升级的原理很简单,即首先将应用的旧版本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系统 差分包的制作,使用命令:
./ota_from_target_files -n -i <旧包> <新包> <差分包名>
在查阅ota_from_target_files 的代码可知,是在函数WriteIncrementalOTAPackage里生成差分包的,在这个函数里边创建了common.Difference这 个类,我们继续跟进,在common.py中的类 class Difference(object):里可以看到:
diff_program = DIFF_PROGRAM_BY_EXT.get(ext, “bsdiff”)
至此我们就看到了android中提供我们用来制作差分增量升级包的工具,”bsdiff”,这是一个很牛X开源的二进制差分工具.相关的介绍传送 门 相关的代码地址 或者在android的代码目录下 \external\bsdiff bsdiff是二进制差分工具,其对应的bspatch是相应的补丁合成工具 需要注意的是增量升级的补丁包,是需要在服务器端,即PC端完成,大致流程如,制作补丁时调用bsdiff函数,根据两个不同版本的二进制文件,生成补丁 文件。
命令:bsdiff oldfile newfile patchfile
例如: bsdiff xx_v1.0.apk xx_v2.0.apk xx.patch
将生成的补丁包 xx.patch放置在升级服务器上,供用户下载升级,对应多版本需要对不同的版本进行差分,对于版本跨度较大的,建议整包升级。 用户在下载了 xx.patch补丁包后,需要用到补丁所对应的apk,即原来系统安装的旧版本apk和补丁合成的bspatch工具。系统旧版本的apk可以通过 copy系统data/app目录下的apk文件获取,而补丁合成的bspatch可以通过将bspatch源码稍作修改,封装成一个so库,供手机端调 用。
bspatch的命令格式为:
bspatch oldfile newfile patchfile
和差分时的参数一样。合成新的apk便可以用于安装。 以上只是简单的操作原理,增量升级还涉及很多其他方面,例如,升级补丁校验等问题,可以参考android源码中bootable\recovery \applypatch的相关操作,本文只是浅析,在此不表。 不足 增量升级并非完美无缺的升级方式,至少存在以下两点不足: 1.增量升级是以两个应用版本之间的差异来生成补丁的,你无法保证用户每次的及时升级到最新,所以你必须对你所发布的每一个版本都和最新的版本作差分,以 便使所有版本的用户都可以差分升级,这样操作相对于原来的整包升级较为繁琐,不过可以通过自动化的脚本批量生成。 2.增量升级成功的前提是,用户手机端必须有能够让你拷贝出来且与你服务器用于差分的版本一致的apk,这样就存在,例如,系统内置的apk无法获取到, 无法进行增量升级;对于某些与你差分版本一致,但是内容有过修改的(比如破解版apk),这样也是无法进行增量升级的,为了防止合成补丁错误,最好在补丁 合成前对旧版本的apk进行sha1sum校验,保证基础包的一致性。 小实验 多说无益,实践才是王道。下面就来简单实践一下,检测之前理论的正确性。
├── 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 //手机端的测试工具
├── oldAPK1.6.2.apk // 旧版本的apk
└── newAPK1.8.0.apk //新版本的apk
APK来做测试,在shell进入test\bsdiff4.3-win32文件夹,并下运行命令:
1 |
bsdiff.exe oldAPK1.6.2.apk newAPK1.8.0.apk apk.patch |
原来的apk(2.94M),新版本的(3.24M),得到的patch文件为1.77M,用户需要下载的就只是1.77M,流量节省了很多。 下面先在电脑端将他们合并。
bspatch.exe oldAPK1.6.2.apk new.apk apk.patch
执行后得到名为new.apk 的合成版本应用。这个和我们newAPK1.8.0.apk其实是一样的。
现在写一个安卓小DEMO出来,测试一下这个工具。直接在创建安卓工程的时候添加native支持,在CPP文件中添加以下代码
#include "com_droidupdate_jni_PatchUtil.h" #include "bzlib_private.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, const 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 *oldStr, *newStr; 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 x bytes from oldfile to x bytes from the diff block; copy y bytes from the extra block; seek forwards in oldfile by z bytes". */ /* Read header */ if (fread(header, 1, 32, f) < 32) { if (feof(f)) errx(1, "Corrupt patch\n"); err(1, "fread(%s)", argv[3]); } /* Check for appropriate magic */ if (memcmp(header, "BSDIFF40", 8) != 0) errx(1, "Corrupt patch\n"); /* Read lengths from header */ bzctrllen = offtin(header + 8); bzdatalen = offtin(header + 16); newsize = offtin(header + 24); if ((bzctrllen < 0) || (bzdatalen < 0) || (newsize < 0)) errx(1, "Corrupt patch\n"); /* Close patch file and re-open it via libbzip2 at the right places */ if (fclose(f)) err(1, "fclose(%s)", argv[3]); if ((cpf = fopen(argv[3], "r")) == NULL) err(1, "fopen(%s)", argv[3]); if (fseeko(cpf, 32, SEEK_SET)) err(1, "fseeko(%s, %lld)", argv[3], (long long) 32); if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); if ((dpf = fopen(argv[3], "r")) == NULL) err(1, "fopen(%s)", argv[3]); if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) err(1, "fseeko(%s, %lld)", argv[3], (long long) (32 + bzctrllen)); if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); if ((epf = fopen(argv[3], "r")) == NULL) err(1, "fopen(%s)", argv[3]); if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) err(1, "fseeko(%s, %lld)", argv[3], (long long) (32 + bzctrllen + bzdatalen)); if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); if (((fd = open(argv[1], O_RDONLY, 0)) < 0) || ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || ((oldStr = (u_char*) malloc(oldsize + 1)) == NULL) || (lseek(fd, 0, SEEK_SET) != 0) || (read(fd, oldStr, oldsize) != oldsize) || (close(fd) == -1)) err(1, "%s", argv[1]); if ((newStr = (u_char*) malloc(newsize + 1)) == NULL) err(1, NULL); oldpos = 0; newpos = 0; while (newpos < newsize) { /* Read control data */ for (i = 0; i <= 2; i++) { lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8); if ((lenread < 8) || ((cbz2err != BZ_OK) && (cbz2err != BZ_STREAM_END))) errx(1, "Corrupt patch\n"); ctrl[i] = offtin(buf); }; /* Sanity-check */ if (newpos + ctrl[0] > newsize) errx(1, "Corrupt patch\n"); /* Read diff string */ lenread = BZ2_bzRead(&dbz2err, dpfbz2, newStr + newpos, ctrl[0]); if ((lenread < ctrl[0]) || ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) errx(1, "Corrupt patch\n"); /* Add old data to diff string */ for (i = 0; i < ctrl[0]; i++) if ((oldpos + i >= 0) && (oldpos + i < oldsize)) newStr[newpos + i] += oldStr[oldpos + i]; /* Adjust pointers */ newpos += ctrl[0]; oldpos += ctrl[0]; /* Sanity-check */ if (newpos + ctrl[1] > newsize) errx(1, "Corrupt patch\n"); /* Read extra string */ lenread = BZ2_bzRead(&ebz2err, epfbz2, newStr + newpos, ctrl[1]); if ((lenread < ctrl[1]) || ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) errx(1, "Corrupt patch\n"); /* Adjust pointers */ newpos += ctrl[1]; oldpos += ctrl[2]; }; /* Clean up the bzip2 reads */ BZ2_bzReadClose(&cbz2err, cpfbz2); BZ2_bzReadClose(&dbz2err, dpfbz2); BZ2_bzReadClose(&ebz2err, epfbz2); if (fclose(cpf) || fclose(dpf) || fclose(epf)) err(1, "fclose(%s)", argv[3]); /* Write the new file */ if (((fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0) || (write(fd, newStr, newsize) != newsize) || (close(fd) == -1)) err(1, "%s", argv[2]); free(newStr); free(oldStr); return 0; } jint JNICALL Java_com_droidupdate_jni_PatchUtil_applyPatchToOldApk(JNIEnv *pEnv, jclass clazz, jstring oldPath, jstring newPath, jstring patchPath) { const char* pOldPath = pEnv->GetStringUTFChars(oldPath, JNI_FALSE); const char* pNewPath = pEnv->GetStringUTFChars(newPath, JNI_FALSE); const char* pPatchPath = pEnv->GetStringUTFChars(patchPath, JNI_FALSE); const char* argv[4]; argv[0] = "bspatch"; argv[1] = pOldPath; argv[2] = pNewPath; argv[3] = pPatchPath; int ret = -1; ret = applypatch(4, argv); pEnv->ReleaseStringUTFChars(oldPath, pOldPath); pEnv->ReleaseStringUTFChars(newPath, pNewPath); pEnv->ReleaseStringUTFChars(patchPath, pPatchPath); return ret; }
需要发布升级包的时候,把新打好的包用windows的bsdiff.exe制作好.patch文件,然后我们程序检测到新版本的时候就下载这 个.patch文件,然后调用这个JNI函数把.patch文件和当前的版本比较生成一个最新版本的apk文件,然后通过 application/vnd.android.package-archive来安装即可!
下面是工具和安卓端测试源码我放在我另外一个博客上面了。需要的直接访问下载。
相关推荐
本文将深入探讨Android增量升级的源代码实现及其背后的原理。 首先,理解增量升级的基本概念是至关重要的。在Android系统中,增量升级通常基于差分技术,比较旧版本和新版本之间的差异,然后创建一个包含这些差异的...
为了更深入地了解Android增量升级的实践和技巧,可以参考提供的博客链接,那里可能包含了详细的步骤介绍、问题解决方法以及实际案例分析。 总之,Android应用的增量升级测试是一个涉及多个环节的过程,需要对bsdiff...
本文将深入探讨Android增量升级的实现机制,以及如何生成patch文件和与NDK相关的头文件。 一、增量升级原理 1. 对比差异:增量升级的基础是找出两个版本间的文件差异。这通常通过文件比较工具(如rsync或diff)来...
对应博文链接:http://blog.csdn.net/yyh352091626/article/details/50579859 Android底下实现类似小米应用商店的增量更新工程。主要进行补丁的生成,和新的APK的合并
增量升级源码是一种高效、节省资源的软件更新方式,它主要针对已安装的应用程序进行部分更新,而不是重新下载整个应用程序包。这种方式极大地减少了用户下载的数据量,尤其在处理大型应用或者频繁更新的情况下,能...
- "android 增量升级(附后源码)_初学C码农_新浪博客.pdf" 和 "浅析android应用增量升级 - 何明桂的小窝 - 博客频道 - CSDN.pdf" 是两篇技术文章,可能会详细介绍增量更新的理论和实践,包括具体的实现技巧和注意...
恒玄OTA升级源码则提供了这样的能力,让我们能够了解如何在 Android 系统上实现无线更新。 **1. 什么是 OTA 升级** OTA 升级是一种通过网络向设备推送软件更新的方式,无需用户手动连接电脑或前往零售店进行升级。...
以下是对“Android增量更新完整实例代码”的详细解读。 一、增量更新的基本原理 增量更新基于对两个版本应用的差异分析,通常是新版本与旧版本之间的二进制差异。通过计算这些差异,开发者可以生成一个较小的更新包...
在Android系统中,增量升级通常通过对比新旧apk的差异,然后仅下载这些差异部分来实现。接下来,我们将深入探讨增量升级的原理、实现方法以及相关知识点。 一、增量升级的原理 1. 文件差异分析:增量升级的关键...
"Android使用bsdiff做差分包增量更新源码和相关资源文件"的主题着重于如何使用bsdiff工具来实现对应用的源码和资源进行高效更新。bsdiff是一种开源的差分算法,它能够生成两个文件之间的差异,然后将这些差异应用于...
"增量更新Win+Android端源码 android bsdiff.rar"这个压缩包包含了实现这一功能的关键组件。 首先,我们来看`bsdiff.c`和`bspatch.c`这两个文件。它们是BSDiff工具的源代码,由Colin Percival开发,用于计算两个二...
总之,Android AB升级源码涉及了系统更新的多个层面,包括用户界面、网络通信、文件系统操作、恢复模式以及引导加载程序的交互。理解这一机制对于Android开发者和系统维护者来说至关重要,因为它关乎到系统的更新...
在这个“Android应用源码安卓增量更新(差分升级)项目”中,我们将探讨如何实现这一功能。 首先,理解增量更新的基本概念:增量更新是针对已有版本的应用,只下载与现有版本不同的新代码或资源,而不是整个新版本的...
增量更新是一种有效的软件升级策略,它只下载和安装自上次版本以来发生变化的部分,而不是整个应用程序。在Android开发中,这种技术对于节省用户流量、缩短更新时间以及提高用户体验至关重要。本项目是一个C++实现的...
总结,Android程序升级涉及多个层次,从用户应用的APK安装,到系统的OTA更新,再到增量更新的实现,都需要对源码有深入理解。了解这些机制有助于开发者优化升级流程,提升用户体验。同时,权限管理、多版本共存等...
以上是基于“Android应用源码之程序如何升级.zip”可能涉及的知识点,通过对源码的分析,我们可以更深入地理解Android应用升级的实现细节,包括版本控制、更新流程、数据处理等方面。学习这部分知识对于Android...
作者smuyyh,源码IncrementallyUpdate,Android实现应用的增量更新和升级。通过旧版APK+新版APK生成差分包,再用差分包与旧版APK合成新版APK并安装。 详细说明:http://android.662p.com/thread-6727-1-1.html
6. **多设备同步**:为了实现多设备间的同步,源码可能包含设备识别和身份验证的逻辑,以及一个简单的同步算法,比如增量同步,只更新修改过的数据。 7. **权限管理**:由于涉及到局域网通信,应用可能需要请求网络...