Android热补丁动态修复技术(一):从Dex分包原理到热补丁

标签: android 补丁 热补丁
7017人阅读 评论(1) 收藏 举报

一、参考

博文:安卓App热补丁动态修复技术介绍——by QQ空间终端开发团队
博文:Android dex分包方案——by 猫的午后
开源项目:https://github.com/jasonross/Nuwa
开源项目:https://github.com/dodola/HotFix
感谢以上几位大神分享的技术知识!

关于热补丁技术,以上文章已经做了很详细的描述。但是细节上的东西都一带而过,这里会做出更为详细的说明,更适合初学者学习这门技术。

二、Dex分包方案的由来

2.1 Dalvik限制

众所周知,当apk解压后里面是只有一个classes.dex文件的,而这个dex文件里面就包含了我们项目的所有.class文件。

但是当一个app功能越来越复杂,可能会出现两个问题:

  1. 编译失败,因为一个dvm中存储方法id用的是short类型,导致dex中方法不能超过65536个
  2. 你的apk在android 2.3之前的机器无法安装,因为dex文件过大(用来执行dexopt的内存只分配了5M)

2.2 解决方案

针对上述两个问题,有人研究出了dex分包方案。
原理就是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中。

除了第一个dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以资源的方式放在安装包中,并在Application的onCreate回调中被注入到系统的ClassLoader。因此,对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个Dex文件中。

三、Dex分包的原理——ClassLoader

接下来我们就来看看,如何将第二个dex文件注入到系统中。

3.1 ClassLoader体系

我们都知道,java执行程序的时候是需要先将字节码加载到jvm之后才会被执行的,而这个过程就是使用到了ClassLoader类加载器。Android也是如此

以下是DVM的ClassLoader体系

这里写图片描述

查看官方文档可以知道以下两点:
1.Android系统是通过PathClassLoader加载系统类和已安装的应用的。
Android uses this class for its system class loader and for its application class loader(s).

2.而DexClassPath则可以从一个jar包或者未安装的apk中加载dex
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

从这里就可以看出,动态加载dex的时候我们应该使用DexClassLoader

3.2 ClassLoader源码分析

源码可以到这个网站查阅:http://androidxref.com/

DexClassLoader和PathClassLoader都只重写了BaseDexClassLoader的构造而已,而具体的加载逻辑则在BaseDexClassLoader中。

这部分源码都很简单,请务必看懂

BaseDexClassLoader部分源码

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    /**
     * Constructs an instance.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     * should be written; may be {@code null}
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}

从源码得知,当我们需要加载一个class时,实际是从pathList中去找的,而pathList则是DexPathList的一个实体。

DexPathList部分源码:

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";

    /** class definition context */
    private final ClassLoader definingContext;

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private final Element[] dexElements;

    /**
     * Finds the named class in one of the dex files pointed at by
     * this instance. This will find the one in the earliest listed
     * path element. If the class is found but has not yet been
     * defined, then this method will define it in the defining
     * context that this instance was constructed with.
     *
     * @param name of class to find
     * @param suppressed exceptions encountered whilst finding the class
     * @return the named class or {@code null} if the class is not
     * found in any of the dex files
     */
    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
}

从这段源码可以看出,dexElements是用来保存dex的数组,而每个dex文件其实就是DexFile对象。遍历dexElements,然后通过DexFile去加载class文件,加载成功就返回,否则返回null

通常情况下,dexElements数组中只会有一个元素,就是apk安装包中的classes.dex
而我们则可以通过反射,强行的将一个外部的dex文件添加到此dexElements中,这就是dex的分包原理了。
这也是热补丁修复技术的原理。

四、热补丁修复技术的原理

上面的源码,我们注意到一点,如果两个dex中存在相同的class文件会怎样?
先从第一个dex中找,找到了直接返回,遍历结束。而第二个dex中的class永远不会被加载进来。
简而言之,两个dex中存在相同class的情况下,dex1的class会覆盖dex2的class。
盗一下QQ空间的图,如图:classes1.dex中的Qzone.class并不会被加载
这里写图片描述

而热补丁技术则利用了这一特性,当一个app出现bug的时候,我们就可以将出现那个bug的类修复后,重新编译打包成dex,插入到dexElements的前面,那么出现bug的类就会被覆盖,app正常运行,这就是热修复的原理了。
这里写图片描述

五、本章结束

这章为大家介绍了热补丁技术的原理,但是大家可能并不会实际操作。
1. 怎么通过反射将dex插入到elements
2. 怎么讲修复后的类打包成dex
这将是下一篇博客的内容,感谢阅读。

查看评论

Android 热补丁动态修复框架小结

转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/49883661; 本文出自:【张鸿洋的博客】 一、概述最新githu...
  • lmj623565791
  • lmj623565791
  • 2015-11-17 10:01:52
  • 128522

安卓App热补丁动态修复技术介绍

1.背景 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅...
  • yangxi_001
  • yangxi_001
  • 2016-09-13 10:48:25
  • 453

Android热补丁动态修复技术(四):自动化生成补丁——解决混淆问题

一、前言在上一章中,我们使用javassist成功为项目注入了System.out.println(AntilazyLoad.class);这行代码,解决了class_ispreverified问题,...
  • u010386612
  • u010386612
  • 2016-04-21 18:01:02
  • 4592

Android 热补丁技术——资源的热修复

今年真是热补丁框架的洪荒之力爆发的一年,短短时间内,已经出现了好几个热修复的框架了,基本上都是大同小异,这里我就不过多的去评论这些框架。只有自己真正的去经历过,你才会发现其中的坑。事实上,现在出现的大...
  • sbsujjbcy
  • sbsujjbcy
  • 2016-09-15 08:55:30
  • 15672

Linux热补丁的实现

Linux热补丁的实现
  • byrantch
  • byrantch
  • 2016-04-24 08:43:47
  • 2372

各大热补丁方案分析和比较

最近开源界涌现了很多热补丁项目,但从方案上来说,主要包括Dexposed、AndFix、ClassLoader(来源是原QZone,现淘宝的工程师陈钟,在15年年初就已经开始实现)三种。前两个都是阿里...
  • chen03050903
  • chen03050903
  • 2016-06-08 17:12:57
  • 1009

Android热补丁原理简单分析与问题思考

面对Android热补丁(热修复)给人的美好愿景,相信大多数开发、运维人员都会为之所动。但是理想很丰满,现实却很骨感。本文在简单分析主流的热补丁原理后,抛出一些自己想到或遇到的问题作为思考点,供看到的...
  • w7849516230
  • w7849516230
  • 2017-08-31 09:55:31
  • 983

linux 下C程序热补丁技术的原理和实现——要求

上半年做运营商的一个项目,设备规范是中兴华为这些大厂商写的,其中有关于热补丁的要求。运营商对设备运行时间要求很高,不会让更改一个小小的问题都要重启程序。所以就有了热补丁要求:要求程序在运行过程中能够更...
  • chgaowei
  • chgaowei
  • 2010-10-20 23:14:00
  • 13118

Android热补丁的一点总结

由于项目需要,我研究热补丁的实现是从12月上旬开始的,那时候我还是个对编译、ant、hudson只闻其名,对javassist、groovy闻所未闻的孩子;而现在,我已经是一个对ant、hudson一...
  • l465659833
  • l465659833
  • 2016-01-25 10:28:37
  • 1593

如何实现应用程序二进制补丁(冷补丁、热补丁、冷转热补丁,装备补丁)

冷补丁:打上补丁后需重启设备或软件; 热补丁:打上补丁后立即生效; 冷转热补丁:打上后,通过设置,可以补丁立即生效; 装备补丁:实现某些特定功能的补丁; 基出准备: (1)列出SO中所的的...
  • dodonei
  • dodonei
  • 2017-07-27 20:46:16
  • 480
    个人资料
    持之以恒
    等级:
    访问量: 10万+
    积分: 1732
    排名: 2万+
    文章分类
    最新评论