Android 增量更新全解

原创 2018年04月14日 23:47:34
    背景:当一个APK大到接近100MB,或者几十MB,需要频繁更新的时候,全量更新是不是特别浪费流量和时间?用BSDiff/Patch ?网上一搜一大片都是用这个方案。如果用HDiffPatch和XDelta方案做下测试结果数据对比,你会发现,在Android的APK差分更新实现上,BSDiff/Patch方案效果是最差的!!!

一、增量更新原理

Android 增量更新流程图

1、增量更新主要分为两步:

    1)服务端拿新版本A和旧版本B做差分,生成差分包C‘
    2)客户端检测到可增量更新的差分包,下载差分包C‘之后,和本地旧版本B做合成,生成新版本A。

2、步骤详细展开:

    服务器端:服务端的同学拿到客户端同学开发的新版本A,跟已发布的旧版本B1,B2,B3...做了差分生成相应的差分包C1,C2,C3...,并生成相应差分包的MD5值,当然全量包的签名、MD5值也是需要的,这样客户端需要的所有数据就OK了。
    客户端:用户手动更新或程序主动请求检测更新:
        1)客户端用MD5值和版本号作为参数向服务端请求更新数据,若服务端没有差分包则返回全量包下载URL、MD5值、签名值。
        2)若服务端存在相应的差分包则返回差分包下载URL,全量包签名值、全量包和差分包MD5值,全量包签名值和MD5值。把差分包下载到本地之后(C1),先做MD5值校验,确保下载的差分包数据的完整性,校验失败则走全量更新逻辑,校验无误和本地现有安装的旧版本(B1)进行差分合并生成新版本(A),之后进行合成版本的MD5值校验和签名校验,确保合成文件的完整性和签名信息的正确性。校验无误进行安装。

3、需要考虑的一些问题:

    1)服务端生成的差分包大小接近新包大小,或者直接超过新包大小,就没必要进行差分更新;
    2)下载到本地之后是否需要进行签名校验依赖各自情况,若有和系统方进行合作的,系统方一般会拿APK进行二次签名之后作为系统内置应用。
    3)下载文件当然也需要支持断点续传,考虑再细点,下载APK的过程中有可能被劫持或者被运营商重定向,如果是全量更新下载,可以和服务端约定每段下载数据的校验逻辑规则,在HTTP头中附加校验字段数据,确保万无一失;
    4)服务端是否根据客户端的更新请求实时生成差分数据?从目前生成差分包的测试数据来看,这个实现是不靠谱的。最好就是有新版本之后,在服务端先把差分包数据准备好,而不是等到请求更新的时候再生成差分包。

二、现有增量更新实现方案    

1、BSDiff/Patch     

    这个实现方案是最多的,网上一大堆都是这个方案的实现,Android系统也整合了这个实现。更多资料可参考Binary diff/patch 

2、HPatch

    博客资料参考开源我的基于字节的数据补丁算法库HDiffPatch,GitHub代码资源:HDiffPatch,这份代码资源也提供了三个实现方案的对比测试,但是不是基于Android APK文件的差分,所以测试结果数据跟下面的测试结果有差异。

    另附上:HDiffPatch和BSDiff/Patch两个方案Android Demo实现GitHub代码资源,解决了不熟悉C开发的环境编译配置问题。

3、XDelta

    参考XDelta官网,这边需要注意的是必须基于3.0.4版本,最新的版本编译生成的SO得到的测试结果有问题,生成的差分包很大,差分包的合成也有问题,对比了两个版本的代码,只在几个小地方的处理逻辑有差异,那些逻辑看着也不像是导致问题的原因,如果有熟悉XDelta和C开发的大神知道原因烦告知下原因。

4、Courgette

    用在Chrome 浏览器的更新上,在BSDiff/BSPatch基础上改进的,性能更优,But...不适用于Android APK更新,详细可参考Courgette测试报告
    想要了解更多Courgette的内容可参考Courgette官方文档

三、BSDiff/BSPatch、HPatch、XDelta测试数据对比

BSDiff/Patch、HPatch、XDelta实现方案测试数据对比

四、增量更新涉及到的逻辑代码

