Binary diff/patch utility: 4.3
BZIP2 :1.0.6
编译bsdiff系统:centos
AndroidStudio:3.5
新的apk包与老的apk包通过bsdiff工具生成增量更新包,下载增量更新包之后, 通过bspatch工具生成全量安装包。安装全量包即达到了增量更新的目的。
在linux中下载bsdiff,通过make命令编译。由于网站上下载的MakeFile编写问题,会报错。格式错误, shell语法的指令前面要有tab。
CFLAGS += -O3 -lbz2 PREFIX ?= /usr/local INSTALL_PROGRAM ?= ${INSTALL} -c -s -m 555 INSTALL_MAN ?= ${INSTALL} -c -m 444 all: bsdiff bspatch bsdiff: bsdiff.c bspatch: bspatch.c install: ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin .ifndef WITHOUT_MAN ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1 .endif
修改之后在执行make命令,如果报以下错误, 说明bzlib没有安装。根据自己的系统安装bzlib。
bsdiff.c:33:19: fatal error: bzlib.h: No such file or directory #include <bzlib.h> ^ compilation terminated.安装完成再执行make命令,生成以下文件则编译成功。
cc -O3 -lbz2 bsdiff.c -o bsdiff cc -O3 -lbz2 bspatch.c -o bspatch通过命令生成差分包patch,自此服务器端的工作结束。android端需要下载这个差分包,通过bspatch工具合成全量包安装。
./bsdiff old.apk new.apk patch新建C++项目,bsdiff源码中的bspatch.c文件放到项目中。将因为bspatch同样依赖bzlib,所以需要将bzlib的源码也下载下来供使用。下载地址可用文字开始的链接。最后项目的目录结构会如下图所示(bzlib源码已剔除不需要的部分):
编辑CMakeLists.txt,引入bzlib库和bspatch,完整代码如下所示:
cmake_minimum_required(VERSION 3.4.1) file(GLOB bzip bzlib/*.c) add_library( native-lib SHARED ${bzip} bspatch.c native-lib.cpp) include_directories(bzlib) find_library( log-lib log) target_link_libraries( native-lib ${log-lib})编写BsPatcher工具类,提供java调用的native方法,如下所示:
public class BsPatcher { static { System.loadLibrary("native-lib"); } /** * 生成增量更新包 * @param oldApkPath 老apk路径 * @param newApkPath 生成的全量包路径 * @param patchPath 从服务器上获取的差量包路径 */ public static native void update(String oldApkPath, String newApkPath, String patchPath); }编写native-lib.cpp文件,编写native方法,调用bspatch的main方法实现用old.apk和差分包合成全量包。代码如下所示(xxx是包路径中的一部分,涉及敏感信息,用xxx代替):
#include <jni.h> #include <string> extern "C" { extern int main(int argc, const char *argv[]); } extern "C" JNIEXPORT void JNICALL Java_com_xxx_hotfixdemo_BsPatcher_update(JNIEnv *env, jclass clazz, jstring old_apk_path, jstring new_apk_path, jstring patch_path) { const char *oldPath = env->GetStringUTFChars(old_apk_path, 0); const char *newPath = env->GetStringUTFChars(new_apk_path, 0); const char *patch = env->GetStringUTFChars(patch_path, 0); const char *argv[] = {"", oldPath, newPath, patch}; main(4, argv); env->ReleaseStringUTFChars(old_apk_path, oldPath); env->ReleaseStringUTFChars(new_apk_path, newPath); env->ReleaseStringUTFChars(patch_path, patch); }
编写MainActivity类,通过点击事件触发更新,为了简化下载差分包的过程,此demo将差分包已经放到SD卡的根目录。合成全量包是耗时操作,需要放到子线程工作,本demo使用AsynTask实现。代码如下所示:
@SuppressLint("StaticFieldLeak") public void update(View view) { new AsyncTask<Void, Void, File>() { @Override protected File doInBackground(Void... voids) { String patch = new File(Environment.getExternalStorageDirectory(), "patch").getAbsolutePath(); String oldApk = getApplicationInfo().sourceDir; String newApk = createNewApk().getAbsolutePath(); BsPatcher.update(oldApk, newApk, patch); return new File(newApk); } @Override protected void onPostExecute(File file) { super.onPostExecute(file); Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { /* Android N 写法*/ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".fileprovider", file); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); } else { /* Android N之前的老版本写法*/ intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } startActivity(intent); } }.execute(); } private File createNewApk() { File newApk = new File(Environment.getExternalStorageDirectory(), "bsdiff.apk"); if (!newApk.exists()) { try { newApk.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } return newApk; }最终实现效果,点击升级,直接用patch和old.apk生成全量包,然后拉起安装程序进行安装。
需要读取权限,Android6.0及以上版本需要动态申请权限
Android7.0及以上安装需要FileProvider
Android8.0及以上安装需要在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />