热修复从零开始一热修复原理

什么是热修复呢

  热修复 就是我们的app在线上运行的时候出现了bug 可以在不发新版本安装包只用发布补丁包 用户无感知的前提下修复bug的技术

那么热修复要怎么实现呢

需要分为开发端 服务端 以及用户端三个部分 

开发端的任务自然是修复bug 生成补丁包  服务端的任务主要是管理补丁包 而用户端自然就是下载执行补丁包

介绍一下几个主要的开源框架

各大开源热更库的对比

AndFix与Robust的原理 即在编译打包阶段自动插入一段代码(字节码插桩) 类似于代理 将方法的执行代码重定向到其他方法中

插桩前后代码对比

那么Tinker的原理是什么呢

Tinker通过计算原始包中的dex与修复后的包中dex的区别生成补丁包dex 补丁包中的内容即为两者间差分的描述 运行时将补丁包与原始包中的dex文件进行合成 重启后生成新的修复后的dex文件

其实不管怎么怎样 热修复最终还是要基于类加载机制 以及反射去实现 

所以必须要了解的 就是ClassLoader以及双亲加载机制

众所周知 我们编写的类在生成apk的时候 会打包成多个dex文件 而我们的程序在主线程起来之后 会创建ClassLoader(ActivityThread handleBindApplication)来加载dex

那么什么是ClassLoader呢 


ClassLoader类图

ClassLoader是一个抽象类 而他在我们系统里的实现 主要有BootClassLoader以及BaseDexClassLoader 其中BootClassLoader就是用于加载Android framework层的源码 那么BaseDexClassLoader就是用来加载我们编写类的dex  我们来看看BaseDexClassLoader的实现 

public class BaseDexClassLoader extends ClassLoader {

    // 需要加载的dex列表

    private final DexPathList pathList;

    // dexPath要加载的dex文件所在的路径,optimizedDirectory是odex将dexPath

    // 处dex优化后输出到的路径,这个路径必须是手机内部路劲,libraryPath是需要

    // 加载的C/C++库路径,parent是父类加载器对象

    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>();

        // 使用pathList对象查找name类

        Class c = pathList.findClass(name, suppressedExceptions);

        return c;

    }

}

可以看到BaseDexClassLoader 就是把dex文件的地址传给DexPathList 生成了一个DexPathList对象而findClass方法也是从DexPathList中查找需要注意的是BaseDexClassLoader构建的时候会把dexPath使用“:”分隔开传入多个dex文件路径 那么我们再来看一下DexPathList的实现

final class DexPathList {

    private static final String DEX_SUFFIX = ".dex";

    private final ClassLoader definingContext;

    private final Element[] dexElements;

    // 本地库目录

    private final File[] nativeLibraryDirectories;

    public DexPathList(ClassLoader definingContext, String dexPath,

            String libraryPath, File optimizedDirectory) {

        // 当前类加载器的父类加载器

        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

        // 根据输入的dexPath创建dex元素对象

        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

                                          suppressedExceptions);

        if (suppressedExceptions.size() > 0) {

            this.dexElementsSuppressedExceptions =

                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);

        } else {

            dexElementsSuppressedExceptions = null;

        }

        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);

    }

}

可以看到 DexPathList 是通过makeDexElements 方法创建出了一个Element[]数组                    我们不具体看这个方法的实现 总之就是把dex文件转换成了这个数组 也就相当于把我们实现的所有的类加载到了这个数组 至于为何是数组 因为我们的apk不止一个dex文件 那么我们再来看看findClass的实现

// 加载名字为name的class对象

public Class findClass(String name, List<Throwable> suppressed) {

    // 遍历从dexPath查询到的dex和资源Element

    for (Element element : dexElements) {

        DexFile dex = element.dexFile;

        // 如果当前的Element是dex文件元素

        if (dex != null) {

            // 使用DexFile.loadClassBinaryName加载类

            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);

            if (clazz != null) {

                return clazz;

            }

        }

    }

    if (dexElementsSuppressedExceptions != null) {

        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));

    }

    return null;

}

很清楚了对吧 就是通过遍历Element数组直到查找到我们要找的类

那么什么是双亲加载机制呢 

我们先来看一下我们使用ClassLoader加载一个类是怎么实现的呢

protected ClassloadClass(String name, boolean resolve)

throws ClassNotFoundException

{

// First, check if the class has already been loaded

        Class c = findLoadedClass(name);

        if (c ==null) {

try {

if (parent !=null) {

c =parent.loadClass(name, false);

                }else {

c = findBootstrapClassOrNull(name);

                }

}catch (ClassNotFoundException e) {

// ClassNotFoundException thrown if class not found

// from the non-null parent class loader

            }

if (c ==null) {

// If still not found, then invoke findClass in order

// to find the class.

                c = findClass(name);

            }

}

return c;

}

可以看到 查找的流程就是首先会去查找已经加载过的类 如果加载过就不会重新加载 如果没有加载过 就会去parent里面找 而如果parent没有找到 就会调用自己的findClass                              前面我们说了 ClassLoader主要有两个实现类 BootClassLoader以及BaseDexClassLoader 这里的parent其实就是BootClassLoader 这也就是所谓双亲加载机制 

为什么要有双亲加载机制呢

通过双亲加载机制 我们在加载一个类的时候会先从父类 也就是framework层的代码去找 这就保证了我们自己定义的类不会覆盖系统的类保证系统代码的安全

那么热修复究竟要怎么实现呢

这里我们讲替换dex文件思路的实现  前面我们讲到DexPathList 里面有一个Element数组我们在加载类的时候会遍历这个数组 那么我们的思路也就是通过反射Hook代码 把我们修复后的dex插入到这个数组的最前面 这样在加载类的时候就会首先加载我们修复好的类 也就修复了bug 当然实际运用可能还涉及到Android各个版本的兼容等问题并没有这么简单

    但总体思路就是应用补丁包: patchElment(补丁包生成的) + oldElement(APK原有的) 赋值给oldElement

1、获取程序的PathClassLoader对象

2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象

3、反射获取pathList的dexElements对象 (oldElement)

4、把补丁包变成Element数组:patchElement(反射执行makePathElements)

5、合并patchElement+oldElement = newElement (Array.newInstance)

6、反射把oldElement赋值成newElement

至于具体要怎么落地呢 我们下节再讲


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容

  • 本文目的是为了让大家了解什么是热修复,具体的实现细节,将在系列课中为大家直播演绎。大家了解了原理后,lance老师...
    Android工程师Lance阅读 367评论 0 0
  • 前言 热修复原理,这个一直是这几年来很热门的话题,在项目中使用的话,也基本要么是阿里系或者腾讯系的开源框架。但是作...
    架构师成长日记阅读 251评论 0 0
  • /***每天一个知识点day65 TODO 热修复原理** https://www.jianshu.com/p/e...
    SlideException阅读 253评论 0 0
  • 热修复:不用安装,静默修复。 正常情况下:版本1.0上线,用户安装,发现bug,紧急修复,重新发布1.1版本,用户...
    晓晓桑阅读 2,279评论 0 6
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,562评论 0 11