1、获取当前APK文件路径

    当一个APK安装之后,系统中保留有一份APK备份文件,通过以下代码方式获取APK文件的绝对路径。   
public String getInstallPath(){
        try{
            String packageName = getPackageName();
            ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
            return info.sourceDir;
        }catch (Exception e){
            return null;
        }
 }

2、MD5值校验

    参考博客:Android数据加密之MD5加密    

3、签名校验

    分别获取当前APK签名值,合成APK的签名值,对比无误就可以进行安装了 
    /**
     * 获取当前APK签名
     * 
     * @param context
     * @return
     */
    private Signature[] getSignatureInfo(Context context) {
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> apps = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
        Iterator<PackageInfo> iter = apps.iterator();
        while (iter.hasNext()) {
            PackageInfo packageinfo = iter.next();
            String packageName = packageinfo.packageName;
            String currentPackage = getPackageName();
            if (packageName.equals(currentPackage)) {
                return packageinfo.signatures;
            }
        }
        return null;
    }
       
    /**
     * 获取外部APK文件签名
     * 
     * @param apkFile APK文件路径
     * @return
     */
    public static Signature[] getSignatureInfo(String apkFile){
        DisplayMetrics metrics = new DisplayMetrics();
        metrics.setToDefaults();
        Object pkgParserPkg = null;
        Class[] typeArgs = null;
        Object[] valueArgs = null;
        try {
            Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
            Constructor<?> packageParserConstructor = null;
            Object packageParser = null;
            //由于SDK版本问题,这里需做适配,来生成不同的构造函数
            if (Build.VERSION.SDK_INT > 20) {
                //无参数 constructor
                packageParserConstructor = packageParserClass.getDeclaredConstructor();
                packageParser = packageParserConstructor.newInstance();
                packageParserConstructor.setAccessible(true);//允许访问

                typeArgs = new Class[2];
                typeArgs[0] = File.class;
                typeArgs[1] = int.class;
                Method pkgParser_parsePackageMtd = packageParserClass.getDeclaredMethod("parsePackage", typeArgs);
                pkgParser_parsePackageMtd.setAccessible(true);

                valueArgs = new Object[2];
                valueArgs[0] = new File(apkFile);
                valueArgs[1] = PackageManager.GET_SIGNATURES;
                pkgParserPkg = pkgParser_parsePackageMtd.invoke(packageParser, valueArgs);
            } else {
                //低版本有参数 constructor
                packageParserConstructor = packageParserClass.getDeclaredConstructor(String.class);
                Object[] fileArgs = { apkFile };
                packageParser = packageParserConstructor.newInstance(fileArgs);
                packageParserConstructor.setAccessible(true);//允许访问

                typeArgs = new Class[4];
                typeArgs[0] = File.class;
                typeArgs[1] = String.class;
                typeArgs[2] = DisplayMetrics.class;
                typeArgs[3] = int.class;

                Method pkgParser_parsePackageMtd = packageParserClass.getDeclaredMethod("parsePackage", typeArgs);
                pkgParser_parsePackageMtd.setAccessible(true);

                valueArgs = new Object[4];
                valueArgs[0] = new File(apkFile);
                valueArgs[1] = apkFile;
                valueArgs[2] = metrics;
                valueArgs[3] = PackageManager.GET_SIGNATURES;
                pkgParserPkg = pkgParser_parsePackageMtd.invoke(packageParser, valueArgs);
            }

            typeArgs = new Class[2];
            typeArgs[0] = pkgParserPkg.getClass();
            typeArgs[1] = int.class;
            Method pkgParser_collectCertificatesMtd = packageParserClass.getDeclaredMethod("collectCertificates", typeArgs);
            valueArgs = new Object[2];
            valueArgs[0] = pkgParserPkg;
            valueArgs[1] = PackageManager.GET_SIGNATURES;
            pkgParser_collectCertificatesMtd.invoke(packageParser, valueArgs);
            // 应用程序信息包, 这个公开的, 不过有些函数变量没公开
            Field packageInfoFld = pkgParserPkg.getClass().getDeclaredField("mSignatures");
            Signature[] info = (Signature[]) packageInfoFld.get(pkgParserPkg);
            return info;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
     }

    /**
     * 签名值对比
     * 
     * @param s1
     * @param s2
     * @return
     */
    private boolean compareSignatures(Signature[] s1, Signature[] s2) {
        if (s1 == null) {
            return false;
        }
        if (s2 == null) {
            return false;
        }
        HashSet<Signature> set1 = new HashSet<Signature>();
        for (Signature sig : s1) {
            set1.add(sig);
        }
        HashSet<Signature> set2 = new HashSet<Signature>();
        for (Signature sig : s2) {
            set2.add(sig);
        }
        // Make sure s2 contains all signatures in s1.
        if (set1.equals(set2)) {
            return true;
        }

        return false;
     }

原文链接:Android 增量更新全解



版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/air798/article/details/79945762

android增量更新

  • 2018年03月23日 09:53
  • 38.82MB
  • 下载

手把手带你实现Android增量更新

Android增量更新技术在很多公司都在使用,网上也有一些相关的文章,但大家可能未必完全理解实现的方式,本篇博客,我将一步步的带大家实现增量更新。为什么需要增量更新?当我们开发完一个项目之后,上线维护...
  • u012124438
  • u012124438
  • 2016-11-13 17:07:06
  • 3216

Elasticsearch 索引的全量/增量更新

当你的es 索引数据从mysql 全量导入之后,如何根据其他客户端改变索引数据源带来的变动来更新 es 索引数据呢。 首先用 Python 全量生成 Elasticsearch 和 ik 初始的...
  • qq_28018283
  • qq_28018283
  • 2018-02-07 10:38:51
  • 421

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

虽然很多App的版本更新并不频繁,但是一个App基本上也有几兆到几十兆不等,在没有Wifi的条件下,更新App是非常耗流量的。说到这个吐槽一下三大网络运营商,4G网络是变快了,但是流量确没有多,流量仍...
  • yyh352091626
  • yyh352091626
  • 2016-01-25 16:01:36
  • 22283

Android高级之十三讲-HotFix、热加载和热更新

   本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!组件化与插件化:前者对功能进行拆分后,独立开发,打成一个包发布;后者对功能拆分,使用主包+分包,...
  • liuxian13183
  • liuxian13183
  • 2016-12-14 23:25:36
  • 2085

漫谈Android 增量更新

在前几年,整体移动网络环境相比现在差很多,加之流量费用又相对较高,因此每当我们发布新版本的时候,一些用户升级并不是很积极,这就造成了新版本的升级率并不高。而google为了解决了这个问题,提出了Sma...
  • dd864140130
  • dd864140130
  • 2016-10-25 22:53:07
  • 17157

Android增量更新的实现(一)

如何实现在windows环境下的增量更新
  • abc181139079
  • abc181139079
  • 2017-09-27 17:16:13
  • 219

Android开发之增量更新

一、使用场景 apk升级,节省服务器和用户的流量 二、原理自从 Android 4.1 开始, Google Play 引入了应用程序的增量更新功能,App使用该升级方式,可节省约2/3的流量。现...
  • qq_33750826
  • qq_33750826
  • 2017-07-20 18:56:22
  • 1416

Android 增量更新完全解析 是增量不是热修复

感悟 今天是我身份证上的25岁生日,所以法律上来说我25岁啦~~ 一直没有写过总结,本来是准备写个总结的,但是列出来几条觉得太装逼,也不是什么干货,所以决定换个角度。 那么就聊聊,目前人生中做的...
  • lmj623565791
  • lmj623565791
  • 2016-10-11 08:45:09
  • 49581

实现android的增量更新

这几天刚好遇到一个问题:APP越做体积越庞大,但是目前又因为其他原因,不适合使用混合开发等缩小体积的方式,所以当用户更新一次APP的时候显得有点麻烦(体积略大)。查了一下资料,因为目前APP基本不会有...
  • a287574014
  • a287574014
  • 2017-02-09 16:41:22
  • 375
收藏助手
不良信息举报
您举报文章:Android 增量更新全解
举报原因:
原因补充:

(最多只允许输入30个字)