Android Small框架增量升级方案


转载地址:http://blog.csdn.net/u013022222/article/details/52268526


背景

随着业务的发展,安装包的体积也在不断的增大,这时候,如果要版本更新,用户不得不去下载完整的安装包。但是如果使用增量升级方案,用户只需要下载新旧版本的差异包,然后在本地合成就行,这样省时省力。我们可以看下某产品使用增量升级时的数据:

  • xxx.2.4.0000.apk 32MB
  • xxx.2.5.0000.apk 28MB
  • 差异包 6.3MB

其中增量包就是上文提到的差异包,可见用户每次升级只需下载差异包就行,省时省力。

原理

这里写图片描述

这里写图片描述

其原理就是,我们在服务器端先拿新版本安装包和旧版本安装包进行对比,在生成差异包之后下发,之后客户端根据对应的差异包和本地旧版本安装包合成,便生成了新版本安装包

实现

0x01 下载编译差分合并工具: 
apk文件的差分和合并都是使用的开源的二进制比较工具 bsdiff实现,值得注意的是,该工具依赖bizp2这个库。在下载完毕后,直接make便可以编译工具,不过我在一台苹果机器上编译的时候出现了一点问题,那时候make文件没法正确的读取,所以我修改了make文件,读者编译的时候(仅限mac, linux)只需要运行install.sh脚本就行。这部分的代码我已经单独抽出来作为一个repo,读者喜欢可以自行star,然后下载编译 repo

0x02 生成差异包 
如果你刚刚编译成了bsdiff,在命令行里面输入bsdiff或者bspatch就会出现如下的信息: 
这里写图片描述
其中bsdiff用于比较新旧文件的差异部分并生成差异包,bspatch根据之前差异包和旧文件生成新文件,我们可以从这两个可执行文件的报错信息看出该命令如何使用。我们这里有两个文件:new.so 和 old.so 
这里写图片描述
之后我们运行 diff old.so new.so patch.so便生成了差异包patch.so 
这里写图片描述

这里写图片描述

0x03 合成新版本 
通过刚刚的介绍,我们已经在Android端实现了相关的库,只需要简单的调用

YPatch.patch(oldFilePath, newFilePath, patchFilePatch)
 
 
  • 1

便可以将新版本存放到newFilePath指定的目录下

结合Small框架

上面好像都是在讲原理,没有具体的实践讲解的话,估计读者还是不懂,我们现在就结合Small框架来看看如何做到增量更新。 
对于我们的Small框架,我们要知道,它把每个插件都编译成.so文件,然后存放到app的native目录下,不过,如果它发现自己的download目录有新的插件,那么就会去加载download目录下的插件,并且这种加载优先权是最大的,也就是说它会优先加载download目录下的插件。所以,如果我们要做增量更新,旧文件就从app的native目录进行读取,然后从服务器端下载增量包,最后合成的文件存放到download目录下,这样每次插件启动都会到download目录下加载新的插件。

实现:

        //其中pluginUri对应的是bundle.json中插件对应的uri
        //下文有介绍
        String packageName = parsePackageName(context, pluginUri);
        if (TextUtils.isEmpty(packageName)) {
            Log.e(TAG, "can't parse plugin's package name, check if assets/bundle.json is deleted");
            return;
        }

        // small框架的约定就是,插件名字是以lib+包名.so明明的
        // 比如如果我们插件的包名是com.chan.app.setting
        // 那么对于插件名就是libcom_chan_app_setting.so
        final String soName = "lib" + packageName.replaceAll("\\.", "_") + ".so";

        //找到旧版本的插件安装包
        final File oldPlugin = new File(context.getApplicationInfo().nativeLibraryDir, soName);
        //新版本都存放到small指定的download目录下
        final File newPlugin = new File(FileUtils.getDownloadBundlePath(), soName);

        //开始下载patch
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder().url(uri).build();

        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {

            }

            @Override
            public void onResponse(Response response) throws IOException {

                //保存从服务器端下载的增量包
                File patch = new File(FileUtils.getDownloadBundlePath(), "patch.so");
                FileOutputStream fileOutputStream = new FileOutputStream(patch);
                byte[] content = response.body().bytes();
                fileOutputStream.write(content);
                fileOutputStream.flush();
                fileOutputStream.close();

                //合成的新插件安装包存放文职有new Plugin指定
                YPatch.patch(oldPlugin.getAbsolutePath(), newPlugin.getAbsolutePath(), patch.getAbsolutePath());
            }
        });
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

可以看到这个函数中出现的pluginUri,它其实对应的是bundle.json中插件的uri:

{
  "version": "1.0.0",
  "bundles": [
    {
      "uri": "main",
      "pkg": "com.chan.app.main",
      "rules": {
        "item": ".ItemActivity"
      }
    },
    {
      "uri": "setting",
      "pkg": "com.chan.app.setting",
      "rules": {
        "index": ".MainActivity"
      }
    }
  ]
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

也就是这里的main, setting

而这个bundle.json就是宿主中asset下的small配置文件: 
这里写图片描述

示例代码:

OkSmall.merge(LaunchActivity.this, "setting", "http://192.168.1.100:8080/patch.so");
                Toast.makeText(LaunchActivity.this, "重启应用后更新生效", Toast.LENGTH_SHORT).show();
 
 
  • 1
  • 2

至此,所有的内容已经介绍完毕

要踩的坑

更新插件的时候要记得把插件的versionCode加大,比如 我们这里的setting插件: 
这里写图片描述

修改它的build.gradle文件: 
这里写图片描述
要记得加大11行的数值,不然加载还是不成功的

引用


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值