安卓实现基于bsdiff的Dex增量更新

文章目录

  • Dex文件增量
    • 大致思路
      • 1.下载bsdiff库
      • 2.找到bsdiff库的bsdiff.exe 和bspatch.exe
      • 3.生成差分文件
      • 4.执行合并
    • 安卓项目使用bspatch
      • 创建c++项目
      • 导入要使用的源文件
      • 修改so库名称
      • 添加cpp编译版本
      • 编写逻辑代码
        • native方法
        • java代码
          • AndroidManifest.xml
          • 合并方法
          • 再者就是打开模拟器权限
    • dex差分思路
      • jar和dex的区别
      • dex的结构
      • 使用010Editor查看
      • dex思路细节
      • 本文的demo可以私信找我要哦

Dex文件增量

今天实现一种不通过应用市场进行更新的方式,可以动态更新,类似于某些app的检查更新功能。

用户点击检查更新,会先下载新包,下载完毕后重新安装app,覆盖旧的app,实现更新。

大致思路

先介绍一个差分库,bsdiff

他能计算两个文件的不同并生成差分文件,也可以通过差分文件进行合并,生成新文件

使用:

现在在Windows平台上实现一个old.dex的版本升级(旧文件升级)的功能

1.下载bsdiff库

http://www.daemonology.net/bsdiff/

2.找到bsdiff库的bsdiff.exe 和bspatch.exe

这里解释一下,cmd运行的就是.exe文件,cmd去执行的时候就是执行的他们的main函数,后边拼接的参数也就是我们main函数需要的参数。

3.生成差分文件

切换到包含bsdiff.exe bspatch.exe的目录,cmd敲入下面命令

bsdiff old.dex new.dex patch
//参数1 文件1路径
//参数2 文件2路径
//参数3 生成的差分文件路径

这其实就是就是执行了bsdiff.c main函数,传入两个参数分别是

//4,  bsdiff,old.dex,new.dex,patch
int main(int argc, char * argv[]) 
{
    
}

4.执行合并

还是当前目录,cmd敲入下面命令

bspatch old.dex new2.dex patcth
//参数1 源文件路径
//参数2 生成的新文件路径
//参数3 差分文件路径

此时在当前目录生成了new2.dex 此时这个文件和new.dex是等价的

使用010Editor查看两个文件

new.dex

安卓实现基于bsdiff的Dex增量更新_第1张图片

new2.dex
安卓实现基于bsdiff的Dex增量更新_第2张图片

发现两个文件一毛一样

这是我们在Windows下的操作

如果想在android使用呢?

安卓项目使用bspatch

现在实现文章开头说的需求

创建c++项目

创建项目时我们选择c++,生成的项目结构如下

project视图

安卓实现基于bsdiff的Dex增量更新_第3张图片

android视图

安卓实现基于bsdiff的Dex增量更新_第4张图片

只是多了cpp目录

CMakeList.text类似于于我们的gradle,是c++的构建文件

native-lib是系统帮我们生成的jni调用,里面有一个默认的函数

导入要使用的源文件

要知道的是,客户端不用去做diffpatch是服务器下发的,也就是说我们不需要bsdiff.c,只需要 bspatch.c就可以了,复制我们的bspatch.ccpp目录下

修改cmake文件,导入我们的bspatch.c

add_library( # Sets the name of the library.
        mydexdiff

        # Sets the library as a shared library.
        SHARED
        bspatch.c
        # Provides a relative path to your source file(s).
        native-lib.cpp)

此时发现bspatch.c存在爆红

此处需要导入bzlib.h,下载地址https://sourceforge.net/projects/bzip2/

接下来导入bzlib,复制文件到cpp目录

此时的cpp目录
安卓实现基于bsdiff的Dex增量更新_第5张图片

接下来在cmake添加bzip依赖,bzip的文件比较多,因此不适合在add_library中一个一个添加,我们使用下面的方法

#声明SOURCES变量,代表bzip2的文件夹
aux_source_directory(bzip2 SOURCES)

add_library(
        bspatch_utlis

        SHARED
        native-lib.cpp
        bspatch.c
        #导入SOURCES
        ${SOURCES})
        
#引入bzip2文件夹
include_directories(bzip2)

这时候bspatch不再爆红

修改so库名称

修改一下生成的so库的名称

target_link_libraries( # Specifies the target library.
		#so库名称
        bspatch_utlis
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})
        
        
add_library( # Sets the name of the library.
        bspatch_utlis
        #省略其他
        )

添加cpp编译版本

修改appbuild.gradle,在defaultConfig中添加

//根据不同的cpu架构,编译不同so库的版本,x86基本时模拟器的架构
ndk{
    abiFilters  'x86','arm64-v8a'
}
//配置打包进apk的so库版本
externalNativeBuild{
    cmake{
        abiFilters 'x86','arm64-v8a'
    }
}

编译查看生成的apk,下面就是我们新的apk目录结构

安卓实现基于bsdiff的Dex增量更新_第6张图片

编写逻辑代码

native方法

MainActivity中声明native方法

public static native int patch(String oldApk,String newApk,String patchFile);

alt+Enter 自动生成jni方法调用

会在native-lib.cpp中生成

