android apk差分升级
2016-05-19 15:49
330 查看
最近项目上需要做APK的差分升级,网上查了些前人的技术文档学习来下,这里也总结下自己的经验。
差分升级主要流程就是,拿新APK和旧apk进行比较生成patch包,放在服务器上让用户下载,用户下载完成后本地apk程序再将本地的apk和patch包整合成一个最新的apk让用户进行安装。
我们需要如下2套源码来实现以上功能 bsdiff/bzlib2
这里我还要介绍一下其实android已经集成了以上两个模块,我们可以在源码的external目录里面看到bsdiff和bzip2两个文件夹对应的就是上面的2套源码。
我们在能编译的源码环境中mmm external/bsdiff看如下截图
这里out/host/linux-x86/obj32/EXECUTABLES/bspatch_intermediates/bsdiff就是用来生成patch的,out/host/linux-x86/obj32/EXECUTABLES/bspatch_intermediates/bspatch是用来整合patch生成新apk的。
所以我们可以拿bsdiff去生成patch包,然后把bspatch相关代码整合成so库加载到我们应用内去运行再生成最新的整包这整个差分升级功能就完成了。
bsdiff命令如下,大家可以自己尝试oldfile是旧包路径+文件名,newfile是新包的,patchfile是生成的patch我们这里可以随便命名为XXX.patch
./bsdiff oldfile newfile patchfile
以上前期准备完成了,现在直奔主题贴上android上功能实现需要的代码
1、新建jni目录存放so库所需源码我只写了3个文件
bs_patch.c
bs_patch.h
Android.mk
bs_patch.h
bs_patch.c这个文件功能参考源码external/bsdiff/bspatch.c加了jni接口
Android.mk
到此jni模块就完成了,我们只需要编译一下就可以生成libtest_bspatch.so
2、java层添加调用接口
PatchUtil.java
这样我们差分升级的功能接口就已经写完了,我们只需要在线程或者AsyncTask里面去调用就可以了,当然对于一个完整的apk从弹框提示用户升级,直到用户安装更新完成,这一个过程还是比较复杂需要很多异常情况处理这里就不深入描述了,我个人是多花1小时来写了一个完整情况流程图再去写代码,才杜绝了后期各种特殊情况下的bug发生。
差分升级主要流程就是,拿新APK和旧apk进行比较生成patch包,放在服务器上让用户下载,用户下载完成后本地apk程序再将本地的apk和patch包整合成一个最新的apk让用户进行安装。
我们需要如下2套源码来实现以上功能 bsdiff/bzlib2
这里我还要介绍一下其实android已经集成了以上两个模块,我们可以在源码的external目录里面看到bsdiff和bzip2两个文件夹对应的就是上面的2套源码。
我们在能编译的源码环境中mmm external/bsdiff看如下截图
这里out/host/linux-x86/obj32/EXECUTABLES/bspatch_intermediates/bsdiff就是用来生成patch的,out/host/linux-x86/obj32/EXECUTABLES/bspatch_intermediates/bspatch是用来整合patch生成新apk的。
所以我们可以拿bsdiff去生成patch包,然后把bspatch相关代码整合成so库加载到我们应用内去运行再生成最新的整包这整个差分升级功能就完成了。
bsdiff命令如下,大家可以自己尝试oldfile是旧包路径+文件名,newfile是新包的,patchfile是生成的patch我们这里可以随便命名为XXX.patch
./bsdiff oldfile newfile patchfile
以上前期准备完成了,现在直奔主题贴上android上功能实现需要的代码
1、新建jni目录存放so库所需源码我只写了3个文件
bs_patch.c
bs_patch.h
Android.mk
bs_patch.h
#ifndef BS_PATCH_H #define BS_PATCH_H JNIEXPORT jint JNICALL Java_com_android_updatetest_util_PatchUtil_applyPatchToApk (JNIEnv *, jclass, jstring, jstring, jstring); #endif
bs_patch.c这个文件功能参考源码external/bsdiff/bspatch.c加了jni接口
#include <jni.h> #include "bs_patch.h" #include <bzlib.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <err.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.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_android_updatetest_util_PatchUtil_applyPatchToApk(JNIEnv *env, jobject obj, jstring old, jstring new , jstring patch){ int argc=4; char * argv[argc]; argv[0]="bspatch"; argv[1]=(char*)((*env)->GetStringUTFChars(env,old, 0)); argv[2]=(char*)((*env)->GetStringUTFChars(env,new, 0)); argv[3]=(char*)((*env)->GetStringUTFChars(env,patch, 0)); int ret = -1; 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
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libtest_bspatch LOCAL_SRC_FILES := bs_patch.c LOCAL_C_INCLUDES += external/bzip2 LOCAL_STATIC_LIBRARIES := libbz include $(BUILD_SHARED_LIBRARY)
到此jni模块就完成了,我们只需要编译一下就可以生成libtest_bspatch.so
2、java层添加调用接口
PatchUtil.java
package com.android.updatetest.util; import java.io.IOException; import android.content.Context; import android.util.Log; public class PatchUtil { static { System.loadLibrary("test_bspatch");//这里需要注意没有lib字母 } public static native int applyPatchToApk(String oldapk_filepath, String newapk_savepath, String patchpath); public static int applyPatch(String oldApkPath, String newApkPath, String patchPath) throws IOException { return applyPatchToApk(oldApkPath, newApkPath, patchPath); } public static int applyPatchToOwn(Context context, String newApkPath, String patchPath) throws IOException { //一般patch都是和本地apk比较来生成新apk,本地apk可以用以下函数得到 String old = context.getApplicationInfo().sourceDir; return applyPatchToApk(old, newApkPath, patchPath); } }
这样我们差分升级的功能接口就已经写完了,我们只需要在线程或者AsyncTask里面去调用就可以了,当然对于一个完整的apk从弹框提示用户升级,直到用户安装更新完成,这一个过程还是比较复杂需要很多异常情况处理这里就不深入描述了,我个人是多花1小时来写了一个完整情况流程图再去写代码,才杜绝了后期各种特殊情况下的bug发生。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories