Android增量更新

一、概述

什么是增量更新?

增量更新就是在安装新版本的apk(以下简称new.apk)时,可以不用下载完整的apk文件,只需要下载一个增量包(.patch文件),然后将旧版本apk(以下简称old.apk)和该增量包合并就成了新版本的apk。

本文参考了鸿洋大神的这篇文章http://blog.csdn.net/lmj623565791/article/details/52761658,在此基础上完善一下步骤,总结一下遇到的坑。

二、实现

服务器端上

首先当有新版本时,服务器端肯定要提供patch文件,该文件通过old.apk和new.apk得到(要根据不同版本的old.apk提供不同的patch文件),使用bsdiff工具https://download.pokorra.de/coding/bsdiff_win_exe.zip
解压缩后,将old.apk和new.apk移到该目录,在命令行下输入以下命令,可以得到PATCH.patch文件。
这里写图片描述
目录下另一个文件bspatch是将old.apk和patch文件合并为new.apk的,这里不使用,在手机客户端也就是代码上实现合并。

客户端上

首先要下载bsdiff的源码,因为它依赖于bzip2,所以也要下载bzip2的源码。
bsdiff源码下载地址:http://www.daemonology.net/bsdiff/bsdiff-4.3.tar.gz
bzip2源码下载地址:http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz
(下载不成功的可以在我的github上下载,最后会给链接)
在Android Studio上创建好项目后,在app的main目录下新建jni目录,将bsdiff压缩包解压缩后得到的bspatch.c文件复制进jni目录,在jni目录下创建一个bzip2目录,将bzip2压缩包解压缩后得到的.h和.c文件(总共15个)都复制进bzip2目录,如下图所示:
这里写图片描述
然后在app目录下的build.gradle目录下配置ndk:

defaultConfig {
    ...
    ndk {
        moduleName = 'bspatch'
    }
}

这步前提是要已经安装了ndk(local.properties文件中有ndk.dir这一行),没有的话在SDK Manager的SDK Tools目录下勾选NDK这项。
local.properties文件中确定有
这里写图片描述
这两行后,在gradle.properties文件中添加

android.useDeprecatedNdk=true

然后在创建类BsPatchUtil来调用jni

public class BsPatchUtil {
    static {
        System.loadLibrary("bspatch"); //这里的library名称对应build.gradle目录下ndk的moduleName值
    }

    public static native int patch(String oldApkPath, String newApkPath, String patchPath);
}

再然后在jni目录下的bspatch.c文件中添加上面patch对应的方法

JNIEXPORT jint JNICALL Java_com_hzh_apkpatchdemo_BsPatchUtil_patch
        (JNIEnv *env, jclass cls,
         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 = patchMethod(argc, argv);

    (*env)->ReleaseStringUTFChars(env, old, argv[1]);
    (*env)->ReleaseStringUTFChars(env, new, argv[2]);
    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);
    return ret;
}

这里的方法名Java_com_hzh_apkpatchdemo_BsPatchUtil_patch换成自己对应的,其中com_hzh_apkpatchdemo对应包名,BsPatchUtil是类名,patch是方法名。

  int ret = patchMethod(argc, argv);

这行中的patchMethod方法就是把bspatch.c文件中的main方法改成patchMethod
文件中的

#include <bzlib.h>

改成

#include <bzip2/bzlib.h>

同时添加

#include "jni.h"

然后运行,会发现报一堆以下错误

Error:(70) multiple definition of `main'

提示main方法重复定义了,在出错信息中直接进入相应文件删除main方法就行了。

至此,ndk配置完毕了,接下了就是将old.apk与patch文件合并成new.apk并安装它的方法。

创建一个ApkExtract类,在该类中给一个获取当前版本apk路径的方法

    public static String extract(Context context) {
        context = context.getApplicationContext();
        ApplicationInfo applicationInfo = context.getApplicationInfo();
        String apkPath = applicationInfo.sourceDir;
        Log.d("hzh", apkPath);
        return apkPath;
    }

给一个安装apk的方法

    public static void install(Context context, String apkPath) {
        Intent i = new Intent(Intent.ACTION_VIEW);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        File file = new File(apkPath);
        Uri uri;
        if (Build.VERSION.SDK_INT >= 24){
            i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            uri = FileProvider.getUriForFile(context,"com.hzh.apkpatchdemo.fileprovider",file);
        }else{
            uri = Uri.fromFile(file);
        }
        i.setDataAndType(uri,
                "application/vnd.android.package-archive");
        context.startActivity(i);
    }

这里Android 7.0以上版本要用FileProvider来获取uri

    uri = FileProvider.getUriForFile(context,"com.hzh.apkpatchdemo.fileprovider",file);

“com.hzh.apkpatchdemo.fileprovider”该属性要在AndroidManifest.xml文件中声明

    <application 
        ...>
        <provider
            android:authorities="com.hzh.apkpatchdemo.fileprovider"
            android:name="android.support.v4.content.FileProvider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"
                />
        </provider>
    </application>

@xml/filepaths文件内容如下

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="" name="update"/>
</paths>

其中external-path代表Environment.getExternalStorageDirectory(),path中的内容是该路径下的子目录,name不能为空,可以随便取。
最后在MainActivity中触发调用

    private void doBspatch() {
        final File destApk = new File(Environment.getExternalStorageDirectory(), "new.apk");
        final File patch = new File(Environment.getExternalStorageDirectory(), "PATCH.patch");

        //一定要检查文件都存在
        BsPatchUtil.patch(ApkExtract.extract(MainActivity.this),
                destApk.getAbsolutePath(),
                patch.getAbsolutePath());

        if (destApk.exists())
            ApkExtract.install(MainActivity.this, destApk.getAbsolutePath());
    }

最后注意要给读写权限,这是重点,之前就因为忘记加了搞了半天搞不出来,也没显示哪里错。

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    public void onClick(View view) {
        if (ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 2);
        } else {
            doBspatch();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == 2) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                doBspatch();
            }
        }
    }

接下来就是打包生成apk,生成patch文件,将patch文件放到手机客户端的指定目录
这里写图片描述
在手机上安装old.apk,运行之后效果图如下:
这里写图片描述

三、总结

服务器端下发patch文件时,同时也要下发md5值,合并完成后,不要忘记校验下md5。
附上github地址:https://github.com/HZHAboom/ApkPatchDemo
工具下载地址:
https://github.com/HZHAboom/-/tree/master/Android%E5%A2%9E%E9%87%8F%E6%9B%B4%E6%96%B0%E5%B7%A5%E5%85%B7%E5%8C%85

除了上面讲的配置ndk的方法,还可以用CMakeLists.txt文件来配置
这里写图片描述
在新建项目时勾选Include C++ Support
这里写图片描述
在完成时勾选这两项,点击Finish,项目就会自动在app目录下生成CMakeLists.txt文件,自动生成cpp目录、native-lib.cpp文件和调用的演示方法。
这里写图片描述
然后将之前的jni目录下的文件拷贝到cpp目录下,将CMakeLists.txt文件中的native-lib和路径改成bspatch就行了

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.



add_library( # Sets the name of the library.
             bspatch

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             # Associated headers in the same location as their source
             # file are automatically included.
             src/main/cpp/bspatch.c )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       bspatch

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

以上就配置好了,或者不在新建项目时勾选Include C++ Support也行,在项目创建之后,自己在app目录下创一个CMakeLists.txt文件,在build.gradle文件中配置就行了

android {
    ...
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值