extern "C"
JNIEXPORT jint JNICALL
Java_com_hbsf_mydexdiff_MainActivity_patch(JNIEnv *env, jclass clazz, jstring old_apk,
                                           jstring new_apk, jstring patch_file) {
    // TODO: implement patch()

}

现在需要调用c代码,需要在cpp中进行适配

//这样声明时告诉当前文件有一个地方实现了executePatch,你自己去找吧,所以我们也要把bspatch的main改为executePatch
extern "C" {
extern int executePatch(int argc, char *argv[]);
}

完善上面的方法

Java_com_hbsf_mydexdiff_MainActivity_patch(JNIEnv *env, jclass clazz, jstring old_apk,
                                           jstring new_apk, jstring patch_file) {
    //之前说过了,调用bspatch需要4个参数
    int args = 4;
    char *argv[args];
    argv[0] = "bspatch";
	//必须把java的字符串转换成cpp层的字符串
    argv[1] = (char *) (env->GetStringUTFChars(old_apk, 0));
    argv[2] = (char *) (env->GetStringUTFChars(new_apk, 0));
    argv[3] = (char *) (env->GetStringUTFChars(patch_file, 0));

    //此处executePathch()就是上面我们修改出的
    int result = executePatch(args, argv);
	
    //手动进行释放
    env->ReleaseStringUTFChars(old_apk, argv[1]);
    env->ReleaseStringUTFChars(new_apk, argv[2]);
    env->ReleaseStringUTFChars(patch_file, argv[3]);
   
    __android_log_print(ANDROID_LOG_ERROR,"diff","==%s==%s==%s==%d",argv[1] ,argv[2] ,argv[3],result );
    return result;

}

java代码

AndroidManifest.xml

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />


<provider
          android:name="androidx.core.content.FileProvider"
          android:authorities="com.hbsf.mydexdiff.fileprovider"
          android:exported="false"
          android:grantUriPermissions="true">
    <meta-data
               android:name="android.support.FILE_PROVIDER_PATHS"
               android:resource="@xml/file_paths" />
provider>
合并方法
public void patch() {
    //生成新apk的路径
    File newFile = new File(getExternalFilesDir("apk"), "app.apk");
    //patch的路径,我么们给他放在私有目录下了
    File patchFile = new File(getExternalFilesDir("apk"), "patch.apk");

    Log.e("patch", getApplicationInfo().sourceDir + "\n" + newFile.getAbsolutePath() + "\n" + patchFile.getAbsolutePath());
    //调用native方法
    int result = patch(getApplicationInfo().sourceDir, newFile.getAbsolutePath(),
                       patchFile.getAbsolutePath());
    if (result == 0) {
        //安装apk
        install(newFile);
    }
}

//安装代码
private void install(File file) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0+以上版本
        Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    } else {
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
    }
    startActivity(intent);
}
再者就是打开模拟器权限

运行则可以安装新的apk,发现和我我们修改的地方一样

之前

安卓实现基于bsdiff的Dex增量更新_第7张图片

之后

安卓实现基于bsdiff的Dex增量更新_第8张图片

dex差分思路

bsdiff是根据整体文件的差分,不稳定,有时候很快有时候很慢,虽然说这是后台帮我们做的事情,但是客户端也要了解。

每种文件都有他的结构,我们可以通过了解dex的结构,只对某些需要改动的结构进行修改,这样就充分利用了dex的特性,进行了优化。

jar和dex的区别

首先是jardex的区别,dex会把每个类的字符串只保存一份在字符串索引区,而jar包时每个class自己保存自己的,相当于dex进行了压缩优化

安卓实现基于bsdiff的Dex增量更新_第9张图片

dex的结构

安卓实现基于bsdiff的Dex增量更新_第10张图片

使用010Editor查看

old_test.java 如下

public class Test {

    public static void test() {
        System.out.println("a1");
        System.out.println("b1");
        System.out.println("c1");
        System.out.println("d1");
        System.out.println("e1");
        System.out.println("f1");
        System.out.println("g1");
        System.out.println("h1");
        System.out.println("i1");
    }

}

把生成的dex导入010Editor

安卓实现基于bsdiff的Dex增量更新_第11张图片

一共21个字符串包括的a1,b1之类的字符串

存储的是偏移地址和大小,表示一个字符串在文件存储的位置和区域

安卓实现基于bsdiff的Dex增量更新_第12张图片

dex思路细节

上面我们使用的是bsdiff,全部的文件都可以使用bsdiff进行差分,并没用使用dex文件的特性,这里提供dex修改的思路

通过010我们可以清楚的看到dex的文件内部结构,所以可以通过解析这个二进制文件,取出来所有的数据,根据不同的区封装成不同的对象,比如头区,字符串区等等,解析完旧的dex再解析新的dex,对两个dex的对象属性进行对比,可以把这些差异持久化保存下来,按照自己的规则协议来生成,在合并的时候就可以通过解析这个差分文件,这个差分文件的基本信息就是在哪需要操作,操作是什么,改变的数据是什么,再封装一个自己的dex文件输出流,就可以完美的把旧的dex同通过差分文件变成新的dex

本文的demo可以私信找我要哦

原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}

⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}

✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

你可能感兴趣的:(安卓,java,android)