By 何明桂(http://blog.csdn.net/hmg25) 转载请注明出处

很久没有更新博客了,真是堕落啊,几次想提起笔,却总是被各种琐事耽搁,以后会多写文章记录点滴。

背景

        随着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系统差分包的制作,使用命令:
  1. ./build/tools/releasetools/ota_from_target_files -n -i <旧包> <新包> <差分包名>
  ./build/tools/releasetools/ota_from_target_files -n -i <旧包> <新包> <差分包名>
       在查阅ota_from_target_files 的代码可知,是在函数WriteIncrementalOTAPackage里生成差分包的,在这个函数里边创建了common.Difference这个类,我们继续跟进,在common.py中的类   class Difference(object):里可以看到:    
  1. diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
    diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")

至此我们就看到了android中提供我们用来制作差分增量升级包的工具,"bsdiff",这是一个很牛X开源的二进制差分工具.相关的介绍传送门

相关的代码地址 或者在android的代码目录下 \external\bsdiff
 
       bsdiff是二进制差分工具,其对应的bspatch是相应的补丁合成工具
       需要注意的是增量升级的补丁包,是需要在服务器端,即PC端完成,大致流程如,制作补丁时调用bsdiff函数,根据两个不同版本的二进制文件,生成补丁文件。 
  1. 命令:bsdiff oldfile newfile patchfile
  2. 例如: bsdiff xx_v1.0.apk xx_v2.0.apk xx.patch
命令: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库,供手机端调用。
  1. bspatch的命令格式为:
  2. bspatch oldfile newfile patchfile
   bspatch的命令格式为:
bspatch oldfile newfile patchfile
      和差分时的参数一样。合成新的apk便可以用于安装。
    以上只是简单的操作原理,增量升级还涉及很多其他方面,例如,升级补丁校验等问题,可以参考android源码中bootable\recovery\applypatch的相关操作,本文只是浅析,在此不表。

不足

     增量升级并非完美无缺的升级方式,至少存在以下两点不足:
    1.增量升级是以两个应用版本之间的差异来生成补丁的,你无法保证用户每次的及时升级到最新,所以你必须对你所发布的每一个版本都和最新的版本作差分,以便使所有版本的用户都可以差分升级,这样操作相对于原来的整包升级较为繁琐,不过可以通过自动化的脚本批量生成。
    2.增量升级成功的前提是,用户手机端必须有能够让你拷贝出来且与你服务器用于差分的版本一致的apk,这样就存在,例如,系统内置的apk无法获取到,无法进行增量升级;对于某些与你差分版本一致,但是内容有过修改的(比如破解版apk),这样也是无法进行增量升级的,为了防止合成补丁错误,最好在补丁合成前对旧版本的apk进行sha1sum校验,保证基础包的一致性。

小实验

       多说无益,实践才是王道。下面就来简单实践一下,检测之前理论的正确性。下载实验包 (http://download.csdn.net/detail/hmg25/4676737),解压后文件如下
  1. ├── bsdiff-4.3        //bsdiff的源码路径,官网获取
  2. │   ├── bsdiff.1
  3. │   ├── bsdiff.c
  4. │   ├── bspatch.1
  5. │   ├── bspatch.c
  6. │   └── Makefile
  7. ├── bsdiff-4.3.tar.gz
  8. ├── bsdiff4.3-win32     //windows PC端的测试工具
  9. │   ├── Binary diff.txt
  10. │   ├── bsdiff.exe
  11. │   ├── bspatch.exe
  12. │   └── LICENSE
  13. ├── bspatch             //手机端的测试工具
  14. ├── iReader1.6.2.0(v35).apk      // 旧版本的apk
  15. └── iReader1.8.0.1(v40).apk    //新版本的apk
├── 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文件夹,并下运行命令:

  1. bsdiff.exe   ../iReader1.6.2.0(v35).apk   ../iReader1.8.0.1(v40).apk   ../ireader.patch
   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,流量节省了很多。
     下面先在电脑端将他们合并。

  1. bspatch.exe  ../iReader1.6.2.0(v35).apk   ../new.apk    ../ireader.patch
  bspatch.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调用。有兴趣的童鞋可以自己操作一下~~~
            By 何明桂(http://blog.csdn.net/hmg25) 转载请注明出处   原装正版,盗版必究 ^_^

 
补充:
很多人不知道怎么进行封装为lib,其实这个和一般的android的C库是一样的,不明白的看看jni相关的知识,原来的测试工程已经找不到了,下边我给出个简单的例子,抛砖引玉,给大家参考下。
 
  1. #include <stdio.h>
  2. #include "com_hmg25_newstart_BSpatch.h"
  3. #include <bzlib.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. #include <err.h>
  8. #include <unistd.h>
  9. #include <fcntl.h>
  10. #include <android/log.h>
  11. static off_t offtin(u_char *buf)
  12. {
  13. off_t y;
  14. y=buf[7]&0x7F;
  15. y=y*256;y+=buf[6];
  16. y=y*256;y+=buf[5];
  17. y=y*256;y+=buf[4];
  18. y=y*256;y+=buf[3];
  19. y=y*256;y+=buf[2];
  20. y=y*256;y+=buf[1];
  21. y=y*256;y+=buf[0];
  22. if(buf[7]&0x80) y=-y;
  23. return y;
  24. }
  25. int applypatch(int argc,char * argv[])
  26. {
  27. FILE * f, * cpf, * dpf, * epf;
  28. BZFILE * cpfbz2, * dpfbz2, * epfbz2;
  29. int cbz2err, dbz2err, ebz2err;
  30. int fd;
  31. ssize_t oldsize,newsize;
  32. ssize_t bzctrllen,bzdatalen;
  33. u_char header[32],buf[8];
  34. u_char *old, *new;
  35. off_t oldpos,newpos;
  36. off_t ctrl[3];
  37. off_t lenread;
  38. off_t i;
  39. if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
  40. /* Open patch file */
  41. if ((f = fopen(argv[3], "r")) == NULL)
  42. err(1, "fopen(%s)", argv[3]);
  43. /*
  44. File format:
  45. 0   8   "BSDIFF40"
  46. 8   8   X
  47. 16  8   Y
  48. 24  8   sizeof(newfile)
  49. 32  X   bzip2(control block)
  50. 32+X    Y   bzip2(diff block)
  51. 32+X+Y  ??? bzip2(extra block)
  52. with control block a set of triples (x,y,z) meaning "add x bytes
  53. from oldfile to x bytes from the diff block; copy y bytes from the
  54. extra block; seek forwards in oldfile by z bytes".
  55. */
  56. /* Read header */
  57. if (fread(header, 1, 32, f) < 32) {
  58. if (feof(f))
  59. errx(1, "Corrupt patch\n");
  60. err(1, "fread(%s)", argv[3]);
  61. }
  62. /* Check for appropriate magic */
  63. if (memcmp(header, "BSDIFF40", 8) != 0)
  64. errx(1, "Corrupt patch\n");
  65. /* Read lengths from header */
  66. bzctrllen=offtin(header+8);
  67. bzdatalen=offtin(header+16);
  68. newsize=offtin(header+24);
  69. if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))
  70. errx(1,"Corrupt patch\n");
  71. /* Close patch file and re-open it via libbzip2 at the right places */
  72. if (fclose(f))
  73. err(1, "fclose(%s)", argv[3]);
  74. if ((cpf = fopen(argv[3], "r")) == NULL)
  75. err(1, "fopen(%s)", argv[3]);
  76. if (fseeko(cpf, 32, SEEK_SET))
  77. err(1, "fseeko(%s, %lld)", argv[3],
  78. (long long)32);
  79. if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)
  80. errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
  81. if ((dpf = fopen(argv[3], "r")) == NULL)
  82. err(1, "fopen(%s)", argv[3]);
  83. if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
  84. err(1, "fseeko(%s, %lld)", argv[3],
  85. (long long)(32 + bzctrllen));
  86. if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)
  87. errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
  88. if ((epf = fopen(argv[3], "r")) == NULL)
  89. err(1, "fopen(%s)", argv[3]);
  90. if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
  91. err(1, "fseeko(%s, %lld)", argv[3],
  92. (long long)(32 + bzctrllen + bzdatalen));
  93. if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)
  94. errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
  95. if(((fd=open(argv[1],O_RDONLY,0))<0) ||
  96. ((oldsize=lseek(fd,0,SEEK_END))==-1) ||
  97. ((old=malloc(oldsize+1))==NULL) ||
  98. (lseek(fd,0,SEEK_SET)!=0) ||
  99. (read(fd,old,oldsize)!=oldsize) ||
  100. (close(fd)==-1)) err(1,"%s",argv[1]);
  101. if((new=malloc(newsize+1))==NULL) err(1,NULL);
  102. oldpos=0;newpos=0;
  103. while(newpos<newsize) {
  104. /* Read control data */
  105. for(i=0;i<=2;i++) {
  106. lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
  107. if ((lenread < 8) || ((cbz2err != BZ_OK) &&
  108. (cbz2err != BZ_STREAM_END)))
  109. errx(1, "Corrupt patch\n");
  110. ctrl[i]=offtin(buf);
  111. };
  112. /* Sanity-check */
  113. if(newpos+ctrl[0]>newsize)
  114. errx(1,"Corrupt patch\n");
  115. /* Read diff string */
  116. lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]);
  117. if ((lenread < ctrl[0]) ||
  118. ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
  119. errx(1, "Corrupt patch\n");
  120. /* Add old data to diff string */
  121. for(i=0;i<ctrl[0];i++)
  122. if((oldpos+i>=0) && (oldpos+i<oldsize))
  123. new[newpos+i]+=old[oldpos+i];
  124. /* Adjust pointers */
  125. newpos+=ctrl[0];
  126. oldpos+=ctrl[0];
  127. /* Sanity-check */
  128. if(newpos+ctrl[1]>newsize)
  129. errx(1,"Corrupt patch\n");
  130. /* Read extra string */
  131. lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]);
  132. if ((lenread < ctrl[1]) ||
  133. ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
  134. errx(1, "Corrupt patch\n");
  135. /* Adjust pointers */
  136. newpos+=ctrl[1];
  137. oldpos+=ctrl[2];
  138. };
  139. /* Clean up the bzip2 reads */
  140. BZ2_bzReadClose(&cbz2err, cpfbz2);
  141. BZ2_bzReadClose(&dbz2err, dpfbz2);
  142. BZ2_bzReadClose(&ebz2err, epfbz2);
  143. if (fclose(cpf) || fclose(dpf) || fclose(epf))
  144. err(1, "fclose(%s)", argv[3]);
  145. /* Write the new file */
  146. if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||
  147. (write(fd,new,newsize)!=newsize) || (close(fd)==-1))
  148. err(1,"%s",argv[2]);
  149. free(new);
  150. free(old);
  151. return 0;
  152. }
  153. JNIEXPORT jint JNICALL Java_com_hmg25_newstart_BSpatch_applyPatch(JNIEnv *env,
  154. jobject obj, jstring old, jstring new , jstring patch){
  155. int argc=4;
  156. char * argv[argc];
  157. argv[0]="bspatch";
  158. argv[1]=(*env)->GetStringUTFChars(env,old, 0);
  159. argv[2]=(*env)->GetStringUTFChars(env,new, 0);
  160. argv[3]=(*env)->GetStringUTFChars(env,patch, 0);
  161. int ret=applypatch(argc, argv);
  162. (*env)->ReleaseStringUTFChars(env,old,argv[1]);
  163. (*env)->ReleaseStringUTFChars(env,new,argv[2]);
  164. (*env)->ReleaseStringUTFChars(env,patch,argv[3]);
  165. return ret;
  166. }
#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 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) ||
((old=malloc(oldsize+1))==NULL) ||
(lseek(fd,0,SEEK_SET)!=0) ||
(read(fd,old,oldsize)!=oldsize) ||
(close(fd)==-1)) err(1,"%s",argv[1]);
if((new=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, new + 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))
new[newpos+i]+=old[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, new + 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,new,newsize)!=newsize) || (close(fd)==-1))
err(1,"%s",argv[2]); free(new);
free(old); return 0;
} JNIEXPORT jint JNICALL Java_com_hmg25_newstart_BSpatch_applyPatch(JNIEnv *env,
jobject obj, jstring old, jstring new , jstring patch){
int argc=4;
char * argv[argc];
argv[0]="bspatch";
argv[1]=(*env)->GetStringUTFChars(env,old, 0);
argv[2]=(*env)->GetStringUTFChars(env,new, 0);
argv[3]=(*env)->GetStringUTFChars(env,patch, 0); int ret=applypatch(argc, argv); (*env)->ReleaseStringUTFChars(env,old,argv[1]);
(*env)->ReleaseStringUTFChars(env,new,argv[2]);
(*env)->ReleaseStringUTFChars(env,patch,argv[3]);
return ret;
}

Android.mk:

  1. LOCAL_PATH:= $(call my-dir)
  2. include $(CLEAR_VARS)
  3. # This is the target being built.
  4. LOCAL_MODULE:= libbspatch
  5. # All of the source files that we will compile.
  6. LOCAL_SRC_FILES:= \
  7. com_hmg25_newstart_BSpatch.c
  8. # No static libraries.
  9. LOCAL_STATIC_LIBRARIES := \
  10. libbz
  11. # Also need the JNI headers.
  12. LOCAL_C_INCLUDES += \
  13. $(JNI_H_INCLUDE) external/bzip2
  14. # No special compiler flags.
  15. LOCAL_CFLAGS +=
  16. include $(BUILD_SHARED_LIBRARY)

浅析android应用增量升级(转)的更多相关文章

  1. Android App增量升级

    移动互联网主打的就是用户体验和产品的快速迭代,通过用户反馈和用户行为跟踪及时调整产品方向,这样才能持续保持生命力和创造力.说的接地气点就是,你频繁的升级更新,有时只是修复了几个bug或者微调了下界面, ...

  2. Mac平台与Windows平台下AndroidStudio增量升级

    Android Studio增量升级什么情况下使用最合适呢? 比如现在的as版本是2.2版本,而你的as版本2.0版本,这个时候点Check For Updates就没有反应了,因为你已经2个有版本没 ...

  3. 手把手实现andriod应用增量升级

    近期研究了android应用增量升级的应用.当中用到了android NDK编程,先说下为什么要使用增量升级.当我们的应用达到一定大小的时候,比方眼下有30M.假设新版本号35M仅仅是添加了几个功能, ...

  4. 待实验:Android 增量升级

    参考资料: 增量升级(省流量更新)的Android客户端实现  http://blog.csdn.net/sgwhp/article/details/9009427 http://my.oschina ...

  5. Android增量升级的方法和原理

    总结: 我们使用delta编码算法减少Android应用升级程序的大小.我们通过bsdiff和bspatch工具在android上实现delta编码算法.服务器软件和android应用已经部署.当前, ...

  6. android黑科技系列——应用市场省流量更新(增量升级)原理解析

    一.前言 最近在看热修复相关的框架,之前我们已经看过了阿里的Dexposed和AndFix这两个框架了,不了解的同学可以点击这里进行查看:Dexposed框架原理解析 和 AndFix热修复框架原理解 ...

  7. react native 增量升级方案(转)

    前言 facebook的react-native给我们带来了用js写出原生应用的同时,也使得使用RN编写的代码的在线升级变得可能,终于可以不通过应用市场来进行升级,极大的提升了app修bug和赋予新功 ...

  8. Android Recovery OTA升级(一)—— make otapackage

    文件夹 文件夹 概述 make otapackage BUILT_TARGET_FILES_PACKAGE ota_from_target_files WriteFullOTAPackage Sign ...

  9. android开发 更新升级安装到一半自动闪退

    如题:android开发 更新升级安装到一半自动闪退,,,解决办法,如下(红色为我新增的代码) /**     * 安装APK文件     */    private void installApk( ...

随机推荐

  1. easyui combobox开启搜索自动完成功能

    combo.json [{ "id":-1, "text":" ", "spell":"" },{ ...

  2. mt7620 wireless驱动特性意外发现

    前言 今天又客户反映无线參数SSID编程了HT_AP0, 同事通过后台给他改动后反映给我,我想不正确啊,难道是无线驱动crash了?那应该不能玩才对啊... 追查线索 我们的路由器会定期汇报数据SSI ...

  3. MySQL:按前缀批量删除表格

    想要实现mysql>drop table like "prefix_%" 没有直接可用的命令,不过可以通过mysql语法来组装, SELECT CONCAT( 'DROP T ...

  4. 32.NET中加密解密基本概念

    对消息的接收方来说,安全的交流方式需要同时满足3个条件: 1.完整性:消息在传输途中没有被篡改过,即消息是完好无损的. 2.保密性:接收放可以理解或解密来自发送方的信息.(不保证第三方无法获得,但保证 ...

  5. java zxing实现二维码生成和解析zxing实现二维码生成和解析

    原文:https://www.cnblogs.com/zhangzhen894095789/p/6623041.html zxing实现二维码生成和解析   二维码 zxing   二维码的生成与解析 ...

  6. bochs和硬盘管理

    bochs和硬盘管理 实验一 目的:熟悉实验环境,认识Bochs虚拟机 内容: 1.下载并安装Bochs 2.3.7,官方网站 http://bochs.sourceforge.net/ 2.下载DO ...

  7. 如何让两个div并排,并且div要看得见边框

    <div style="float:left; width:100px; height:100px; border:2px solid #0000FF;"></d ...

  8. Oracle常用系统查询SQL

    以user1身份登录oracle,然后执行:select table_name from user_tables;或select table_name from tabs; 常用SQL --1.查询o ...

  9. 10个最好的 jQuery 视频插件

    在这篇文章中已经收集了10个最佳的jQuery视频插件,帮助开发人员容易地实现网站播放影片功能.可以显示视频和视频播放列表. 1. Bigvideo.js BigVideo.js 是一个jQuery插 ...

  10. scala 学习笔记六 推导

    1.介绍 在Scala中,推导将生成器.过滤器.和定义组合在一起. 2.例子 有一种将result用作val(而不是var)的方式,:“就地”构建result,而不是逐项构建,利用yield关键字,当 ...