Android热修复基础篇(二),移动智能终端开发技术题库

本文详细解析了Android中类加载的过程,重点介绍了PathClassLoader的加载机制,涉及双亲委托模型。文章指出,双亲委托机制避免了类的重复加载,并确保核心API的安全。同时,文章探讨了热修复技术中如何利用类加载机制替换已加载的类,强调了补丁包的位置重要性。对于已加载的类,无法通过热修复进行替换,需要重启以清除缓存。
摘要由CSDN通过智能技术生成

由于 PathClassLoader 源码太简单,没有找到我们想要的 loadClass() 方法,所以我们来看看其父类 BaseDexClassLoader,发现还是没有,继续找父类 ClassLoader,在这里面我们看到了loadClass()

//发现了两个 是重载方法

public Class<?> loadClass(String name) throws ClassNotFoundException {

//可以看到 最后还是调到了 含两个参数的 loadClass 方法,

return loadClass(name, fal

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

se);

}

protected Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

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

//找缓存

//1、在Android中 class 存在于 dex file 中

//2、public PathClassLoader(String dexPath, ClassLoader parent) 第一个参数就是就是 dex 文件的存放路径

//3、所以 classLoader 在加载类的时候 是通过 IO 操作将文件读进来 ,然后按照 dex 格式解析出每一个类!

//4、为了避免你重复用一个类的时候反复 IO读取+解析 所以,先找缓存

Class<?> c = findLoadedClass(name);

//如果没找到 缓存 则进入 if语句

if (c == null) {

try {

if (parent != null) {

//1、前面我就告诉过大家 parent =》 BootClassLoader

//2、也就是说 先在 BootClassLoader 里找类

c = parent.loadClass(name, false);

} else {

//如果你的 parent==null 系统调用findBootstrapClassOrNull方法查找,看方法名字就知

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;

}

源码分析 注释已经写得很清楚了,在这里面就涉及到传说中的 双亲委托机制

为什么要用这种 双亲委托机制 ?

定义:某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成委托加载任务,就成功返回;只有父类加载器无法完成加载任务或者没有父类加载器时,才自己去加载。

使用原因:

  • 避免重复加载,当父类加载器已经加载了该类的时候,就没必要子ClassLoader在加载一次。

举例:加载 MainActivity 时肯定 得加载其父类  Activity,而 Activity 是由 父加载器 BootClassLoader 加载了,那你不用 双亲委托机制 ,非得 PathClassLoader 自己去加载,那不是重复了吗!!!

  • 安全性考虑,防止核心API库被随意篡改。

举例:String类 我们都知道是系统自带的 是由 BootClassLoader 加载,而你不用 双亲委托机制 ,非得用 PathClassLoader 加载,那不是加载到你写的 String类了,这样很不安全!!!

双亲委托机制 是我们本段内容的一个小插曲,我们回归主题,看一下 PathClassLoader 的 findClass() ,(PathClassLoader 源码很简单,当然没有我们想要的,所以我们得看其直接父类的(BaseDexClassLoader)实现)

public class BaseDexClassLoader extends ClassLoader {

/什么是DexPathList呢?解释如下:/

/**

  • A pair of lists of entries, associated with a {@code ClassLoader}.

  • One of the lists is a dex/resource path — typically referred

  • to as a “class path” — list, and the other names directories

  • containing native code libraries. Class path entries may be any of:

  • a {@code .jar} or {@code .zip} file containing an optional

  • top-level {@code classes.dex} file as well as arbitrary resources,

  • or a plain {@code .dex} file (with no possibility of associated

  • resources).

  • This class also contains methods to use these lists look up

  • classes and resources.

*/

/*这是关联一个ClassLoder 的条目列表,这其中有dex/resource path

通常称为“class path” ,其他名称目录包含本地代码库,class path条目

可能是包含一个可选的顶级class.dex的zip文件,jar文件,以及任意资源

*/

private final DexPathList pathList;

/*重写了ClassLoder的这个方法,这个方法会在loadClass中调用,当loadClass时,具体步骤是这样的:

先查找已经加载的类,如果有返回,如果没有,用父装载器去加载这个类,如果失败,则会调用我们重写的findclass这个方法*/

@Override

protected Class<?> findClass(String name) throws ClassNotFoundException {

List suppressedExceptions = new ArrayList();

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;

}

}

附上DexPathList源码:

final class DexPathList {

/**

  • List of dex/resource (class path) elements.

  • Should be called pathElements, but the Facebook app uses reflection

  • to modify ‘dexElements’ (http://b/7726934).

  • 注意了啊:这里是一个数组 所以 dex 文件遍历是有顺序的

  • [patch.dex,classes.dex,classes2.dex]

  • patch.dex 得放到最前面,你放到后面就没用了,最先找到的还是你那个有错误的 dex
    

*/

private final Element[] dexElements;

public Class findClass(String name, List 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;

}

}

到此为止,类的加载源码已经分析完了,下面我们通过几个问题来帮大家梳理一下

1、类是怎么被加载的?

解:由于是加载你自己写的类,所以,我们定位到 PathClassLoader 这个类,为了找到 loadClass() 这个方法,我们得看 PathClassLoader 的父类 ClassLoader,在这里我们分析知道了其运用了 双亲委托机制,先在 父类加载器 BootClassLoader里找,由于是我们自己写的类,BootClassLoader 肯定找不到,于是,我们来分析 PathClassLoader 的findClass() 方法,这还是得看 父类 BaseDexClassLoader 的 findClass() 方法,从源码中我们可以看到,真正查找类是由 DexPathList pathList.findClass() 完成的,在它源码里面,是遍历了每个 dexFile 来查找我们调用的类(Element 是DexFile的包装类)。

所以,一句话概括就是:类 是通过 类加载器 遍历 dexFile 而加载的

2、怎么使用补丁包中的类?

解:把你的 补丁包(patch.dex),放到 dexElements 数组的 **最前面,最前面,最前面!!!**原因如下:

3、已经被加载过的类还能够替换修复吗?

解:当然不行啊!!!,已经被加载 === 有缓存了,缓存里能找到这个类,后面的方法都不执行了啊。

这里就是为什么 Tinker 和 QZone 热修复解决方案得重启才行。重启就是为了清理缓存,让所有类重新加载,避免因为错误的类在缓存中导致 正确的类不被加载!!!

4、怎样保证正确class的dex先加载?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值