热修复相关知识

虽然热修复框架很多,但热修复框架的核心技术主要有三类,分别是代码修复、资源修复和动态链接库修复,其中每个核心技术又有很多不同的技术方案,每个技术方案又有不同的实现,另外这些热修复框架仍在不断的更新迭代中,可见热修复框架的技术实现是繁多可变的。作为开发需需要了解这些技术方案的基本原理,这样就可以以不变应万变。

代码修复主要有三个方案:

  • 类加载方案

    类加载方案基于Dex分包方案,这个得先从65536限制和LinearAlloc限制说起,Dex分包方案主要做的是在打包时将应用代码分成多个Dex,将应用启动时必须用到的类和这些类的直接引用类放到主Dex中,其他代码放到次Dex中。当应用启动时先加载主Dex,等到应用启动后再动态的加载次Dex,从而缓解了主Dex的65536限制和LinearAlloc限制。 Dex分包方案主要有两种,分别是Google官方方案、Dex自动拆包和动态加载方案。 注:我们将有bug的类Key.class进行修改,再将Key.class打包成包含dex的补丁包Patch.jar,放在Element数组dexElements的第一个元素,这样会首先找到Patch.dex中的Key.class去替换之前存在bug的Key.class,排在数组后面的dex文件中的存在bug的Key.class根据ClassLoader的双亲委托模式就不会被加载,这就是类加载方案,如下图所示。 类加载方案需要重启App后让ClassLoader重新加载新的类,为什么需要重启呢?这是因为类是无法被卸载的,因此要想重新加载新的类就需要重启App,因此采用类加载方案的热修复框架是不能即时生效的。 微信Tinker将新旧apk做了diff,得到patch.dex,然后将patch.dex与手机中apk的classes.dex做合并,生成新的classes.dex,然后在运行时通过反射将classes.dex放在Element数组的第一个元素。

  • 底层替换方案

    直接在Native层修改原有类,替换ArtMethod结构体中的字段或者替换整个ArtMethod结构体,这就是底层替换方案。底层替换方案直接替换了方法,可以立即生效不需要重启。采用底层替换方案主要是阿里系为主,包括AndFix、Dexposed、阿里百川、Sophix。

  • Instant Run方案

      Instant Run在第一次构建apk时,使用ASM在每一个方法中注入了类似如下的代码:
      IncrementalChange localIncrementalChange = $change;//1
      if (localIncrementalChange != null) {//2
      localIncrementalChange.access$dispatch(
      "onCreate.(Landroid/os/Bundle;)V", new Object[] { this,
      paramBundle });
      return;
      }
    

Android N引入了一种包含编译、解释和JIT(Just In Time)的混合运行时,以便在安装时间、内存占用、电池消耗和性能之间获得最好的折衷。

ART的主要特征之一就是安装时对应用的AOT编译。这种方式的主要优点就是优化产生的本地代码性能更好,执行起来需要更少的电量。劣势在于安装文件所需的空间和时间。

Dalvik平台存在插桩导致的性能损耗,Art平台由于地址偏移问题导致补丁包可能过大的问题;

DexDiff 生成了一个自定义格式的 dex,这个 dex 在不同的虚拟机环境下会区分 dalvik 和 art 平台合成不同的补丁包,而这种方式也是分别为了解决 dalvik 下和 art 下出现的问题产生的方案。这样就避免了 Dalvik 下需要宿主提前插桩才能解决验证问题等。

列出几个问题:

1,如何判断 dex 、资源文件已经加载完成

2,如何判断这个机型不支持 ClassLoader 的 dex 插入机制,以及

3,如何连续下发补丁,补丁的回退机制

Tinker 都已解决上述问题,并且提供了很完整的日志供开发同学分析。

像之前的框架基本上补丁的加载都是在 Application 的 onCreate里或者 attachBaseContext 里,这就会造成一个问题就是 Application 出现问题是无法被修复的, Tinker 为了解决这个问题采用了隔离 Application 的方式, Application 的初始化和声明周期由 MiniLoader 来进行代理这样我们就可以把很多操作放在代理类里完成, Application 和其提前加载的类都是可以进行修复的。

Java虚拟机(一)结构原理与运行时数据区域

参考文章:

