介绍 Android
类加载机制,双亲委派模型,BaseDexClassLoader, DexClassLoader, PathClassLoader
加载 dex, jar, apk
等文件。
JVM
虚拟机执行方式 Java
的口号是“一次编译,到处运行”;也就是说 Java
程序的可执行文件(class
文件,字节码),是与机器硬件平台无关的;在程序运行时,由 JVM
将字节码翻译成当前运行环境处理器 CPU
的机器指令来执行。JVM
中通常有两种执行方式:
解释执行 字节码中每一行代码,由 JVM
解释器解释翻译成机器码再运行,依此循环从字节码中取出下一行解释并执行,因此解释执行的速度比较慢。
编译执行JVM
将字节码编译成机器码后,运行机器码的执行方式,执行效率很高。
通常情况下,JVM
采用混合执行的方式来运行程序的:将高频代码编译成机器码编译执行,其他代码直接解释执行。
编译方式 编译执行的过程,有两种常见的编译方式:
JIT:Just In Time
指在运行时编译,边运行边编译,属于动态编译。在程序启动时,将字节码编译成机器码,然后运行。优点是能更好的利用 Java
动态行为的特性(特别是多态,只有在运行时才能确定执行哪个方法);缺点是动态编译时间会计算在程序运行时间中,特别是会导致程序启动时间变长,以及程序运行期间编译带来的性能消耗。JIT
通常只会将执行频率高的热方法动态编译,获取更好的效率平衡。
AOT:Ahead Of Time
指在运行前编译,编译完后再运行,属于静态编译。在程序运行前,将字节码编译成机器码存储到本地,程序启动时,直接启动对应的机器码。优点是不存在动态编译的内存和性能消耗,直接运行本地机器码启动速度块;缺点是 Java
动态特性带来的复杂性,影响了静态编译的代码质量。
编译方式对比:
动态 JIT
静态 AOT
平台无关性
有
无
代码质量
优秀
良好
利用动态行为
是
否
类和层次结构的知识
有
无
编译时间
有限制,有运行时成本
限制很少,无运行时成本
运行时性能影响
有
无
编译方式
需要谨慎编译,由 JIT
处理
需要谨慎编译,由开发人员处理
总的来说,动态编译 JIT
能提供最好的系统稳定性能;而静态编译 AOT
能提供最好的交互性能。
Android
虚拟机概念 Android
虚拟机实现有两个阶段:
Dalvik
Android K 4.4
之前的版本都是使用的 Dalvik
虚拟机。Java
生成的 class
文件由 dx
工具转换为 Dalvik
字节码即 dex
文件,之后进行签名对齐等操作变成 APK
文件。而 Dalvik
虚拟机负责将 dex
字节码解释为机器码,解释执行的速度慢效率低;从 Android 2.2 froyo
开始引入 JIT
动态编译执行方式,APP
运行时,JIT
将执行次数较多的 dex
文件动态编译为机器码缓存起来,后续再次执行时大大提高运行速度。JIT
动态编译的特点是:每次打开 APP
运行时,都需要重新编译。
ART: Android Runtime
Android K 4.4
采用了 Dalvik
和 ART
共存方式,两者可以相互切换;从 Android L 5.0
开始彻底丢弃 Dalvik
全面转换为 ART
方式。ART
虚拟机采用的是 AOT
静态编译执行方式,APP
在第一次安装时,dex
字节码会被预先编译成机器码;之后打开 APP
运行时,不需要额外的解释翻译工作,直接使用本地机器码执行,提高运行速度。
两者的区别
Dalvik
是运行时编译;ART
是运行前编译(安装时编译)
Dalvik
每次运行时都会将执行频繁的 dex
文件编译为机器码,运行启动时间加长,边运行边编译会额外销毁内存和系统性能
ART
在安装时编译,安装时间会延长;把程序代码转换成机器语言存储在本地,会消耗掉更多的存储空间,增幅通常不会超过应用代码包大小的 20%
文件格式
dex
文件格式 魔数:dex
,Android
平台可执行文件,字节码;每个 APK
中包含一个或多个 dex
文件格式的可执行文件。
odex
文件格式 魔数:dey
,odex: Optimize Dex
即优化后的 dex
文件。Dalvik
虚拟机从 APK
中将 dex
文件提取出来优化后生成 odex
文件,并存储在本地,后期运行时直接编译解释 odex
文件(仍然是字节码,dey
字节码 )。而 APK
被提取后可以有也可以没有 dex
文件;在多 dex
文件的 APK
中,提取优化后只会生成一个 odex
文件。
oat
文件格式 魔数:.elf
,是 ELF
格式的可执行文件。ART
虚拟机在 APK
安装时,将 dex
编译为本地机器码,并生成对应的 oat
文件格式的文件,可以直接执行。
vdex
文件格式 魔数:vdex
,是 APK
中 dex
文件的一份拷贝,同时会将多个 dex
合并为一个文件。在 Android O 8.0
之前,oat
格式文件除了机器码还包含一份 dex
的拷贝;但在 Android O
之后,dex2oat
静态编译时会产生两个文件:odex, vdex
,其中 odex
为机器码,通常很小;而 vdex
则是原始 dex
的一份拷贝,它是一个全新格式的文件(不是 ELF
格式)。
art
文件格式 魔数:art
,是一个 img
文件,表示类对象映像;这个 img
文件直接被映射到 ART
虚拟机的堆空间中,包含了 oat
中的某些对象实例以及函数地址。
为保证 Dalvik
和 ART
的兼容性,以及历史遗留问题,可能会使用相同文件后缀表示不同的文件格式 ,非常容易混淆。比如 .dex
后缀可以是 dex, oat
文件,odex
后缀可以是 odex, oat
文件等。
转换流程图 Java
源文件转换为 oat
格式文件的流程图:
文件分析工具
dex
文件生成及分析工具d8.bat/dx.bat
:生成工具,位置路径为 Windows sdk\build-tools\28.0.3
。示例:d8.bat --output=test.jar Test.jar test.class
;其中 output
必须是 .zip, .jar
,input
可以是 .dex, .apk, .jar, .class, .zip
。dexdump/dexdump2
:分析工具,在 AOSP
编译完后 out/host$ cd linux-x86/bin/
目录下会生成对应工具。
oat
文件 因为是 ELF
格式文件,所以通用工具都可以读出,如:readelf
,AOSP
编译完后生成的 otadump
也可以分析。
vdex
文件github: vdexExtractor ,这款工具可以从 vdex
中提取出原始的 dex
文件。
示例 如下信息手机系统为 Android O 8.1
:
系统编译 framework
生成文件 按照 out
目录生成文件路径,拷贝到手机对应路径中;在系统启动时,会拷贝一份到 /data/dalvik-cache/arm64
目录下。
1 2 3 4 5 6 7 8 6.1M system/framework/arm64/boot-framework.art 25M system/framework/arm64/boot-framework.oat 20M system/framework/arm64/boot-framework.vdex 7.4M system/framework/framework.jar // 系统启动后在 /data/dalvik-cache/arm64 目录下生成对应文件 6.0M system@framework@boot-framework.art 0 system@framework@boot-framework.oat -> /system/framework/arm64/boot-framework.oat 0 system@framework@boot-framework.vdex -> /system/framework/arm64/boot-framework.vdex
系统编译应用 Email
生成文件 系统应用在安装时同样会拷贝到 /data/dalvik-cache/arm64
目录下,但是会多生成一个 classes.art
文件。注意:在 data
目录生成的 dex
后缀的文件,实际上是 oat
文件 ,pull
出来后魔数是 .elf
格式的。
1 2 3 4 5 6 7 6.7M system/app/Email/Email.apk 76K system/app/Email/oat/arm64/Email.odex 4.1M system/app/Email/oat/arm64/Email.vdex // 系统启动后在 /data/dalvik-cache/ 目录下生成对应文件 32K /data/dalvik-cache/arm64/system@app@Email@Email.apk@classes.art 72K /data/dalvik-cache/arm64/system@app@Email@Email.apk@classes.dex 4.0M /data/dalvik-cache/arm64/system@app@Email@Email.apk@classes.vdex
安装 qsbk.apk
生成的文件,apk
中包含多个 dex
文件 第三方应用在安装时,直接安装到 /data/app/
目录下,并根据包名随机生成一个字符串做目录区分。注意:生成的 base.odex
实际上是 oat
文件 ,pull
出来后魔数是 .elf
格式的。
1 2 3 32M /data/app/qsbk.app-9P***/base.apk 276K /data/app/qsbk.app-9P***/oat/arm/base.odex 15M /data/app/qsbk.app-9P***/oat/arm/base.vdex
小结 系统自带 framework, app
都会生成 art, oat, vdex
三种文件格式的文件;而第三方应用安装后,只会生成 oat, vdex
两种文件格式的文件。其中 framework
中后缀为 .oat
、系统 app
中后缀为 dex
、第三方 app
中后缀为 odex
,这三个 oat, dex, odex
后缀的文件,实际上都是 oat
文件,注意别混淆了 ,仅仅是后缀不同而已。
代码速查表 本文基于 Android O 8.1
源码分析 Android
平台的 ClassLoader
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 libcore/ojluni/src/main/java/java/lang/Class.java libcore/ojluni/src/main/java/java/lang/ClassLoader.java libcore/libart/src/main/java/java/lang/VMClassLoader.java libcore/dalvik ./src/main/java/dalvik/system/BaseDexClassLoader.java ./src/main/java/dalvik/system/InMemoryDexClassLoader.java ./src/main/java/dalvik/system/DexClassLoader.java ./src/main/java/dalvik/system/PathClassLoader.java ./src/main/java/dalvik/system/DelegateLastClassLoader.java ./src/main/java/dalvik/system/DexPathList.java ./src/main/java/dalvik/system/DexFile.java art/runtime/native/dalvik_system_DexFile.cc art/runtime/native/java_lang_VMClassLoader.cc
Android
类加载器类图结构
本文不分析 SecureClassLoader, URLClassLoader
这两个类加载器。
ClassLoader
:抽象类,类加载器的最终父类
BootClassLoader
:启动类加载器;在双亲委托模型中,是第一级父加载器
BaseDexClassLoader
:Android
平台加载 dex
文件的基类
PathClassLoader
:系统类加载器,也相当于应用类加载器,是用户自定义类加载器默认的父加载器;APK
文件都是该加载器加载的
DexClassLoader
:主要用于从包含 classes.dex
的 .jar, .apk
文件中加载类,而这些文件并没有随着应用一起安装
InMemoryDexClassLoader
:主要用于加载内存中的 dex
文件,而这些内存中的文件并不需要存储在本地
DelegateLastClassLoader
:最近委托查找策略类加载器,并不完全按照双亲委派模型来加载的,会提前执行 findClass
从加载 dex
文件中查找类,然后才是双亲委派模型
从各个博客看下来,Classloader
在 Android
不同大版本中不管是代码位置还是子类都在不停的变化。
自定义类加载器时,如果不指定父加载器,则默认其父加载器为 PathClassLoader
;通过 Class
获取加载器时,如果其加载器为空,则指定其加载器为 BootClassLoader
。
双亲委派模型
类加载器的双亲委派模型:要求除了启动类加载器外,其余的类加载器都应当有自己的父加载器(父子不是继承关系,而是组合关系来复用父类代码)。双亲委派模型并不是强制要求,只是 Java
的推荐方式,可以通过重新加载方法来改变。 双亲委派模型原则:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。Android
和标准 Java
对比:没有扩展类加载器,启动加载器 BootClassLoader
也是 Java
实现的。
父加载器和父类加载器是两个不同的概念:父加载器是参考双亲委派模型在构造方法中指定的;父类加载器表示当前加载器的父类(类图结构上是继承关系)。
ClassLoader
ClassLoader
是抽象类,其他所有的类加载都是它的继承类;有以下几个特点:
标准 Java
中系统默认加载器为 AppClassLoader
;而 Android
中系统类加载器是 PathClassLoader
系统类加载器 PathClassLoader
的父加载器为启动加载器 BootClassLoader
Android
启动加载器 BootClassLoader
是 ClassLoader
的内部类,也是其子类;默认为顶层父加载器
ClassLoader
构造方法中指定父加载器,如果不指定:父加载器默认为系统加载器 PathClassLoader
;即:自定义类加载器,如果不指定父加载器,则其父加载器默认为 PathClassLoader
loadClass
实现了双亲委派模型
findClass, defineClass
必须在子类中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 public abstract class ClassLoader { // 系统类加载器 static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } // 系统类加载器为 PathClassLoader private static ClassLoader createSystemClassLoader() { String classPath = System.getProperty("java.class.path", "."); String librarySearchPath = System.getProperty("java.library.path", ""); // String[] paths = classPath.split(":"); // URL[] urls = new URL[paths.length]; // for (int i = 0; i < paths.length; i++) { // try { // urls[i] = new URL("file://" + paths[i]); // } // catch (Exception ex) { // ex.printStackTrace(); // } // } // // return new java.net.URLClassLoader(urls, null); // TODO Make this a java.net.URLClassLoader once we have those? // PathClassLoader 的父加载器为 BootClassLoader return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance()); } // 系统加载器的类 PathClassLoader.class protected final Class<?> findSystemClass(String name) throws ClassNotFoundException { return Class.forName(name, false, getSystemClassLoader()); } // 系统类加载器为 PathClassLoader public static ClassLoader getSystemClassLoader() { return SystemClassLoader.loader; } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; public final ClassLoader getParent() { return parent; } private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; } protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } // 如果不指定父加载器,则其父加载器默认为 PathClassLoader protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } // 实现双亲委派模型 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded // 1. 先确认类是否已经被加载 Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { // 2. 如果没有被加载,父加载器是否为空 // 不为空则父加载器加载,依次递归 c = parent.loadClass(name, false); } else { // 3. 这里沿用标准 Java 的双亲加载模型 // 但是 Android 中并没有 Bootstrap c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader // 4. 如果父加载器抛出 ClassNotFoundException // 说明父加载器无法完成类加载请求 } if (c == null) { // If still not found, then invoke findClass in order // to find the class. // 5. 父加载器无法完成加载或者其他原因没有加载成功 // 则调用本身的 findClass 进行类加载 c = findClass(name); } } return c; } // 通过本地代码在 ART 中查找 protected final Class<?> findLoadedClass(String name) { ClassLoader loader; if (this == BootClassLoader.getInstance()) loader = null; else loader = this; return VMClassLoader.findLoadedClass(loader, name); } // Android 中没有Bootstrap,直接返回 null private Class<?> findBootstrapClassOrNull(String name) { return null; } protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } protected final Class<?> defineClass(...) throws ClassFormatError { throw new UnsupportedOperationException("..."); } ... }
从源码 loadClass
中再描述下双亲委派模型:
先确认类是否已经被加载
如果没有被加载,且父加载器不为空;使用父加载器加载。依次递归
父加载器为空,使用 Bootstrap
来加载,但是 Android
中并不存在该加载器,直接返回 null
父加载器无法完成加载或者其他原因没有加载成功,则调用当前递归到的类加载器 findClass
进行类加载
BootClassLoader
启动类加载器 BootClassLoader
,是 ClassLoader
的成员内部类,也是 ClassLoader
的子类;是顶层父加载器。和 JVM
不一样,Android
的启动类加载器是 Java
实现的。有如下特点:
启动类加载器 BootClassLoader
的父加载器为 null
,它是最顶层的加载器
因为是最顶层的父加载器,在 loadClass
中如果发现类没有加载,直接在这一层 findClass
findClass
是通过反射 Class.classForName
(它是一个 native
方法,标准 Java
中并存在) 实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class BootClassLoader extends ClassLoader { private static BootClassLoader instance; @... 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 ); } @Override protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className); if (clazz == null ) { clazz = findClass(className); } return clazz; } ... }
Class
因为 Android
修改了标准 Java
的类加载器,所以在 Class.java
中也做了对应修改。主要有以下几个特点:
默认类加载器和调用者为同一个加载器
获取类加载器时,如果加载器为空,则指定其加载器为 BootClassLoader
native
方法 classForName
,从虚拟机中加载类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public final class Class <T > implements java .io .Serializable , GenericDeclaration , Type , AnnotatedElement { ... private transient ClassLoader classLoader; public static Class<?> forName(String className) throws ClassNotFoundException { return forName(className, true , VMStack.getCallingClassLoader()); } public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null ) { loader = BootClassLoader.getInstance(); } Class<?> result; try { result = classForName(name, initialize, loader); } catch (ClassNotFoundException e) { Throwable cause = e.getCause(); if (cause instanceof LinkageError) { throw (LinkageError) cause; } throw e; } return result; } @FastNative static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException; public ClassLoader getClassLoader () { if (isPrimitive()) { return null ; } return (classLoader == null ) ? BootClassLoader.getInstance() : classLoader; } ... }
PathClassLoader
Android
的系统类加载器,也相当于应用类加载器,是用户自定义类加载器默认的父加载器;类中只有构造方法,它继承了 BaseDexClassLoader
。APK
加载时,默认使用该加载器加载到 ART
中。
1 2 3 4 5 6 7 8 9 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); } }
DexClassLoader
继承了 BaseDexClassLoader
,只有构造方法。主要用于从包含 classes.dex
的 .jar, .apk
文件中加载类,而这些文件并没有随着应用一起安装;比如放在 assert
目录下等等。 而实际上,从构造方法传递的参数来看:DexClassLoader, PathClassLoader
并没有任何区别!!
1 2 3 4 5 6 public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super (dexPath, null , librarySearchPath, parent); } }
InMemoryDexClassLoader
继承了 BaseDexClassLoader
,只有构造方法。主要用于加载内存中的 dex
文件,而这些内存中的文件并不需要存储在本地;方便网络下载并加载。
1 2 3 4 5 6 7 8 9 public final class InMemoryDexClassLoader extends BaseDexClassLoader { public InMemoryDexClassLoader (ByteBuffer[] dexBuffers, ClassLoader parent) { super (dexBuffers, parent); } public InMemoryDexClassLoader (ByteBuffer dexBuffer, ClassLoader parent) { this (new ByteBuffer[] { dexBuffer }, parent); } }
DelegateLastClassLoader
DelegateLastClassLoader
继承 PathClassLoader
,是最近委托查找策略;它加载类和资源的策略如下(代码中也有详细注释):
首先查看类是否已经被加载
然后使用最顶层启动类加载器 BootClassLoader
来加载
然后使用当前类加载器 findClass
,搜索与此类加载器的 dexPath
关联的 dex
文件列表中,是否已经加载
最后才是委托父加载器加载
从代码中可以看到,DelegateLastClassLoader
并没有完全遵循双亲委派模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public final class DelegateLastClassLoader extends PathClassLoader { public DelegateLastClassLoader (String dexPath, ClassLoader parent) { super (dexPath, parent); } public DelegateLastClassLoader (String dexPath, String librarySearchPath, ClassLoader parent) { super (dexPath, librarySearchPath, parent); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> cl = findLoadedClass(name); if (cl != null ) { return cl; } try { return Object.class.getClassLoader().loadClass(name); } catch (ClassNotFoundException ignored) { } ClassNotFoundException fromSuper = null ; try { return findClass(name); } catch (ClassNotFoundException ex) { fromSuper = ex; } try { return getParent().loadClass(name); } catch (ClassNotFoundException cnfe) { ... throw fromSuper; } } ... }
BaseDexClassLoader
BaseDexClassLoader
是 Android
平台加载 dex
文件的基类,所有从 dex
文件中查找加载的类 findClass
都是在这里实现。 构造方法中各参数的含义:
dexPath
:包含 dex
文件的绝对路径列表;文件可以是 apk, jar
,文件列表分隔符为 :
optimizedDirectory
:没有任何作用,为了兼容早期的版本
librarySearchPath
:native
库所在文件目录的绝对路径列表;文件目录分隔符默认为 :
parent
:当前类加载器的父加载器
dexFiles
:包含 dex
文件的二进制数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class BaseDexClassLoader extends ClassLoader { ... private final DexPathList pathList; public BaseDexClassLoader (String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { super (parent); this .pathList = new DexPathList(this , dexPath, librarySearchPath, null ); ... } public BaseDexClassLoader (ByteBuffer[] dexFiles, ClassLoader parent) { super (parent); this .pathList = new DexPathList(this , dexFiles); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null ) { ClassNotFoundException cnfe = new ClassNotFoundException(...); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; } public void addDexPath (String dexPath) { pathList.addDexPath(dexPath, null ); } ... }
findClass
流程图:
dex
文件加载与解析DexFile
每个 jar, apk, dex
文件对应一个 DexFile
类实例,它用来将对应的文件加载到虚拟机中。
所有的 dex, jar ,apk
文件,最终都是通过 openDexFile
加载到虚拟机中的
如果是通过 dex
来加载类,最终会走到 defineClass
从虚拟机中加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public final class DexFile { private Object mCookie; private Object mInternalCookie; private final String mFileName; ... public Class loadClass (String name, ClassLoader loader) { String slashName = name.replace('.' , '/' ); return loadClassBinaryName(slashName, loader, null ); } public Class loadClassBinaryName (String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, this , suppressed); } private static Class defineClass (String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed) { Class result = null ; try { result = defineClassNative(name, loader, cookie, dexFile); } catch (NoClassDefFoundError e) { if (suppressed != null ) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null ) { suppressed.add(e); } } return result; } private static Object openDexFile (String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException { return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null ) ? null : new File(outputName).getAbsolutePath(), flags, loader, elements); } private static Object openInMemoryDexFile (ByteBuffer buf) throws IOException { if (buf.isDirect()) { return createCookieWithDirectBuffer(buf, buf.position(), buf.limit()); } else { return createCookieWithArray(buf.array(), buf.position(), buf.limit()); } } private static native Class defineClassNative (String name, ClassLoader loader, Object cookie, DexFile dexFile) ; private static native Object openDexFileNative (String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) ; private static native Object createCookieWithDirectBuffer ( ByteBuffer buf, int start, int end) ; private static native Object createCookieWithArray (byte [] buf, int start, int end) ; ... }
DexPathList
内部类:
Element
可能叫 DexElement
更合适,但是由于历史原因,可能会存在反射调用此类的情形。每个 Element
对应一个 DexFile
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static class Element { private final File path; private final DexFile dexFile; private ClassPathURLStreamHandler urlHandler; private boolean initialized; ... public Class<?> findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) { return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null ; } public URL findResource (String name) { for (Element element : dexElements) { URL url = element.findResource(name); if (url != null ) { return url; } } return null ; } public Enumeration<URL> findResources (String name) { ArrayList<URL> result = new ArrayList<URL>(); for (Element element : dexElements) { URL url = element.findResource(name); if (url != null ) { result.add(url); } } return Collections.enumeration(result); } }
NativeLibraryElement
库文件元素,每个 native lib
对应一个 NativeLibraryElement
,可能会包含系统库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static class NativeLibraryElement { private final File path; private final String zipDir; private ClassPathURLStreamHandler urlHandler; private boolean initialized; ... public String findNativeLibrary (String name) { maybeInit(); if (zipDir == null ) { String entryPath = new File(path, name).getPath(); if (IoUtils.canOpenReadOnly(entryPath)) { return entryPath; } } else if (urlHandler != null ) { String entryName = zipDir + '/' + name; if (urlHandler.isEntryStored(entryName)) { return path.getPath() + zipSeparator + entryName; } } return null ; } ... }
DexPathList
是对两个内部类的一个封装,表示每个对象可以包含多个可执行文件 dex, jar, apk
等;同时每个对象可以包含多个库文件等。
Elements[]
数组包含了所有的 dex, jar, apk
文件,热补丁技术通常是从这里 inject
findClass
最终通过 DexFile
从虚拟机中加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 final class DexPathList { ... private final ClassLoader definingContext; private Element[] dexElements; private final NativeLibraryElement[] nativeLibraryPathElements; ... public DexPathList (ClassLoader definingContext, ByteBuffer[] dexFiles) { ... this .definingContext = definingContext; ... this .nativeLibraryPathElements = makePathElements(this .systemNativeLibraryDirectories); ... this .dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions); ... } public DexPathList (ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) { ... this .definingContext = definingContext; ... this .dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext); ... List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); this .nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories); ... } private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles, List<IOException> suppressedExceptions) { Element[] elements = new Element[dexFiles.length]; int elementPos = 0 ; for (ByteBuffer buf : dexFiles) { try { DexFile dex = new DexFile(buf); elements[elementPos++] = new Element(dex); } catch (IOException suppressed) { ... } } if (elementPos != elements.length) { elements = Arrays.copyOf(elements, elementPos); } return elements; } private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader) { Element[] elements = new Element[files.size()]; int elementsPos = 0 ; for (File file : files) { if (file.isDirectory()) { ... elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { try { DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null ) { elements[elementsPos++] = new Element(dex, null ); } } catch (IOException suppressed) { ... } } else { DexFile dex = null ; try { dex = loadDexFile(file, optimizedDirectory, loader, elements); } catch (IOException suppressed) { ... } if (dex == null ) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } else { ... } } ... return elements; } private static DexFile loadDexFile (File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException { if (optimizedDirectory == null ) { return new DexFile(file, loader, elements); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0 , loader, elements); } } private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) { return makeDexElements(files, optimizedDirectory, suppressedExceptions, null ); } private static NativeLibraryElement[] makePathElements( List<File> files) { NativeLibraryElement[] elements = new NativeLibraryElement[files.size()]; int elementsPos = 0 ; for (File file : files) { String path = file.getPath(); if (path.contains(zipSeparator)) { String split[] = path.split(zipSeparator, 2 ); File zip = new File(split[0 ]); String dir = split[1 ]; elements[elementsPos++] = new NativeLibraryElement(zip, dir); } else if (file.isDirectory()) { elements[elementsPos++] = new NativeLibraryElement(file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; } 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 ; } ... }
Android
预加载系统开机启动会在 ZygoteInit
进程创建 Java
环境,预加载系统常用类,并创建系统的类加载器 PathClassLoader
。
启动路径和系统服务路径 启动路径 BOOTCLASSPATH
和系统服务路径 SYSTEMSERVERCLASSPATH
最终都是写入 ./root/init.environ.rc
文件的。
BOOTCLASSPATH
编译系统中 PRODUCT_BOOT_JARS
中添加的 jar
包名和路径对应生成的,包含所有 framework
相关 jar
包路径。PRODUCT_BOOT_JARS
最终被编译添加到 BOOTCLASSPATH
变量中,组建过程: 1 2 3 4 5 ./device/qcom/common/base.mk ./build/make/core/envsetup.mk ./build/make/target/product/core_minimal.mk ./device/qcom/common/common.mk ./device/qcom/custom/custom.mk
SYSTEMSERVERCLASSPATH
在 build/make/target/product/core_minimal.mk
文件中定义的 PRODUCT_SYSTEM_SERVER_JARS
变量,会在 system/core/rootdir/Android.mk
中生成 SYSTEMSERVERCLASSPATH
变量。
./root/init.environ.rc
文件中这两个变量的内容为:
1 2 export BOOTCLASSPATH /system/framework/com.qualcomm.qti.camera.jar:/system/framework/QPerformance.jar:/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/bouncycastle.jar:/system/framework/apache-xml.jar:/system/framework/legacy-test.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/android.hidl.base-V1.0-java.jar:/system/framework/android.hidl.manager-V1.0-java.jar:/system/framework/qcrilhook.jar:/system/framework/hiqmi.jar:/system/framework/qcnvitems.jar:/system/framework/telephony-qmi.jar:/system/framework/tcmiface.jar:/system/framework/WfdCommon.jar:/system/framework/oem-services.jar:/system/framework/qcom.fmradio.jar:/system/framework/telephony-ext.jar export SYSTEMSERVERCLASSPATH /system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/com.android.location.provider.jar
预加载类 preloadClasses
预加载的类大概有 4500+ 个系统常用类,采用的是空间换时间的策略:系统开机时就将常用类加载,后续应用使用时不用重复加载,提高应用运行速度。 设置预加载的文件为 preloaded-classes
: AOSP
源码路径为:preloaded-classes: frameworks/base/config/preloaded-classes
编译完后会被拷贝到:./system/etc/preloaded-classes
;文件中指定需要加载的常见系统类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 android.app.Activity$HostCallbacks android.app.ActivityManager android.app.ActivityManager$1 android.app.ActivityManager$AppTask ... android.app.ActivityManager$TaskDescription$1 android.app.ActivityOptions android.app.ActivityThread android.app.ActivityThread$1 ... android.app.ContentProviderHolder$1 android.app.ContextImpl android.app.ContextImpl$1 ... android.app.DexLoadReporter android.app.Dialog android.app.Dialog$ListenersHandler android.app.DialogFragment android.app.DownloadManager android.app.Fragment android.app.Fragment$1 ...
预加载类流程图:
创建系统类加载器 Android
默认的系统类加载器为 PathClassLoader
,也是在系统启动阶段 ZygoteInit
中创建的,并加载系统服务的 jar
。 创建系统类加载器 PathClassLoader
,并加载系统服务 jar
文件流程图:
ZygoteInit
处理系统服务进程中,除了生成系统类加载器 PathClassLoader
,并会通过该加载器反射调用 SystemServer.main
方法,启动系统服务进程 system_server
进程,管理整个系统的所有服务。
APK
及四大组件加载过程APK
加载过程LoadedApk
类是整个应用 APK
加载的入口类,最终在类加载器工厂中 ClassLoaderFactory.java
创建具体的加载器 PathClassLoader
。
1 2 3 4 5 6 7 8 9 10 11 public static ClassLoader createClassLoader (String dexPath, String librarySearchPath, ClassLoader parent, String classloaderName) { if (isPathClassLoaderName(classloaderName)) { return new PathClassLoader(dexPath, librarySearchPath, parent); } else if (isDelegateLastClassLoaderName(classloaderName)) { return new DelegateLastClassLoader(dexPath,librarySearchPath,parent); } throw new AssertionError("Invalid classLoaderName: " + classloaderName); }
调用序列图:
部分 Log
打印:
ZygoteInit
新建应用进程
ActivityThread
线程启动进入 main
入口:AMS.attachApplication
启动应用以及进入 Looper.loop()
循环接受主线程消息
AMS.attachApplication
会调用 ActivityThread.bindApplication
启动并绑定该应用
ContextImpl
第一次加载应用时,调用 PathClassLoader
加载整个应用 APK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 *: V/ActivityManager(1696 ): New app record ProcessRecord{5515589 3528 :com.*.knowledge/u0a76} thread=android.os.BinderProxy@ca7598e pid=3528 , processName = com.*.knowledge, appInfo = ApplicationInfo{8 c27ebf com.*.knowledge} *: V/ActivityManager(1696 ): java.lang.RuntimeException *: V/ActivityManager(1696 ): at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:7258 ) *: V/ActivityManager(1696 ): at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:7377 ) *: V/ActivityManager(1696 ): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:291 ) *: V/ActivityManager(1696 ): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971 ) *: V/ActivityManager(1696 ): at android.os.Binder.execTransact(Binder.java:697 ) *: V/ActivityThread(3528 ): bindApplication: processName = com.*.knowledge, instrumentationName = null , appInfo = ApplicationInfo{159d 166 com.*.knowledge} *: V/ActivityThread(3528 ): java.lang.RuntimeException *: V/ActivityThread(3528 ): at android.app.ActivityThread$ApplicationThread.bindApplication(ActivityThread.java:919 ) *: V/ActivityThread(3528 ): at android.app.IApplicationThread$Stub.onTransact(IApplicationThread.java:374 ) *: V/ActivityThread(3528 ): at android.os.Binder.execTransact(Binder.java:697 ) ... *: W/ActivityManager(1696 ): Slow operation: 133 ms so far, now at attachApplicationLocked: after mServices.attachApplicationLocked *: W/Looper(1696 ): Dispatch took 135 ms on android.ui, h=Handler (com.android.server.am.ActivityManagerService$UiHandler) {782d 25f} cb=null msg=53 *: V/ActivityThread(3528 ): Class path: /data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk, JNI path: /data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64 *: E/System(3528 ): XMT, sourceName = /data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk, outputName = null , classloader = dalvik.system.PathClassLoader[null ] *: E/System(3528 ): java.lang.Exception *: E/System(3528 ): at dalvik.system.DexFile.openDexFile(DexFile.java:354 ) *: E/System(3528 ): at dalvik.system.DexFile.<init>(DexFile.java:100 ) *: E/System(3528 ): at dalvik.system.DexFile.<init>(DexFile.java:74 ) *: E/System(3528 ): at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374 ) *: E/System(3528 ): at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337 ) *: E/System(3528 ): at dalvik.system.DexPathList.<init>(DexPathList.java:157 ) *: E/System(3528 ): at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65 ) *: E/System(3528 ): at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64 ) *: E/System(3528 ): at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:73 ) *: E/System(3528 ): at com.android.internal.os.ClassLoaderFactory.createClassLoader(ClassLoaderFactory.java:88 ) *: E/System(3528 ): at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:69 ) *: E/System(3528 ): at android.app.ApplicationLoaders.getClassLoader(ApplicationLoaders.java:35 ) *: E/System(3528 ): at android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:693 ) *: E/System(3528 ): at android.app.LoadedApk.getClassLoader(LoadedApk.java:727 ) *: E/System(3528 ): at android.app.LoadedApk.getResources(LoadedApk.java:954 ) *: E/System(3528 ): at android.app.ContextImpl.createAppContext(ContextImpl.java:2270 ) *: E/System(3528 ): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5658 ) *: E/System(3528 ): at android.app.ActivityThread.-wrap1(Unknown Source:0 ) *: E/System(3528 ): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1663 ) *: E/System(3528 ): at android.os.Handler.dispatchMessage(Handler.java:106 ) *: E/System(3528 ): at android.os.Looper.loop(Looper.java:164 ) *: E/System(3528 ): at android.app.ActivityThread.main(ActivityThread.java:6522 ) *: E/System(3528 ): at java.lang.reflect.Method.invoke(Native Method) *: E/System(3528 ): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438 ) *: E/System(3528 ): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807 )
Application
类加载过程在 ActivityThread.handleBindApplication
除了加载 APK
外,还加载了 Application
类并启动这个应用;最终调用 Instrumentation.newApplication
来加载 Application
类的:
1 2 3 4 5 6 7 public Application newApplication (ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return newApplication(cl.loadClass(className), context); }
调用序列图:
部分 Log
打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Application 类加载过程 *: E/System(23352): XMT, ClassLoader.loadClass, name = com.*.knowledge.KnowledgeApplication, resolve = false *: E/System(23352): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.KnowledgeApplication, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64, /system/lib64, /vendor/lib64]]] *: E/System(23352): java.lang.Exception *: E/System(23352): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735) *: E/System(23352): at java.lang.ClassLoader.loadClass(ClassLoader.java:364) *: E/System(23352): at java.lang.ClassLoader.loadClass(ClassLoader.java:312) *: E/System(23352): at android.app.Instrumentation.newApplication(Instrumentation.java:1088) *: E/System(23352): at android.app.LoadedApk.makeApplication(LoadedApk.java:983) *: E/System(23352): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5734) *: E/System(23352): at android.app.ActivityThread.-wrap1(Unknown Source:0) *: E/System(23352): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1663) *: E/System(23352): at android.os.Handler.dispatchMessage(Handler.java:106) *: E/System(23352): at android.os.Looper.loop(Looper.java:164) *: E/System(23352): at android.app.ActivityThread.main(ActivityThread.java:6522) *: E/System(23352): at java.lang.reflect.Method.invoke(Native Method) *: E/System(23352): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) *: E/System(23352): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) *: E/System(23352): XMT, ClassLoader.loadClass, c = class com.*.knowledge.KnowledgeApplication, parent = java.lang.BootClassLoader@b40a08c
Activity
类加载过程不管是在启动应用时启动主 Activity
,还是当前 Activity
打开另一个 Activity
时,最终都是通过 ActivityStackSupervisor.realStartActivityLocked
来调用 Activity.scheduleLaunchActivity
来启动指定 Activity
;而每个 Activity
是在 Instrumentation.newActivity
中来加载的:
1 2 3 4 5 6 7 public Activity newActivity (ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (Activity)cl.loadClass(className).newInstance(); }
调用序列图:
部分 Log
打印:
上半部分为启动主 Activity
下半部分为打开另一个 Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // 1. 启动主 Activity *: V/ActivityManager(1508): realStartActivityLocked, r = ActivityRecord{4d4a239 u0 com.*.knowledge/.MainActivity t57} *: V/ActivityManager(1508): java.lang.RuntimeException *: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:1466) *: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.attachApplicationLocked(ActivityStackSupervisor.java:983) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:7310) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:7377) *: V/ActivityManager(1508): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:291) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971) *: V/ActivityManager(1508): at android.os.Binder.execTransact(Binder.java:697) ... // Activity 类加载过程 *: V/ActivityThread(4162): scheduleLaunchActivity, info = ActivityInfo{a9454ac com.*.knowledge.MainActivity} *: D/:XMT:KnowApplication:(4162): onCreate: *: E/System(4162): XMT, ClassLoader.loadClass, name = com.*.knowledge.MainActivity, resolve = false *: E/System(4162): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.MainActivity, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64, /system/lib64, /vendor/lib64]]] *: E/System(4162): java.lang.Exception *: E/System(4162): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735) *: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:364) *: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:312) *: E/System(4162): at android.app.Instrumentation.newActivity(Instrumentation.java:1175) *: E/System(4162): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2678) *: E/System(4162): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2865) *: E/System(4162): at android.app.ActivityThread.-wrap11(Unknown Source:0) *: E/System(4162): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1598) *: E/System(4162): at android.os.Handler.dispatchMessage(Handler.java:106) *: E/System(4162): at android.os.Looper.loop(Looper.java:164) *: E/System(4162): at android.app.ActivityThread.main(ActivityThread.java:6524) *: E/System(4162): at java.lang.reflect.Method.invoke(Native Method) *: E/System(4162): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) *: E/System(4162): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) *: E/System(4162): XMT, ClassLoader.loadClass, c = class com.*.knowledge.MainActivity, parent = java.lang.BootClassLoader@11b61a ... // 2. 应用已经启动后,打开其他 Activity 流程 *: V/ActivityManager(1508): realStartActivityLocked, r = ActivityRecord{6a96a51 u0 com.*.knowledge/.fourcomponents.ShowService t57} *: V/ActivityManager(1508): java.lang.RuntimeException *: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:1466) *: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.startSpecificActivityLocked(ActivityStackSupervisor.java:1589) *: V/ActivityManager(1508): at com.android.server.am.ActivityStack.resumeTopActivityInnerLocked(ActivityStack.java:2752) *: V/ActivityManager(1508): at com.android.server.am.ActivityStack.resumeTopActivityUncheckedLocked(ActivityStack.java:2265) *: V/ActivityManager(1508): at com.android.server.am.ActivityStackSupervisor.resumeFocusedStackTopActivityLocked(ActivityStackSupervisor.java:2103) *: V/ActivityManager(1508): at com.android.server.am.ActivityStack.completePauseLocked(ActivityStack.java:1496) *: V/ActivityManager(1508): at com.android.server.am.ActivityStack.activityPausedLocked(ActivityStack.java:1423) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.activityPaused(ActivityManagerService.java:7634) *: V/ActivityManager(1508): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:317) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971) *: V/ActivityManager(1508): at android.os.Binder.execTransact(Binder.java:697) *: V/ActivityThread(4162): scheduleLaunchActivity, info = ActivityInfo{59de479 com.*.knowledge.fourcomponents.ShowService}
Service
类加载过程不管 Service
是否设置新进程,也不管是 startService
还是 bindService
,系统都是在 ActivityThread.handleCreateService
中加载 Service
类的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void handleCreateService (CreateServiceData data) { ... LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null ; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { ... } ... }
bindService
的调用序列图:
部分 Log
打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // 1. ComtextImpl.bindService 开始进入 AMS *: V/ActivityManager(1508): requestServiceBindingLocked *: V/ActivityManager(1508): java.lang.RuntimeException *: V/ActivityManager(1508): at com.android.server.am.ActiveServices.requestServiceBindingLocked(ActiveServices.java:1856) *: V/ActivityManager(1508): at com.android.server.am.ActiveServices.requestServiceBindingsLocked(ActiveServices.java:2260) *: V/ActivityManager(1508): at com.android.server.am.ActiveServices.realStartServiceLocked(ActiveServices.java:2335) *: V/ActivityManager(1508): at com.android.server.am.ActiveServices.bringUpServiceLocked(ActiveServices.java:2187) *: V/ActivityManager(1508): at com.android.server.am.ActiveServices.bindServiceLocked(ActiveServices.java:1442) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.bindService(ActivityManagerService.java:18625) *: V/ActivityManager(1508): at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:584) *: V/ActivityManager(1508): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2971) *: V/ActivityManager(1508): at android.os.Binder.execTransact(Binder.java:697) // 2. 调用 ActivityThread.scheduleBindService *: V/ActivityThread(4162): scheduleBindService *: V/ActivityThread(4162): java.lang.RuntimeException *: V/ActivityThread(4162): at android.app.ActivityThread$ApplicationThread.scheduleBindService(ActivityThread.java:863) *: V/ActivityThread(4162): at android.app.IApplicationThread$Stub.onTransact(IApplicationThread.java:439) *: V/ActivityThread(4162): at android.os.Binder.execTransact(Binder.java:697) // 3. Service 类加载过程 *: E/System(4162): XMT, ClassLoader.loadClass, name = com.*.knowledge.fourcomponents.BindService, resolve = false *: E/System(4162): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.fourcomponents.BindService, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-kLwif9bg7jNq6haatwl4ZQ==/lib/arm64, /system/lib64, /vendor/lib64]]] *: E/System(4162): java.lang.Exception *: E/System(4162): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735) *: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:364) *: E/System(4162): at java.lang.ClassLoader.loadClass(ClassLoader.java:312) *: E/System(4162): at android.app.ActivityThread.handleCreateService(ActivityThread.java:3330) *: E/System(4162): at android.app.ActivityThread.-wrap4(Unknown Source:0) *: E/System(4162): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1686) *: E/System(4162): at android.os.Handler.dispatchMessage(Handler.java:106) *: E/System(4162): at android.os.Looper.loop(Looper.java:164) *: E/System(4162): at android.app.ActivityThread.main(ActivityThread.java:6524) *: E/System(4162): at java.lang.reflect.Method.invoke(Native Method) *: E/System(4162): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) *: E/System(4162): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) *: E/System(4162): XMT, ClassLoader.loadClass, c = class com.*.knowledge.fourcomponents.BindService, parent = java.lang.BootClassLoader@11b61a *: D/:XMT:BindService:(4162): onCreate: *: D/:XMT:BindService:(4162): onBind:
静态注册 BroadcastReceiver
类加载过程 静态注册的广播接收器是在 ActivityThread.handleReceiver
中响应广播事件,并加载对应的 BroadcastReceiver
类的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private void handleReceiver (ReceiverData data) { ... String component = data.intent.getComponent().getClassName(); LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); Application app; BroadcastReceiver receiver; ContextImpl context; try { app = packageInfo.makeApplication(false , mInstrumentation); context = (ContextImpl) app.getBaseContext(); if (data.info.splitName != null ) { context = (ContextImpl) context.createContextForSplit( data.info.splitName); } java.lang.ClassLoader cl = context.getClassLoader(); data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); receiver=(BroadcastReceiver)cl.loadClass(component).newInstance(); } catch (Exception e) { ... } ... }
完整版本可以参考 四大组件 – Broadcast ,简化后的调用序列图:
部分 Log
打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 06-23 10:47:50.276: E/System(19446): XMT, ClassLoader.loadClass, name = com.staqu.essentials.notifications.NotificationActivationReceiver, resolve = false 06-23 10:47:50.276: E/System(19446): XMT, ClassLoader.findLoadedClass, name = com.staqu.essentials.notifications.NotificationActivationReceiver, this = dalvik.system.PathClassLoader[DexPathList[[zip file "/system/priv-app/SalesTracker/SalesTracker.apk"],nativeLibraryDirectories=[/system/priv-app/SalesTracker/lib/arm, /system/fake-libs, /system/priv-app/SalesTracker/SalesTracker.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib, /system/lib, /vendor/lib]]] 06-23 10:47:50.277: E/System(19446): java.lang.Exception 06-23 10:47:50.277: E/System(19446): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735) 06-23 10:47:50.277: E/System(19446): at java.lang.ClassLoader.loadClass(ClassLoader.java:364) 06-23 10:47:50.277: E/System(19446): at java.lang.ClassLoader.loadClass(ClassLoader.java:312) 06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread.handleReceiver(ActivityThread.java:3173) 06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread.-wrap17(Unknown Source:0) 06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1679) 06-23 10:47:50.277: E/System(19446): at android.os.Handler.dispatchMessage(Handler.java:106) 06-23 10:47:50.277: E/System(19446): at android.os.Looper.loop(Looper.java:164) 06-23 10:47:50.277: E/System(19446): at android.app.ActivityThread.main(ActivityThread.java:6522) 06-23 10:47:50.277: E/System(19446): at java.lang.reflect.Method.invoke(Native Method) 06-23 10:47:50.277: E/System(19446): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 06-23 10:47:50.277: E/System(19446): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 06-23 10:47:50.277: E/System(19446): XMT, ClassLoader.loadClass, c = class com.staqu.essentials.notifications.NotificationActivationReceiver, parent = java.lang.BootClassLoader@b40a08c
Android
动态类加载示例:动态加载 assets
目录下的 dex
文件,并反射调用指定类中的某个方法,返回值在当前 Activity
中显示。
生成 dex
文件 在 AS
中新建 Test.java
文件,编译生成对应的 Test.class
,使用工具生成对应的 test.jar
文件:d8.bat --output=test.jar Test.class
。 将 Test.java, Test.class, test.jar
三个文件剪切到 assets
目录下,避免将 Test.java
打包到当前 APK
中了。
1 2 3 4 5 6 7 8 9 package com.*.knowledge.classloading;public class Test { public String test () { return "Test: from other dex file, classLoader: " + Test.class.getClassLoader(); } }
当前 Activity
动态加载
使用异步任务 AsyncTask
实现动态加载
避免内存泄露,定义静态内部类 LoaderAsyncTask
,并使用弱引用指向当前 Activity
将 assets
目录下的 dex
文件,拷贝到 cache
目录下,方便动态加载
使用 DexClassLoader
动态加载 cache
目录下的 dex
文件,指定父加载器为 null
使用反射调用 test
方法,并在当前 Activity
中显示结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com.*.knowledge.classloading;... public class ClassLoadingActivity extends AppCompatActivity { ... private static class LoaderAsyncTask extends AsyncTask <String , Void , String > { private WeakReference<ClassLoadingActivity> mActivity; public LoaderAsyncTask (ClassLoadingActivity activity) { mActivity = new WeakReference<ClassLoadingActivity>(activity); } @Override protected void onPreExecute () { super .onPreExecute(); mActivity.get().mTvResult.setText("..." ); } @Override protected String doInBackground (String... strings) { ... String destFilePath = mActivity.get().getCacheDir().getAbsolutePath() + File.separator + strings[0 ]; File destFile = new File(destFilePath); if (!destFile.exists()) { copyFile(mActivity.get().getApplicationContext(), strings[0 ], destFile); } ... Log.d(TAG, "doInBackground: destFilePath = " + destFilePath); try { DexClassLoader dcl = new DexClassLoader(destFilePath, null , null , null ); Class<?> clazz = dcl.loadClass("com.*.knowledge.classloading.Test" ); Object object = clazz.newInstance(); Method testMethod = clazz.getMethod("test" ); Object result = testMethod.invoke(object, null ); Log.d(TAG, "doInBackground: result=" + result.toString()); return result.toString(); } catch (...){...} ... } } private static void copyFile (Context context, String fileName, File destFile) { InputStream in=null ; OutputStream out=null ; try { context = context.getApplicationContext(); in=context.getAssets().open(fileName); out=new FileOutputStream(destFile.getAbsolutePath()); byte [] bytes=new byte [1024 ]; int len=0 ; while ((len=in.read(bytes))!=-1 ) out.write(bytes,0 ,len); out.flush(); } catch (IOException e) { e.printStackTrace(); }finally { try { if (in!=null ) in.close(); if (out!=null ) out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
这里需要注意下:DexClassLoader
动态加载时,指定父加载器为 null
;依据双亲委派模型,只有父加载器加载失败时,才会使用当前加载器加载。 这里假定: Test.java
在当前 APK
存在,并且父加载器使用的是 PathClassLoader
。依据双亲委派模型会优先使用 PathClassLoader
加载 Test
类,而我们指定的 DexClassLoader
并不能动态加载 assets
目录下的 Test
。
示例 Log
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 *: I/mmid(518): select timeout: wait for receiving msg *: D/XMT:ClassLoadingAct(16046): doInBackground: // 1. 将 assets 目录下的 dex 文件,拷贝到 cache 目录下 *: D/XMT:ClassLoadingAct(16046): copyFile: fileName = test.jar *: D/XMT:ClassLoadingAct(16046): doInBackground: destFilePath = /data/user/0/com.*.knowledge/cache/test.jar // 2. 动态加载 dex 文件 *: E/System(16046): XMT, openDexFile, sourceName = /data/user/0/com.*.knowledge/cache/test.jar, outputName = null, flags = 0, classloader = dalvik.system.DexClassLoader[null] *: E/System(16046): java.lang.Exception *: E/System(16046): at dalvik.system.DexFile.openDexFile(DexFile.java:356) *: E/System(16046): at dalvik.system.DexFile.<init>(DexFile.java:100) *: E/System(16046): at dalvik.system.DexFile.<init>(DexFile.java:74) *: E/System(16046): at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374) *: E/System(16046): at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337) *: E/System(16046): at dalvik.system.DexPathList.<init>(DexPathList.java:157) *: E/System(16046): at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65) *: E/System(16046): at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:54) *: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:85) *: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:53) *: E/System(16046): at android.os.AsyncTask$2.call(AsyncTask.java:333) *: E/System(16046): at java.util.concurrent.FutureTask.run(FutureTask.java:266) *: E/System(16046): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) *: E/System(16046): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) *: E/System(16046): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) *: E/System(16046): at java.lang.Thread.run(Thread.java:764) *: E/System(16046): XMT, element: null *: I/dex2oat(16078): The ClassLoaderContext is a special shared library. *: I/dex2oat(16078): /system/bin/dex2oat --debuggable --debuggable --dex-file=/data/data/com.*.knowledge/cache/test.jar --output-vdex-fd=64 --oat-fd=65 --oat-location=/data/data/com.*.knowledge/cache/oat/arm64/test.odex --compiler-filter=quicken --class-loader-context=& *: I/InputReader(1509): Reconfiguring input devices. changes=0x00000010 *: I/Telecom(1509): DefaultDialerCache: Refreshing default dialer for user 0: now com.android.dialer: DDC.oR@AGc *: W/VoiceInteractionManagerService(1509): no available voice interaction services found for user 0 *: D/CarrierSvcBindHelper(2041): No carrier app for: 0 *: D/CarrierSvcBindHelper(2041): No carrier app for: 1 *: D/ImsResolver(2041): maybeAddedImsService, packageName: com.*.knowledge *: I/dex2oat(16078): dex2oat took 934.000ms (1.609s cpu) (threads: 1000) arena alloc=1432B (1432B) java alloc=16KB (16400B) native alloc=492KB (504744B) free=2MB (2116696B) *: I/zygote64(16046): The ClassLoaderContext is a special shared library. // 3. 加载指定类 Test *: E/System(16046): XMT, ClassLoader.loadClass, name = com.*.knowledge.classloading.Test, resolve = false *: E/System(16046): XMT, ClassLoader.findLoadedClass, name = com.*.knowledge.classloading.Test, this = dalvik.system.DexClassLoader[DexPathList[[zip file "/data/user/0/com.*.knowledge/cache/test.jar"],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]] *: E/System(16046): java.lang.Exception *: E/System(16046): at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:735) *: E/System(16046): at java.lang.ClassLoader.loadClass(ClassLoader.java:364) *: E/System(16046): at java.lang.ClassLoader.loadClass(ClassLoader.java:312) *: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:86) *: E/System(16046): at com.*.knowledge.classloading.ClassLoadingActivity$LoaderAsyncTask.doInBackground(ClassLoadingActivity.java:53) *: E/System(16046): at android.os.AsyncTask$2.call(AsyncTask.java:333) *: E/System(16046): at java.util.concurrent.FutureTask.run(FutureTask.java:266) *: E/System(16046): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) *: E/System(16046): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) *: E/System(16046): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) *: E/System(16046): at java.lang.Thread.run(Thread.java:764) *: E/System(16046): XMT, ClassLoader.loadClass, c = class com.*.knowledge.classloading.Test, parent = dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.*.knowledge-8K_G8HZhGrHEo_X_5AJuhg==/base.apk"],nativeLibraryDirectories=[/data/app/com.*.knowledge-8K_G8HZhGrHEo_X_5AJuhg==/lib/arm64, /system/lib64, /vendor/lib64]]] // 4. 反射调用指定方法 test *: D/XMT:ClassLoadingAct(16046): doInBackground: result = Test: from other dex file, classLoader: dalvik.system.DexClassLoader[DexPathList[[zip file "/data/user/0/com.*.knowledge/cache/test.jar"],nativeLibraryDirectories=[/system/lib64, /vendor/lib64]]]
从 Log
中可以看出,Test.test
方法是 DexClassLoader
加载的,对应路径为 /data/user/0/com.*.knowledge/cache/test.jar
。
Android
热补丁在 Android
动态加载中,我们把 Test.java
从源码中删除了,也就是说 APK
中并不包含这个类,所以通过反射来访问的。接下来我们介绍 Android
热补丁,也就是 Test.java
在 APK
中存在,我们需要使用 assets
中的补丁替换它。
热补丁原理 Android
的类加载器在加载一个类时,先从当前加载器的 DexPathList
对象中的 Element[] dexElements
数组中,获取对应的类并加载。采用的是数组遍历的方式,遍历每一个 dex
文件;先遍历出来的是 dex
文件,先加载 class
,成功加载后就不再遍历后续 dex
文件。 热修复的原理就是:将补丁 class
打包成 dex
文件后,放到 Element
数组的第一个元素 ,这样就能保证获取到的 class
是最新修复好的 class
了。
参考 Java 类加载机制 ,对于 new TestClass()
这个语句,会触发类初始化过程。而初始化分为两部分:类初始化过程 <cinit>
,即类加载过程的初始化阶段;类实例化过程 <init>
。而类一旦初始化后,后续再 new
只会执行实例化过程,也就是常说的类只会被加载一次,但是会实例化多次。所以热补丁必须要在类初始化 <cinit>
之前合入,否则不会生效。
Test
类1 2 3 4 5 6 7 8 9 package com.*.knowledge.classloading;public class Test { public String test () { return "Test: from current APK, classLoader: " + Test.class.getClassLoader(); } }
这个 Test.java
跟随其他代码一起编译到 APK
中,而我们 assets
中的 Test.java
的方法中,返回值不一样 return "Test: from other dex file, classLoader: " ...
,后续通过 Log
和界面显示出来。 如果最终热加载成功,将显示 ... from other dex ...
而不是 ... from current APK ...
。
当前 Activity
合入热补丁 因为 DexPathList, Element
等都是包内可见,所以只能通过反射将补丁包插入到 Element[]
数组的第一个元素中。
获取系统加载器 PathClassLoader, DexPathList, Elements
获取补丁包中的 DexClassLoader, DexPathList, Elements
将补丁包的 Elements
插入到当前系统加载器的 Elements
中
后续加载修复类时,会优先从补丁包中获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 public class HotFixActivity extends AppCompatActivity { ... private String getTestResult () { Test test = new Test(); String result = test.test(); return result; } private void handleHotFix () { HotFixAsyncTask hotFixAsyncTask = new HotFixAsyncTask(this ); hotFixAsyncTask.execute(); } private static class HotFixAsyncTask extends AsyncTask <String , Void , String > { private WeakReference<HotFixActivity> mActivity; private String mDexFilePath; public HotFixAsyncTask (HotFixActivity activity) { mActivity = new WeakReference<HotFixActivity>(activity); } @Override protected String doInBackground (String... strings) { if (isDexFileExist()) { return mActivity.get().getTestResult(); } return "Hot Fix Error!" ; } @Override protected void onPostExecute (String s) { super .onPostExecute(s); mActivity.get().mBtnHotFix.setEnabled(true ); mActivity.get().mTvNew.setText(s); } private boolean isDexFileExist () { String dexFileName = "test.jar" ; mDexFilePath = mActivity.get().getCacheDir().getAbsolutePath() + File.separator + dexFileName; File destFile = new File(mDexFilePath); if (!destFile.exists()) { copyFile(mActivity.get().getApplicationContext(), dexFileName, destFile); } if (!destFile.exists()){ Log.d(TAG, "isDexFileExist: copy error!" ); return false ; } return true ; } private void hotFix () { Log.d(TAG, "hotFix: " ); try { PathClassLoader pcl = (PathClassLoader) mActivity.get().getClassLoader(); DexClassLoader dcl = new DexClassLoader(mDexFilePath, null , null , pcl); Object origDexPathList = getPathList(pcl); Object origElements = getDexElements(origDexPathList); Object patchDexPathList = getPathList(dcl); Object patchElements = getDexElements(patchDexPathList); Object patchedElements = combineArray(patchElements, origElements); setDexElements(origDexPathList, patchedElements); } catch (...) } private static void setField (Object object, Class<?> clazz, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } private static Object getField (Object object, Class<?> clazz, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); return field.get(object); } private Object getPathList (ClassLoader cl) throws NoSuchFieldException, IllegalAccessException { return getField(cl, cl.getClass().getSuperclass(), "pathList" ); } private Object getDexElements (Object dexPathList) throws NoSuchFieldException, IllegalAccessException { Log.d(TAG, "getDexElements: " ); return getField(dexPathList, dexPathList.getClass(), "dexElements" ); } private void setDexElements (Object dexPathList, Object elements) throws NoSuchFieldException, IllegalAccessException { Log.d(TAG, "setDexElements: " ); setField(dexPathList, dexPathList.getClass(), "dexElements" , elements); } private static Object combineArray (Object array1, Object array2) { Log.d(TAG, "combineArray: " ); if (array1 == null || !array1.getClass().isArray() || array2 == null || !array2.getClass().isArray()) { Log.d(TAG, "combineArray: array is null." ); return null ; } Class<?> clazz = array1.getClass().getComponentType(); int length1 = Array.getLength(array1); int length2 = Array.getLength(array2); int length = length1 + length2; Object combineArray = Array.newInstance(clazz, length); System.arraycopy(array1, 0 , combineArray, 0 , length1); System.arraycopy(array2, 0 , combineArray, length1, length2); return combineArray; } ... } }
示例 Log
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 * I/mmid: select timeout: wait for receiving msg */com.*.knowledge D/XMT:HotFixAct: hotFix: */com.*.knowledge E/System: XMT, openDexFile, sourceName = /data/user/0 /com.*.knowledge/cache/test.jar, outputName = null , flags = 0 , classloader = dalvik.system.DexClassLoader[null ] */com.*.knowledge E/System: java.lang.Exception at dalvik.system.DexFile.openDexFile(DexFile.java:356 ) at dalvik.system.DexFile.<init>(DexFile.java:100 ) at dalvik.system.DexFile.<init>(DexFile.java:74 ) at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374 ) at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337 ) at dalvik.system.DexPathList.<init>(DexPathList.java:157 ) at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65 ) at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:54 ) at com.*.knowledge.classloading.HotFixActivity$HotFixAsyncTask.hotFix(HotFixActivity.java:108 ) at com.*.knowledge.classloading.HotFixActivity$HotFixAsyncTask.doInBackground(HotFixActivity.java:76 ) at com.*.knowledge.classloading.HotFixActivity$HotFixAsyncTask.doInBackground(HotFixActivity.java:60 ) at android.os.AsyncTask$2. call(AsyncTask.java:333 ) at java.util.concurrent.FutureTask.run(FutureTask.java:266 ) at android.os.AsyncTask$SerialExecutor$1. run(AsyncTask.java:245 ) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162 ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636 ) at java.lang.Thread.run(Thread.java:764 ) * /com.*.knowledge E/System: XMT, element: null * /com.*.knowledge I/zygote64: The ClassLoaderContext is a special shared library. * /com.*.knowledge D/XMT:HotFixAct: getPathList: * /com.*.knowledge D/XMT:HotFixAct: getDexElements: * /com.*.knowledge D/XMT:HotFixAct: getPathList: * /com.*.knowledge D/XMT:HotFixAct: getDexElements: * /com.*.knowledge D/XMT:HotFixAct: combineArray: * /com.*.knowledge D/XMT:HotFixAct: setDexElements: * /com.*.knowledge D/XMT:HotFixAct: getTestResult: result = Test: from other dex file, classLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/user/0/com.*.knowledge/cache/test.jar" , zip file "/data/app/com.*.knowledge-0dRhS8t5SzW66qvx7ecm8w==/base.apk" ],nativeLibraryDirectories=[/data/app/com.*.knowledge-0d RhS8t5SzW66qvx7ecm8w==/lib/arm64, /system/lib64, /vendor/lib64]]]
从结果可以看出 Test
类是从补丁包中加载的:
类加载器为系统加载器 PathClassLoader
,也就是它的 Elements
数组已经通过反射合入了热补丁
DexPathList
中包含两个文件:补丁包 /data/user/0/com.*.knowledge/cache/test.jar
和原始的 APK: /data/app/com.*.knowledge-0dRhS8t5SzW66qvx7ecm8w==/base.apk
其他 dalvikvm
类似 PC
端的 java
工具,用来指执行 apk,dex,jar
等 Android
可执行文件的,常用参数为:
-cp
:指定可执行文件绝对路径
-verbose:class
:在 logcat
中输出类加载过程
1 2 3 4 5 6 7 8 9 10 11 xmt@server005:~/$ dalvikvm --help Unknown argument: --help dalvikvm: [options] class [argument ...] The following standard options are supported: -classpath classpath (-cp classpath) -Dproperty=value -verbose:tag ('gc', 'jit', 'jni', or 'class') -showversion -help ...
下面是一个调试过程,可以看到 Android
类加载过程
java
文件编写 1 2 3 4 5 6 public class DalvikvmTest { public static void main (String[] args) { System.out.println("This is DalvikvmTest." ); } }
class
文件生成javac -bootclasspath C:\Users\xmt\sdk\platforms\android-28\android.jar DalvikvmTest.java
,这里可以省略 -bootclasspath
,只有在调用了 android
代码时才需要。
dex, jar
文件生成 1 C:\Users\xmt\sdk\build-tools\28.0.3\d8.bat --classpath C:\Users\xmt\sdk\platforms\android-28\android.jar --output=DalvikvmTest.jar DalvikvmTest.class
这里同样可以省略 --classpath
。
push
到手机中,dalvikvm
执行 1 2 3 adb push DalvikvmTest.jar /storage/emulated/0/test adb shell dalvikvm -verbose:class -cp /storage/emulated/0/test/DalvikvmTest.jar DalvikvmTest
对应 log
从 Log
中可以清晰的看到类的加载和类初始化两个过程,默认使用的是 PathClassLoader
类加载器。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 /? I/dalvikvm: Adding image space took 153.906us /? I/dalvikvm: Adding image space took 57.448us /? I/dalvikvm: Adding image space took 71.718us ... /? W/ADebug: Failed to get property persist.sys.media.traces /? I/dalvikvm: Initialized class Landroid/system/OsConstants; from /system/framework/core-libart.jar /? I/dalvikvm: Initialized class Ljava/io/FileDescriptor; from /system/framework/core-oj.jar /? I/dalvikvm: Initialized class Ljava/net/Inet4Address; from /system/framework/core-oj.jar /? I/dalvikvm: Initialized class Ljava/lang/System; from /system/framework/core-oj.jar /? I/dalvikvm: Initialized class Ljava/net/Inet6Address; from /system/framework/core-oj.jar /? I/dalvikvm: Initialized class Ljava/io/UnixFileSystem; from /system/framework/core-oj.jar /? I/dalvikvm: Initialized class Ljava/io/File; from /system/framework/core-oj.jar /? I/dalvikvm: Initialized class Ljava/util/regex/Pattern; from /system/framework/core-oj.jar /? I/dalvikvm: Loaded class [Ljava/lang/ThreadLocal$ThreadLocalMap$Entry; /? E/System: XMT, openDexFile, sourceName = /storage/emulated/0/test/DalvikvmTest.jar, outputName = null, flags = 0, classloader = dalvik.system.PathClassLoader[null] /? E/System: java.lang.Exception at dalvik.system.DexFile.openDexFile(DexFile.java:356) at dalvik.system.DexFile.<init>(DexFile.java:100) at dalvik.system.DexFile.<init>(DexFile.java:74) at dalvik.system.DexPathList.loadDexFile(DexPathList.java:374) at dalvik.system.DexPathList.makeDexElements(DexPathList.java:337) at dalvik.system.DexPathList.<init>(DexPathList.java:157) at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:65) at dalvik.system.PathClassLoader.<init>(PathClassLoader.java:64) at java.lang.ClassLoader.createSystemClassLoader(ClassLoader.java:224) at java.lang.ClassLoader.-wrap0(Unknown Source:0) at java.lang.ClassLoader$SystemClassLoader.<clinit>(ClassLoader.java:183) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1110) /? E/System: XMT, element: null /? I/dalvikvm: The ClassLoaderContext is a special shared library. /? I/dalvikvm: Registering /storage/emulated/0/test/oat/arm64/DalvikvmTest.odex /? I/dalvikvm: Initialized class Ljava/lang/ClassLoader$SystemClassLoader; from /system/framework/core-oj.jar // 加载 DalvikvmTest 类 /? I/dalvikvm: Loaded class LDalvikvmTest; from /storage/emulated/0/test/DalvikvmTest.jar /? I/dalvikvm: Beginning verification for class: DalvikvmTest in /storage/emulated/0/test/DalvikvmTest.jar /? I/dalvikvm: Class preverified status for class DalvikvmTest in /storage/emulated/0/test/DalvikvmTest.jar: 1 // DalvikvmTest 类初始化 /? I/dalvikvm: Initialized class LDalvikvmTest; from /storage/emulated/0/test/DalvikvmTest.jar /? I/dalvikvm: Initialized class Llibcore/icu/NativeConverter; from /system/framework/core-libart.jar /? I/dalvikvm: Initialized class Ljava/nio/charset/StandardCharsets; from /system/framework/core-oj.jar /? I/dalvikvm: Loaded class Ljava/io/BufferedWriter; from /system/framework/core-oj.jar /? I/dalvikvm: Beginning verification for class: java.io.BufferedWriter in /system/framework/core-oj.jar /? I/dalvikvm: Class preverified status for class java.io.BufferedWriter in /system/framework/core-oj.jar: 1 /? I/dalvikvm: Initialized class Ljava/io/BufferedWriter; from /system/framework/core-oj.jar
AOSP libcore
编译在调试时,希望打出类加载堆栈中调用关系,在 DexFile.java
中增加了一个 Log
打印:System.logE("XMT, ***" , new Exception());
。修改完代码后,在 libcore
目录下编译 mm
,但是总是会出错并打印如下信息:
1 2 ninja: error: 'out/host/common/obj/JAVA_LIBRARIES/junit-hostdex_intermediates/classes.jack', needed by 'out/host/common/obj/JAVA_LIBRARIES/core-test-rules-hostdex_intermediates/classes.jack', missing and no known rule to make it 11:21:10 ninja failed with: exit status 1
根据错误信息,需要编译 core-test-rules
模块,其实我们并不需要。在 libcore/JavaLibrary.mk
中看到这个模块是宏 LIBCORE_SKIP_TESTS
控制的,我们在 libcore/Android.mk
中注释掉这个宏,export LIBCORE_SKIP_TESTS = false
,重新编译。Java
代码的改动会更新:
1 2 3 4 out/***/system/framework/core-libart.jar out/***/system/framework/boot-core-libart.vdex out/***/system/framework/boot-core-libart.oat out/***/system/framework/boot-core-libart.art
将这几个文件 push
到手机后重启生效。
小结:
打印 log
方式:使用 System.logE()
编译方式:设置 export LIBCORE_SKIP_TESTS = false
,在 libcore/
目录下 mm
常见问题 APK
加载过程应用中的四大组件和自定义类,编译工具会先将 Java
文件生成 .class
,然后合并成 .dex
文件,最终打包成 APK
。Android
启动 APP
时,会加载对应的 APK
文件,并将整个 APK
中相关类信息加载到虚拟机中,参考 APK
加载过程章节中的序列图。
Android
类加载时机APK
加载后,并不会将拥有的类全部加载,只有在类初始化时才会加载,类加载时机参考 Java 类加载机制 ,大概有 5 种情况会触发。
APK
中的四大组件是主动调用 ClassLoader
来加载,通过反射来实例化的
APK
中普通自定义类通过 new
来加载和实例化;这个过程并没有出现 ClassLoader
类加载器相关调用关系,怀疑是虚拟机 ART
中直接实现(没有看虚拟机的实现代码只是猜测)
APK
中如果遇到了 framework
中的类,会通过双亲委派模型从系统中 ClassLoader.findLoadedClass
查找已经加载的类
虚拟机 ART
中的底层代码,在加载应用中的类时,会在 Java DexPathList.Elements
数组中查找对应的 apk, dex, jar
等文件,并加载对应类(并没有搞清楚底层代码是怎么实现的,但是从热补丁的测试结果来看是这样的)
打印类加载过程 可以通过 dalvikvm -verbose:class
简单了解类加载过程。
几个重要的 native
方法
DexFile.openDexFile -> openDexFileNative
DexFile.defineClass -> defineClassNative
Class.classForName
ClassLoader.findLoadedClass -> VMClassLoader.findLoadedClass
后续
参考文档