Android---高效实现增量更新

原理

获取文件差异:遍历两个字节文件old和new,比对每个字节的差异,并记录差异字节的文件索引,然后把差异的字节内容和索引记录到一个差异文件patch里。

合并差异:解析上面生成的patch文件,根据记录差异的索引和内容,修改或添加到old文件,修改后的old文件的内容就跟new文件的一样了。

重新安装apk:把合并后的apk文件重新安装后,这样就实现了增量更新的功能。

这样做的好处是不管你修改的是java代码,还是资源文件,还是代码混淆等都能正确的合成(前提是生成差异文件和合并差异文件时没出错)需要的新版apk文件,因为它是直接比对字节。

 

工具

要实现差异比对和差异合并,这里用到的工具是:bsdiff,官网地址:http://www.daemonology.net/bsdiff/。bsdiff是用c语言写的,它生成的差异文件会使用bzlib进行压缩,所以差异文件的体积也会相应比较小。

从官网下载下载的bsdiff是个压缩文件,解压后可以看到里面是源码文件,需要在编译后才能使用,Mac系统下的编译步骤:

1、打开终端,进入到bsdiff解压后的目录

2、本文下载的是bsdiff-4.3版本,它的Makefile文件有语法错误,需要修改后才能进行编译。
在终端输入:vim Makefile,修改后内容如下:

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

3、在终端输入:make

【注意】bsdiff依赖于bzlib,所以如果编译提示找不到bzlib.h头文件,那么需要下载bzlib,下面提供三种操作系统的下载方式:

          Ubuntu:   apt install libbz2-dev

         CentOS:  yum -y install bzip2-devel.x86_64

         Mac:        brew install bzip2

4、成功编译后,会在当前目录生成两个文件:bsdiff和bspatch。bsdiff是用来生成差异文件的,bspatch是用来合并差异文件的。

使用bsdiff生成差异文件(一般在服务器上):

./bsdiff old.apk new.apk patch

参数1:旧的文件
参数2:新的文件
参数3:旧文件和新文件的差异部分的记录文件

 

使用bspatch合并差异文件(一般在客户端上):

./bspatch old.apk new.apk patch

参数1:旧的文件
参数2:新的文件
参数3:旧文件和新文件的差异部分的记录文件

【提示】可以对合并后的文件进行MD5校验,看是否跟new.apk一致。

 

集成到Android项目

打开Android Studio创建一个C/C++的工程,下面是工程结构:

其中:
/main/cpp/bsdiff4目录下放的是bsdiff的两个源码文件
/main/cpp/bzip2-1.0.5目录是bsdiff需要依赖的bzlip精简后的源文件
androidLog.h:在natvie层打印Log用
native-lib.cpp:实现生成和合并差异包的源文件
CMakeList.txt:构建so库的配置
BsDiffPatch:java层调用生成和合并差异包


下面是具体核心代码:

native-lib.cpp文件:

#include <jni.h>
#include <string>

