插件化/热修复

image-20210410164426213

插件化和热修复不是同一个概念,虽然站在技术实现的角度来说,他们都是从系统加载器的角度出发,无论是采用hook方式,亦或是代理方式或者是其他底层实现,都是通过“欺骗”Android 系统的方式来让宿主正常的加载和运行插件(补丁)中的内容;但是二者的出发点是不同的。插件化顾名思义,更多是想把需要实现的模块或功能当做一个独立的提取出来,减少宿主的规模,当需要使用到相应的功能时再去加载相应的模块。热修复则往往是从修复bug的角度出发,强调的是在不需要二次安装应用的前提下修复已知的bug。

  • PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。
  • DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,也就是我们一开始提到的补丁。
  • BaseDexClassLoader: 是 PathClassLoader 和 DexClassLoader 的父类,其内有一个 DexPathList 属性,实现了 findClass 方法逻辑,PathClassLoader 和 DexClassLoader 都只是在构造函数上对其做了简单封装而已。

类加载器

每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一个原生的类加载器,它只加载Java API等可信类,通常只是在本地磁盘中加载,这些类一般就够我们使用了。如果我们需要从远程网络或数据库中下载.class字节码文件,那就需要我们来挂载额外的类加载器。

一般来说,类加载器是按照树形的层次结构组织的,每个加载器都有一个父类加载器。另外,每个类加载器都支持代理模式,即可以自己完成Java类的加载工作,也可以代理给其它类加载器。

类加载器的加载顺序有两种,一种是父类优先策略,一种是是自己优先策略,父类优先策略是比较一般的情况(如JDK采用的就是这种方式),在这种策略下,类在加载某个Java类之前,会尝试代理给其父类加载器,只有当父类加载器找不到时,才尝试自己去加载。自己优先的策略与父类优先相反,它会首先尝试子经济加载,找不到的时候才要父类加载器去加载,这种在web容器(如tomcat)中比较常见。

插件化:

  • 插件化:插件化是体现在功能拆分方面的,它将某个功能独立提取出来,独立开发,独立测试,再插入到主应用中。依次来较少主应用的规模。

旧插件化框架方案核心解决两个问题:

一、插件类class的加载问题

这个问题各家的解决方案都是统一的,也就是通过dexclassloader,加载插件apk。

二、activity为主的生命周期问题

这个问题分为两种解决方式:

1、通过壳Activity

通过在宿主工程预注册 壳Activity,通过壳Activity 持有并转调 插件Activity的每个生命周期

a. 插件Activity继承Activity类(需要反射)

然后壳Activity通过反射转调各个生命周期甚至还可能需要避免它的super方法被调用。

这样做要解决一个额外的问题。

Activity被系统构造出实例之后,并不是直接调用onCreate方法的。首先会调用它的attach方法。attach方法实际上就是Activity的初始化方法,系统通过这个方法向Activity注入一些私有变量,比如Window、ActivityThread等等。插件Activity由于是我们壳子Activity自己new出来的,所以系统不会调用插件Activity的attach方法初始化它。Activity如果没有初始化就被调用了onCreate会有什么问题呢?我们前面说了我们的一个前提是插件Activity要求也要能正常编译安装运行,所以插件Activity的onCreate方法里一定写了super.onCreate()调用。我们还要求对插件代码无侵入性,所以也不能在这个调用外面包一层“if (不是插件模式)”。那么在插件环境下,这个super.onCreate()就一定会执行。Activity基类的onCreate方法就会使用那些应该初始化过的私有变量,但是现在它们没有初始化。所以这一类插件框架方案就要解决这个问题。所以要么是反射调用attach方法,传入从壳子Activity拿到的私有变量,比如说反射出壳子Activity的ActivityThread对象,传给插件Activity的attach方法。要么就干脆直接用反射枚举读写壳子Activity和插件Activity的私有变量,把它们写成一样的完成这个初始化。所以这就是为什么旧框架需要使用反射和私有API。

b. 插件Activity继承 普通插件基类 (无需反射)

Shadow使用AOP的方式替换所有插件中的activity类为普通插件基类,但插件基类申明各个activity的实现

2、hook framework,包括Activity启动流程

a .比如virtualApk

是替换系统的instrumentation(替换成自定义的子类),在走到AMS前的execStartActivity中把传入的intent的component改成壳Activity,然后在AMS回调回来后的performLaunchActivity中,再把intent的conponent改回去。达到欺骗系统的目的。

b.比如Replugin

比如RePlugin为首的hook 系统classLoader改成自定义ClassLoader,然后hook了

——————————————

旧框架就是 壳Activity 转调 插件Activity的方案,市面上还有很多插件框架也是这种方案。大家只是在实现转调的手段上不一样。

Android9.0之后限制了反射的使用,黑名单直接禁止了,腾讯开源的Shadow解决Activity等组件生命周期的方法解析 - 掘金 (juejin.cn)

shadow框架基于 第一种壳代理Activity的方式,但是第一种代理Activity的方式为了实现插件也能独立运行的特性,需要插件Activity继承Activity,shadow其实就是把

把一个插件Activity套在一个宿主Activity之中,然后想办法实现一个转调关系。如果插件Activity是一个真的Activity,那这个插件就可以正常编译安装运行,对开发插件或者直接上架插件App非常有利。但是由于它是个系统的Activity子类,它就有很多方法不能直接调用,甚至还可能需要避免它的super方法被调用。如果插件Activity不是一个真的Activity,只是一个跟Activity有差不多方法的普通类,这件事就简单多了,只需要让壳子Activity持有它,转调它就行了。但这种插件的代码正常编译成独立App安装运行会比较麻烦,代码中可能会出现很多插件相关的if-else,也不好。

热修复

  • 热修复:热修复是体现在bug修复方面的,它实现的是不需要重新发版和重新安装,就可以去修复已知的bug。

Tinker实现原理简介:

在 DexPathList.findClass() 过程,一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类在不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。

利用PathClassLoader和DexClassLoader去加载与bug类同名的类,替换掉bug类,进而达到修复bug的目的,原理是在app打包的时候阻止类打上CLASS_ISPREVERIFIED标志,然后在
热修复的时候动态改变BaseDexClassLoader对象间接引用的dexElements,替换掉旧的类

在这里插入图片描述

Author

white crow

Posted on

2021-04-10

Updated on

2024-03-25

Licensed under