运行时数据区域:

  • 程序计数器 也叫做PC寄存器,是一块较小的内存空间

    在一个确定的时刻只有一个处理器执行一条线程中的指令,为了在线程切换后能恢复到正确的执行位置,每个线程都会有一个独立的程序计数器,因此,程序计数器是线程私有的。

  • Java虚拟机栈

    Java虚拟机栈存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等,它的生命周期与线程相同,与线程是同时创建的。

  • 本地方法栈

    它与Java虚拟机栈类似,只不过本地方法栈是用来支持Native方法服务,

  • Java堆

    是被所有线程共享的运行时内存区域。Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆存储的对象被垃圾收集器管理,这些受管理的对象无需也无法显示的销毁。从内存回收的角度,Java堆可以粗略的分为新生代和老年代。从内存分配的角度Java堆中可能划分出多个线程私有的分配缓冲区。不管如何划分,Java堆存储的内容是不变的,进行划分是为了能更快的回收或者分配内存。Java堆的容量可以时固定的,也可以动态的扩展。Java堆的所使用的内存在物理上不需要连续,逻辑上连续即可。

  • 方法区

    是被所有线程共享的运行时内存区域。用来存储已经被Java虚拟机加载的类的结构信息,包括: 运行时常量池、字段和方法信息、静态变量等数据。方法区是Java堆的逻辑组成部分,它一样在物理上不需要连续,并且可以选择在方法区中不实现垃圾收集。方法区并不等同于永久代,只是因为HotSpot VM使用永久代来实现方法区,对于其他的Java虚拟机,比如J9和JRockit等,并不存在永久代概念。


类加载器

双亲委托模式

类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果已经加载就返回该Class,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找,总的来说就是Class文件加载到类加载子系统后,自下而上进行委托,自上而下进行查找,整个过程就是先上后下。

注: 只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类。

采取双亲委托模式主要有两点好处:

  • 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。

  • 更加安全

系统提供的类加载器只能够加载指定目录下的jar包和Class文件,如果想要加载网络上的或者是D盘某一文件中的jar包和Class文件则需要自定义ClassLoader。

实现自定义ClassLoader需要两个步骤:

  • 定义一个自定义ClassLoade并继承抽象类ClassLoader。

  • 复写findClass方法,并在findClass方法中调用defineClass方法。

很多同学会把Java和Android的ClassLoader搞混,甚至会认为Android中的ClassLoader和Java中的ClassLoader是一样的,这显然是不对的。

我们知道Java中的ClassLoader可以加载jar文件和Class文件(本质是加载Class文件),这一点在Android中并不适用,因为无论是DVM还是ART它们加载的不再是Class文件,而是dex文件,这就需要重新设计ClassLoader相关类,我们先来学习ClassLoader的类型。 Android中的ClassLoader类型和Java中的ClassLoader类型类似,也分为两种类型,分别是系统ClassLoader和自定义ClassLoader。其中系统ClassLoader主要有3种分别是BootClassLoader、PathClassLoader和DexClassLoader。

DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk和jar文件),不管是加载哪种文件,最终都是要加载dex文件,为了方便理解和叙述,将dex文件以及包含dex的压缩文件统称为dex相关文件。

DexClassLoader的构造方法有四个参数:

  • dexPath:dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’

  • optimizedDirectory:解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:/data/data//...。

  • librarySearchPath:包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null。

  • parent:父加载器。

DexClassLoader 继承自BaseDexClassLoader ,方法实现都在BaseDexClassLoader中。

PathClassLoader继承自BaseDexClassLoader,实现也都在BaseDexClassLoader中。

PathClassLoader的构造方法中没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的值为:/data/dalvik-cache,很显然PathClassLoader无法定义解压的dex文件存储路径,因此PathClassLoader通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)。Android系统使用PathClassLoader来加载系统类和应用程序的类。

BootClassLoader和PathClassLoader是何时创建的?

  • BootClassLoader是在Zygote进程的入口方法中创建的

  • PathClassLoader则是在Zygote进程创建SystemServer进程时创建的。

InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。

http://liuwangshu.cn/tags/ClassLoader/

https://mp.weixin.qq.com/s?__biz=MzAxMTg2MjA2OA==&mid=2649842083&idx=1&sn=e2553d245ba7601f790fe9f0271815f3&chksm=83bf68f8b4c8e1eebc95c26b4677edf2e8f2fa082dc4c6b5ab2dafb37afce4f3d1dabb0feb91&scene=21#wechat_redirect

https://mp.weixin.qq.com/s?__biz=MzAxMTg2MjA2OA==&mid=2649842379&idx=1&sn=02d49630d412deb9564d9f35fe0f599a&chksm=83bf6b90b4c8e2864546cd6b9cf53d1efe0fe030f1442a3a2c188ad69418f0f8512bc5d9db36&scene=21#wechat_redirect

results matching ""

    No results matching ""