extern "C" {
    // 声明bsdiff的两个生成差异包和合并差异包的函数
    extern int patch_main(int argc, char * argv[]);
    extern int diff_main(int argc,char *argv[]);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_log_bsdiff_BsDiffPatch_diff(JNIEnv *env, jobject thiz, jstring old_apk, jstring new_apk, jstring patch) {

    const char *_old_apk = env->GetStringUTFChars(old_apk, 0);
    const char *_patch = env->GetStringUTFChars(patch, 0);
    const char *_new_apk = env->GetStringUTFChars(new_apk, 0);

    // 调用bsdiff合并差异包函数
    const char *argv[] = {"", _old_apk, _new_apk, _patch};
    diff_main(4, const_cast<char **>(argv));

    env->ReleaseStringUTFChars(old_apk, _old_apk);
    env->ReleaseStringUTFChars(patch, _patch);
    env->ReleaseStringUTFChars(new_apk, _new_apk);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_log_bsdiff_BsDiffPatch_mergeApk(JNIEnv *env, jobject thiz, jstring old_apk, jstring new_apk, jstring patch) {
    const char *_old_apk = env->GetStringUTFChars(old_apk, 0);
    const char *_patch = env->GetStringUTFChars(patch, 0);
    const char *_new_apk = env->GetStringUTFChars(new_apk, 0);

    // 调用bsdiff合并差异包函数
    const char *argv[] = {"", _old_apk, _new_apk, _patch};
    patch_main(4, const_cast<char **>(argv));

    env->ReleaseStringUTFChars(old_apk, _old_apk);
    env->ReleaseStringUTFChars(patch, _patch);
    env->ReleaseStringUTFChars(new_apk, _new_apk);
}

CMakeList.txt文件:

cmake_minimum_required(VERSION 3.10.2)

file(GLOB bzip bzip2-1.0.5/*.c)
file(GLOB bsdiff4 bsdiff4/*.c)

add_library( # Sets the name of the library.
        native-lib
        SHARED
        native-lib.cpp
        ${bzip}
        ${bsdiff4})

include_directories(bzip2-1.0.5)

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

target_link_libraries( # Specifies the target library.
        native-lib
        ${log-lib})

 

java层代码:

package com.log.bsdiff;

public class BsDiffPatch {

    static {
        System.loadLibrary("native-lib");
    }

    /**
     * 根据旧的apk文件和新的apk文件进行对比,生成差异包
     * @param oldApk 旧版本apk包路径
     * @param newApk 合成后新的apk包路径
     * @param patch 差异包路径
     */
    public native void diff(String oldApk, String newApk, String patch);

    /**
     * 将旧的apk包和指定的差异包合并成新版本的apk包
     * @param oldApk 旧版本apk包路径
     * @param newApk 合成后新的apk包路径
     * @param patch 差异包路径
     */
    public native void mergeApk(String oldApk, String newApk, String patch);
}

 

package com.log.bsdiff;

import android.content.Context;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class Util {

    /**
     * 模拟从服务器下载资源apk包
     *
     * @param context
     * @param is
     */
    public static String download(Context context, InputStream is, String fileName) {
        File file = context.getDir("resources", Context.MODE_PRIVATE);
//        File file = Environment.getExternalStorageDirectory();
        String resourceFilePath = file.getAbsolutePath() + "/" + fileName;
        FileOutputStream fos = null;
        try {
            File resourceFile = new File(resourceFilePath);
            if (resourceFile.exists()) {
                resourceFile.delete();
            }
            fos = new FileOutputStream(resourceFile.getAbsolutePath());
            byte[] buf = new byte[2048];
            int len;
            while ((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resourceFilePath;
    }

}
package com.log.bsdiff;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private BsDiffPatch diffPatch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        diffPatch = new BsDiffPatch();
    }

    public void onUpdate(View view) {
        mergerApk();
    }

    public void mergerApk() {
        String oldApk = "/data/user/0/com.log.bsdiff/app_resources/old.apk";
        String newApk = "/data/user/0/com.log.bsdiff/app_resources/new.apk";
        String patch = "/data/user/0/com.log.bsdiff/app_resources/patch";
        diffPatch.mergeApk(oldApk, patch, newApk);
    }

    public void diff(View view) {
        String patch = "/data/user/0/com.log.bsdiff/app_resources/patch";
        // 模拟下载文件
        try {
            String oldApk = Util.download(MainActivity.this,
                    getAssets().open("old.apk"), "old.apk");
            String newApk = Util.download(MainActivity.this,
                    getAssets().open("new.apk"), "new.apk");
            diffPatch.diff(oldApk, newApk, patch);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void onInstall(View view) {
        installApk(new File("/data/user/0/com.log.bsdiff/app_resources/new.apk"));
    }

    private void installApk(File file) {
        if (file == null && !file.exists()) {
            Toast.makeText(MainActivity.this, "文件为空或不存在", Toast.LENGTH_SHORT).show();
            return;
        }

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri fileUri = Uri.fromFile(file);
        intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
        MainActivity.this.startActivity(intent);
    }
}

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="版本2" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="生成差异包"
        android:onClick="diff"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新"
        android:onClick="onUpdate"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="安装新版apk"
        android:onClick="onInstall"/>

</LinearLayout>


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值