前言
在《Android高性能高稳定性代码覆盖率方案技术实践》一文中,我们实现了高效稳定的代码覆盖率采集方案。不少同学对其背后原理非常感兴趣,甚至发私信来询问底层的理论支撑。为了方便大家了解其中原理,我们写下了这篇详细的解析文章,欢迎大家阅读探讨。
方案中使用了新建ClassLoader,复制目标ClassLoader的classTable字段并进行类加载状态查询的方式,如图:
为什么要采取这样的方式?下面我们来一探究竟。
从ClassLoader入手
我们都知道Android和Java的类加载机制非常相似,都是使用ClassLoader进行。在Java中,要采集类级别的代码覆盖率非常简单,直接反射调用ClassLoader的findLoadedClass方法就行,那为什么在Android中却那么困难呢?这得从Android独有的类加载优化机制说起。
我们先来看看Android类加载涉及的几个主要的ClassLoader,如下图:
其中,
BootClassLoader负责加载系统类
PathClassLoader负责加载应用自定义类
DexClassLoader一般用于动态加载应用安装包以外的类
代码覆盖率应当关注的是应用自定义的类,所以我们重点看看PathClassLoader和DexClassLoader。
我们先来看一下ClassLoader是如何加载一个类的,核心代码都在它的loadClass方法中,如下:
//源码路径:/java/lang/ClassLoader.java
protected Class<?> loadClass(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;
}
可以清楚的看到,ClassLoader加载一个类时主要有以下三个步骤:
1.检查类是否已经加载,如果是,则直接返回加载过的类,否则进入下一步。
2.如果有parent,则尝试从parent加载(双亲委派模式);否则,从BootStrap ClassLoader中查找。如果成功找到类,则直接返回,否则进入下一步。
3.尝试自己加载类。
第一步中用来判断类是否加载的findLoadedClass方法,源码如下:
//源码路径:/java/lang/ClassLoader.java
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())