IncrementallyUpdate

Introduction: Android 实现应用的增量更新和升级。通过旧版 APK+新版 APK 生成差分包,再用差分包与旧版 APK 合成新版 APK 并安装
More: Author   ReportBugs   
Tags:
增量升级-增量更新-

Android 实现应用的增量更新和升级

原理

服务端通过新版本 APK 和旧版本 APK 生成 patch 补丁(也成为差分包),客户端更新的时候只需要下载差分包到本地,然后从 system/app 取出旧版本 APK,通过差分包来合成新版本的 APK,这个过程实际上就是打补丁。

步骤 内容
拷贝资源 拷贝旧版本 APK 以及新版本 APK 到 SD 卡。为了后面进行生成差分包
安装旧版本 APK 安装旧版本的 APK
生成补丁 生成差分包。这个实际上应该是在服务端完成
打补丁 通过差分包及旧版本 APK 生成新版本 APK
安装新版本 APK 安装生成的新版本 APK
获取某个应用的 APK 安装文件 在真正的增量更新过程中,旧版本 Apk 应该从/data/app 底下获取,拷贝到 SD 卡,进行打补丁。当然,也可以不拷贝,直接使用该路径。
String srcDir = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-1.apk";
String destDir1 = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-2.apk";
String destDir2 = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess-3.apk";
String patchDir = Environment.getExternalStorageDirectory().toString() + "/DaemonProcess.patch";

首先来看看这四个文件的作用

srcDir:旧版本 apk 路径。也就是已安装的旧版应用的 APK 地址。为了便于演示,这边直接写死路径。若想真正获取旧版 apk 地址,可通过下面代码实现:

String appDir = getPackageManager().getApplicationInfo(packageName, 0).sourceDir;

destDir1:新版本的 apk 路径。

destDir2:新版本的 apk 路径。通过差分包+旧版本 APK 合成新版本 APK。

patchDir:差分包。通过旧版本 APK+新版本 APK 生成差分包。

NDK 配置

若需自己编译 jni 代码,则下载 NDK,并在 local.properties 下配置自己 ndk 路径

ndk.dir=/Users/yuyuhang/Documents/Android/android-ndk-r10c

build.gradle 加入以下内容:

android {
    defaultConfig {
        ndk{
            moduleName "ApkPatchLibrary"
            abiFilters "armeabi", "armeabi-v7a", "x86"
        }
    }
    sourceSets {
        main {
            jni.srcDirs = ['src/main/jni', 'src/main/jni/']
        }
    }
}

若不想编译 jni 资源,也可直接使用项目提供的 so 库。在 build.gradle 配置 so 库路径,去掉 jni 编译相关脚本,sync now...

sourceSets {
        main {
            // jni.srcDirs = ['src/main/jni', 'src/main/jni/']
            jniLibs.srcDirs = ['libs'] // 若不想编译 jni 代码,可直接引用 so 库,ndk 编译相关脚本注释掉
        }
    }

使用

调用生成差分包及合成 APK 的 native 方法。

package com.yyh.lib.bsdiff;

public class DiffUtils {

    static DiffUtils instance;

    public static DiffUtils getInstance() {
        if (instance == null)
            instance = new DiffUtils();
        return instance;
    }

    static {
        System.loadLibrary("ApkPatchLibrary");
    }

    /**
     * 比较 oldapk 与 newapk 之间差异,并生成 patch 包,存储于 patchPath
     * 
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param newApkPath 示例:/sdcard/new.apk
     * @param patchPath 示例:/sdcard/xx.patch
     * @return 0:成功 非 0:失败
     */
    public native int genDiff(String oldApkPath, String newApkPath, String patchPath);
}
package com.yyh.lib.bsdiff;

public class PatchUtils {

    static PatchUtils instance;

    public static PatchUtils getInstance() {
        if (instance == null)
            instance = new PatchUtils();
        return instance;
    }

    static {
        System.loadLibrary("ApkPatchLibrary");
    }

    /**
     * 使用 oldApk 与 patch 补丁包,合成新的 apk,存储于 newApkPath
     * 
     * @param oldApkPath 示例:/sdcard/old.apk
     * @param newApkPath 示例:/sdcard/new.apk
     * @param patchPath 示例:/sdcard/xx.patch
     * @return 0:成功 非 0:失败
     */
    public native int patch(String oldApkPath, String newApkPath, String patchPath);
}

服务端

服务端工具以及源码位于 Server 目录下。目前只在 Linux64 位的系统下编译,其他系统大家可自行编译。Linux 下的可直接修改 makefile,windows 下可用 VC 编译。

Diff 工具:生成差分包
<!--命令             oldApk              newApk              patch-->
./linux-x86_64/Diff DaemonProcess-1.apk DaemonProcess-2.apk dp.patch
Patch 工具:合并
<!--命令              oldApk              newApk              patch-->
./linux-x86_64/Patch DaemonProcess-1.apk DaemonProcess-3.apk dp.patch

项目截图

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools