内容简介:Java 中的Android 中的Android 系统启动时会使用
Java 中的 ClassLoader
可以加载 jar 文件和 Class文件(本质是加载 Class 文件),这一点在 Android 中并不适用,因为无论 DVM 还是 ART 它们加载的不再是 Class 文件,而是 dex 文件。
Android 中的 ClassLoader
类型和 Java 中的 ClassLoader
类型类似,也分为两种类型,分别是 系统 ClassLoader
和 自定义 ClassLoader
。其中 Android 系统 ClassLoader
包括三种分别是 BootClassLoader
、 PathClassLoader
和 DexClassLoader
,而 Java 系统类加载器也包括3种,分别是 Bootstrap ClassLoader
、 Extensions ClassLoader
和 App ClassLoader
。
BootClassLoader
Android 系统启动时会使用 BootClassLoader
来预加载常用类,与 Java 中的 BootClassLoader
不同,它并是由 C/C++ 代码实现,而是由 Java 实现的,1BootClassLoade1 的代码如下所示
// libcore/ojluni/src/main/java/java/lang/ClassLoader.java class BootClassLoader extends ClassLoader { private static BootClassLoader instance; @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); } return instance; } public BootClassLoader() { super(null); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return Class.classForName(name, false, null); } ... } 复制代码
BootClassLoader
是 ClassLoader
的内部类,并继承自 ClassLoader
。 BootClassLoader
是一个单例类, 需要注意的是 BootClassLoader
的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的 。
PathClassLoader
Android 系统使用 PathClassLoader
来加载系统类和应用程序的类,如果是加载非系统应用程序类,则会加载 data/app/$packagename
下的 dex 文件以及包含 dex 的 apk 文件或 jar 文件,不管是加载哪种文件,最终都是要加载 dex 文件,在这里为了方便理解,我们将 dex 文件以及包含 dex 的 apk 文件或 jar 文件统称为 dex 相关文件。 PathClassLoader 不建议开发直接使用。
// libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } } 复制代码
PathClassLoader
继承自 BaseDexClassLoader
,很明显 PathClassLoader
的方法实现都在 BaseDexClassLoader
中。
PathClassLoader
的构造方法有三个参数:
- dexPath:dex 文件以及包含 dex 的 apk 文件或 jar 文件的路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’。
- librarySearchPath:包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为 null
- parent:ClassLoader 的 parent
DexClassLoader
DexClassLoader
可以加载 dex 文件以及包含 dex 的 apk 文件或 jar 文件,也支持从 SD 卡进行加载,这也就意味着 DexClassLoader
可以在应用未安装的情况下加载 dex 相关文件。 因此,它是热修复和插件化技术的基础。
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } } 复制代码
DexClassLoader
构造方法的参数要比 PathClassLoader
多一个 optimizedDirectory
参数,参数 optimizedDirectory
代表什么呢?应用程序在第一次被加载的时候,为了提高以后的启动速度和执行效率,Android 系统会对 dex 相关文件做一定程度的优化,并生成一个 ODEX
文件,此后再运行这个应用程序的时候,只要加载优化过的 ODEX
文件就行了,省去了每次都要优化的时间,而参数 optimizedDirectory
就是代表存储 ODEX
文件的路径,这个路径必须是一个内部存储路径。 PathClassLoader
没有参数 optimizedDirectory
,这是因为 PathClassLoader
已经默认了参数 optimizedDirectory
的路径为: /data/dalvik-cache
。 DexClassLoader
也继承自 BaseDexClassLoader
,方法实现也都在 BaseDexClassLoader
中。
关于以上 ClassLoader
在 Android 系统中的创建过程,这里牵扯到 Zygote
进程,非本文的重点,故不在此进行讨论。
ClassLoader 继承关系
-
ClassLoader
是一个抽象类,其中定义了ClassLoader
的主要功能。BootClassLoader
是它的内部类。 -
SecureClassLoader
类和JDK8
中的SecureClassLoader
类的代码是一样的,它继承了抽象类ClassLoader
。SecureClassLoader
并不是ClassLoader
的实现类,而是拓展了ClassLoader
类加入了权限方面的功能,加强了ClassLoader
的安全性。 -
URLClassLoader
类和JDK8
中的URLClassLoader
类的代码是一样的,它继承自SecureClassLoader
,用来通过URl路径从 jar 文件和文件夹中加载类和资源。 -
BaseDexClassLoader
继承自ClassLoader
,是抽象类ClassLoader
的具体实现类,PathClassLoader
和DexClassLoader
都继承它。
下面看看运行一个 Android 程序需要用到几种类型的类加载器
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var classLoader = this.classLoader // 打印 ClassLoader 继承关系 while (classLoader != null) { Log.d("MainActivity", classLoader.toString()) classLoader = classLoader.parent } } } 复制代码
将 MainActivity
的类加载器打印出来,并且打印当前类加载器的父加载器,直到没有父加载器,则终止循环。打印结果如下:
com.zhgqthomas.github.hotfixdemo D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.zhgqthomas.github.hotfixdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]] com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926 复制代码
可以看到有两种类加载器,一种是 PathClassLoader
,另一种则是 BootClassLoader
。 DexPathList
中包含了很多路径,其中 /data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk
就是示例应用安装在手机上的位置。
双亲委托模式
类加载器查找 Class 所采用的是双亲委托模式,**所谓双亲委托模式就是首先判断该 Class 是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的 BootstrapClassLoader
,如果 BootstrapClassLoader
找到了该 Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。这是 JDK 中 ClassLoader
的实现逻辑,Android 中的 ClassLoader
在 findBootstrapClassOrNull
方法的逻辑处理上存在差异。
// 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) { long t0 = System.nanoTime(); 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. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats } } return c; } 复制代码
上面的代码很容易理解,首先会查找加载类是否已经被加载了,如果是直接返回,否则委托给父加载器进行查找,直到没有父加载器则会调用 findBootstrapClassOrNull
方法。
下面看一下 findBootstrapClassOrNull
在 JDK
和 Android
中分别是如果实现的
// JDK ClassLoader.java private Class<?> findBootstrapClassOrNull(String name) { if (!checkName(name)) return null; return findBootstrapClass(name); } 复制代码
JDK
中 findBootstrapClassOrNull
会最终交由 BootstrapClassLoader
去查找 Class
文件,上面提到过 BootstrapClassLoader
是由 C++ 实现的,所以 findBootstrapClass
是一个 native 的方法
// JDK ClassLoader.java private native Class<?> findBootstrapClass(String name); 复制代码
在 Android 中 findBootstrapClassOrNull
的实现跟 JDK
是有差别的
// Android private Class<?> findBootstrapClassOrNull(String name) { return null; } 复制代码
Android
中因为不需要使用到 BootstrapClassLoader
所以该方法直接返回来 null
正是利用类加载器查找 Class 采用的双亲委托模式,所以可以利用反射修改类加载器加载 dex 相关文件的顺序,从而达到热修复的目的
类加载过程
通过上面分析可知
-
PathClassLoader
可以加载 Android 系统中的 dex 文件 -
DexClassLoader
可以加载任意目录的dex/zip/apk/jar
文件,但是要指定optimizedDirectory
。
通过代码可知这两个类只是继承了 BaseDexClassLoader
,具体的实现依旧是由 BaseDexClassLoader
来完成。
BaseDexClassLoader
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java public class BaseDexClassLoader extends ClassLoader { ... private final DexPathList pathList; public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { this(dexPath, optimizedDirectory, librarySearchPath, parent, false); } /** * @hide */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); if (reporter != null) { reportClassLoaderChain(); } } ... public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) { // TODO We should support giving this a library search path maybe. super(parent); this.pathList = new DexPathList(this, dexFiles); } ... } 复制代码
通过 BaseDexClassLoader
构造方法可以知道,最重要的是去初始化 pathList
也就是 DexPathList
这个类,该类主要是用于管理 dex 相关文件
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); // 查找逻辑交给 DexPathList 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; } 复制代码
BaseDexClassLoader
中最重要的是这个 findClass
方法,这个方法用来加载 dex 文件中对应的 class
文件。而最终是交由 DexPathList
类来处理实现 findClass
DexPathList
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java final class DexPathList { ... /** class definition context */ private final ClassLoader definingContext; /** * List of dex/resource (class path) elements. * Should be called pathElements, but the Facebook app uses reflection * to modify 'dexElements' (http://b/7726934). */ private Element[] dexElements; ... DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { ... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // save dexPath for BaseDexClassLoader this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted); ... } } 复制代码
查看 DexPathList
核心构造函数的代码可知, DexPathList
类通过 Element
来存储 dex 路径
,并且通过 makeDexElements
函数来加载 dex 相关文件,并返回 Element
集合
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { Element[] elements = new Element[files.size()]; int elementsPos = 0; /* * Open all files and load the (direct or contained) dex files up front. */ for (File file : files) { if (file.isDirectory()) { // We support directories for looking up resources. Looking up resources in // directories is useful for running libcore tests. elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null; if (name.endsWith(DEX_SUFFIX)) { // 判断是否是 dex 文件 // Raw dex file (not inside a zip/jar). try { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } catch (IOException suppressed) { System.logE("Unable to load dex file: " + file, suppressed); suppressedExceptions.add(suppressed); } } else { // 如果是 apk, jar, zip 等文件 try { dex = loadDexFile(file, optimizedDirectory, loader, elements); } catch (IOException suppressed) { /* * IOException might get thrown "legitimately" by the DexFile constructor if * the zip file turns out to be resource-only (that is, no classes.dex file * in it). * Let dex == null and hang on to the exception to add to the tea-leaves for * when findClass returns null. */ suppressedExceptions.add(suppressed); } // 将 dex 文件或压缩文件包装成 Element 对象,并添加到 Element 集合中 if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } if (dex != null && isTrusted) { dex.setTrusted(); } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; } 复制代码
总体来说, DexPathList
的构造函数是将 dex 相关文件(可能是 dex、apk、jar、zip , 这些类型在一开始时就定义好了)封装成一个 Element
对象,最后添加到 Element
集合中
其实,Android 的类加载器不管是 PathClassLoader,还是 DexClassLoader,它们最后只认 dex 文件,而 loadDexFile
是加载 dex 文件的核心方法,可以从 jar、apk、zip 中提取出 dex
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java public Class<?> findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz != null) { return clazz; } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } 复制代码
在 DexPathList
的构造函数中已经初始化了 dexElements
,所以这个方法就很好理解了,只是对 Element 数组进行遍历,一旦找到类名与 name 相同的类时,就直接返回这个 class,找不到则返回 null
热修复实现
通过上面的分析可以知道运行一个 Android
程序是使用到 PathClassLoader
,即 BaseDexClassLoader
,而 apk 中的 dex 相关文件都会存储在 BaseDexClassLoader
的 pathList
对象的 dexElements
属性中。
那么热修复的原理就是将改好 bug 的 dex 相关文件放进 dexElements
集合的头部,这样遍历时会首先遍历修复好的 dex 并找到修复好的类,因为类加载器的双亲委托模式,旧 dex 中的存有 bug 的 class 是没有机会上场的。这样就能实现在没有发布新版本的情况下,修复现有的 bug class
手动实现热修复功能
根据上面热修复的原理,对应的思路可归纳如下
- 创建
BaseDexClassLoader
的子类DexClassLoader
加载器 - 加载修复好的 class.dex (服务器下载的修复包)
- 将自有的和系统的
dexElements
进行合并,并设置自由的dexElements
优先级 - 通过反射技术,赋值给系统的
pathList
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Effective Engineer
Edmond Lau / The Effective Bookshelf, Palo Alto, CA. / 2015-3-19 / USD 39.00
Introducing The Effective Engineer — the only book designed specifically for today's software engineers, based on extensive interviews with engineering leaders at top tech companies, and packed with h......一起来看看 《The Effective Engineer》 这本书的介绍吧!