Android 虚拟机与ClassLoader类加载笔记

1 Android虚拟机

在介绍Android的虚拟机之前,我们先来看一下JVM虚拟机之下,我们的class文件的字节码指令的Demo:

public class Demo {public static void test() {int a = 1;int b = 2;int c = a + b;}
}

将Demo.class文件使用命令:javap -c .\Demo.class  反编译一下字节码文件,得到如下的结果:

到这里,我们的Demo展示完了,随着我们接下来内容的介绍,将会使用到该字节码的内容。我们在介绍Android的虚拟机之前,先了解一下一些相关的概念。

1.1 JVM与Dalvik

Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的不是class文件,而是dex文件。

Dalvik虚拟机与Java虚拟机共享有差不多的特性,差别在于两者执行的指令集是不一样的,前者的指令集是基本寄存器的,而后者的指令集是基于堆栈的。

那什么是基于栈的虚拟机,什么又是基于寄存器的虚拟机?

基于栈的虚拟机

对于基于栈的虚拟机来说,每一个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。

我们上面的字节码文件在基于栈的虚拟机中执行将会产生多次的进出栈操作(具体参考JVM内存管理笔记-CSDN博客):

那这样就导致相对的执行效率会偏低。

基于寄存器的虚拟机

基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。

那么我们上面的Demo的test方法在基于寄存器的虚拟机中是如何执行的呢?

我们的虚拟寄存器其实可以理解为就是JVM虚拟机中“局部变量表”和“操作数栈”的结合,减少了栈帧中2个区域的读取与存入操作,没有了对应的操作,那就相对提升了执行的效率。看一下2个对比:

       与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了。到这里我们了解了什么基于栈的虚拟机什么是基于寄存器的虚拟机,他们的主要区别就是效率的提升。

我们Android中使用的虚拟机就是基于寄存器的虚拟机,以提升执行的效率。

1.2 ART与Dalvik

       Dalvik虚拟机执行的是dex字节码,解释执行。那么什么是dex字节码呢?其实你可以理解为就是多个class文件打包成了一个dex文件。从Android 2.2版本开始,支持JIT即时编译(Just In Time在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。而ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。

那么这里就有一个问题,ART执行的机器码是怎么来的呢?

       Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART 引入了预先编译机制Ahead Of Time,在安装时,ART 使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。

       从上面的图可以看出,原来Davilk虚拟机在程序安装的过程中,会将apk中的dex文件转化成OdexFile文件,再加上JIT的即时编译生成的。ART就不一样了,将对应的dex文件在APK的安装过程中直接将文件翻译成了机器码,也就是我们前面介绍的OFT(预编译机制)。

但是你也能想到ART这样搞的话肯定会将APK的安装时间延长,从而导致用户体验不是太好,感觉安装的好慢~~~,那么从Android N开始Android又将虚拟机做了一次改进:

Androd N的虚拟机运作方式

由于纯纯的使用ART虚拟机,会导致安装过程比较慢并且效率低下,所以从Android N开始使用混合方式:

ART 使用预先 (AOT) 编译,并且从 Android N混合使用AOT编译,解释和JIT。

1、最初安装应用时不进行任何 AOT 编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT经过 JIT 编译的方法将会记录到Profile配置文件中。

2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行 AOT 编译。待下次运行时直接使用。

       到这里Android中的虚拟机我们已经介绍完了,那么到此时,我们是不是疑惑我们平时写的代码逻辑都是java文件或者kt文件,那这些文件又是怎么进入到虚拟机中的呢?虚拟机又是什么时候起来的呢?那带着这几个疑问我们看一下我们的类加载机制:classloader;

2 Classloader(类加载器)

       任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。

class Class<T> {...private transient ClassLoader classLoader;...
}

首先我们看一下ClassLoader的代码结构:

ClassLoader是一个抽象类,而它的具体实现类主要有:

  • BootClassLoader

        用于加载Android Framework层class文件。

  • PathClassLoader

        用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex

  • DexClassLoader

        用于加载指定的dex,以及jar、zip、apk中的classes.dex

他们之间的关系:

ClassLoader本身就是一个抽象类:

public abstract class ClassLoader {static private class SystemClassLoader {public static ClassLoader loader = ClassLoader.createSystemClassLoader();}// 省略代码.....
}

ClassLoader的代码与JVM中的ClassLoader类是一样的,但是Android自己实现了2个重要的类加载器:java/lang/ClassLoader$BootClassLoader 和 dalvik/system/PathClassLoader。

       其中BootClassLoader是ClassLoader中的一个子类,用于加载Android Framework层的class文件,那什么是Andrid framework层的class文件呢?比如:android.app.Activity文件就是framework层的class文件,但是在build.grade文件中引入:

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

其中我们熟悉的androidx.activity.ComponentActivity就不是android framework层的代码,所以androidx.activity.ComponentActivity类就不是BootClassLoader加载器加载的。

       那我们平时自己写的一些java类(com.xxx.MainActivity)或者kt类,都是使用PathClassLoader类加载将java文件编译生成的class文件加载到虚拟机中执行的。

我们可以使用如下的方法获取出加载该类的类加载器:

import com.donnycoy.classloderdemo.R;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.e("MainActivity", "APP getClassLoader: " + getClassLoader());Log.e("MainActivity", "Activity: " + Activity.class.getClassLoader());Log.e("MainActivity", "AppCompatActivity: " + AppCompatActivity.class.getClassLoader());}
}

运行结果:

2025-02-17 15:53:21.808  5886-5886  MainActivity            com.donnycoy.classloderdemo          E  APP getClassLoader: dalvik.system.PathClassLoader[DexPathList[[dex file "/data/data/com.donnycoy.classloderdemo/code_cache/.overlay/base.apk/classes3.dex", zip file "/data/app/~~cH9o47591Txac7KbsILMrA==/com.donnycoy.classloderdemo-BrBQW_URo0xFxQ7vGd4l_w==/base.apk"],nativeLibraryDirectories=[/data/app/~~cH9o47591Txac7KbsILMrA==/com.donnycoy.classloderdemo-BrBQW_URo0xFxQ7vGd4l_w==/lib/x86, /system/lib, /system_ext/lib]]]
2025-02-17 15:53:21.808  5886-5886  MainActivity            com.donnycoy.classloderdemo          E  Activity: java.lang.BootClassLoader@8597e21
2025-02-17 15:53:21.808  5886-5886  MainActivity            com.donnycoy.classloderdemo          E  AppCompatActivity: dalvik.system.PathClassLoader[DexPathList[[dex file "/data/data/com.donnycoy.classloderdemo/code_cache/.overlay/base.apk/classes3.dex", zip file "/data/app/~~cH9o47591Txac7KbsILMrA==/com.donnycoy.classloderdemo-BrBQW_URo0xFxQ7vGd4l_w==/base.apk"],nativeLibraryDirectories=[/data/app/~~cH9o47591Txac7KbsILMrA==/com.donnycoy.classloderdemo-BrBQW_URo0xFxQ7vGd4l_w==/lib/x86, /system/lib, /system_ext/lib]]]

       执行的结果也验证了,我们APK的类加载器是PathClassLoader,而我们系统的class使用的类加载器就是BootClassLoader。

接下来我们看一下类加载的源码分析:

PathClassLoader 与 DexClassLoader 的共同父类是 BaseDexClassLoader 。

// dalvik/system/PathClassLoader.javapublic class PathClassLoader extends BaseDexClassLoader {/*** Creates a {@code PathClassLoader} that operates on a given list of files* and directories. This method is equivalent to calling* {@link #PathClassLoader(String, String, ClassLoader)} with a* {@code null} value for the second argument (see description there).** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param parent the parent class loader*/public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ul>** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param librarySearchPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ul>** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param librarySearchPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader* @param sharedLibraryLoaders class loaders of Java shared libraries* used by this new class loader. The shared library loaders are always* checked before the {@code dexPath} when looking* up classes and resources.** @hide*/@SystemApi(client = MODULE_LIBRARIES)public PathClassLoader(@NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent,@Nullable ClassLoader[] sharedLibraryLoaders) {this(dexPath, librarySearchPath, parent, sharedLibraryLoaders, null);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ul>** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param librarySearchPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader* @param sharedLibraryLoaders class loaders of Java shared libraries* used by this new class loader. The shared library loaders are always* checked before the {@code dexPath} when looking* up classes and resources.* @param sharedLibraryLoadersAfter class loaders of Java shared libraries* used by this new class loader. These shared library loaders are always* checked <b>after</b> the {@code dexPath} when looking* up classes and resources.** @hide*/@SystemApi(client = MODULE_LIBRARIES)public PathClassLoader(@NonNull String dexPath, @Nullable String librarySearchPath,@Nullable ClassLoader parent, @Nullable ClassLoader[] sharedLibraryLoaders,@Nullable ClassLoader[] sharedLibraryLoadersAfter) {super(dexPath, librarySearchPath, parent, sharedLibraryLoaders, sharedLibraryLoadersAfter);}
}
// dalvik/system/DexClassLoader.javapublic class DexClassLoader extends BaseDexClassLoader {/*** Creates a {@code DexClassLoader} that finds interpreted and native* code.  Interpreted classes are found in a set of DEX files contained* in Jar or APK files.** <p>The path lists are separated using the character specified by the* {@code path.separator} system property, which defaults to {@code :}.** @param dexPath the list of jar/apk files containing classes and*     resources, delimited by {@code File.pathSeparator}, which*     defaults to {@code ":"} on Android* @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.* @param librarySearchPath the list of directories containing native*     libraries, delimited by {@code File.pathSeparator}; may be*     {@code null}* @param parent the parent class loader*/public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}

可以看到两者唯一的区别在于:创建 DexClassLoader 需要传递一个 optimizedDirectory 参数,并且会将其创建 为 File 对象传给 super ,而 PathClassLoader 则直接给到null。因此两者都可以加载指定的dex,以及jar、zip、apk中的classes.dex

PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());File dexOutputDir = context.getCodeCacheDir();DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(), null,getClassLoader());

PathClassLoader在创建的时候,传入了一个重要的参数(String dexPath)就是我们APK中包含的xxxx.dex文件的路径。那我们看一下对应的类加载器中loadClass()方法是如何实现加载class文件:

那我们到PathClassLoader的父类BaseDexClassLoader中查找看是否有loadClass()方法:

在BaseDexClassLoader中仍然没有loadClass方法,那么我们继续到父类ClassLoader中查找loadClass方法:

找到loadClass方法之后,我们分析一下他的实现:
 

// java/lang/ClassLoader.javaprotected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{// First, check if the class has already been loaded// 在缓存中查找要加载的class文件Class<?> c = findLoadedClass(name);// 如果之前没有加载过该class对象if (c == null) {try {// 使用双亲委托的方式,再次查找是否已经加载过对应的calss文件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.// 最后如果在缓存以及双亲中都没有找到已加载的class,就会调用自己的方法去加载该classc = findClass(name);}}return c;
}

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

  1. 缓存中查找要加载的class文件
  2. 双亲中查找要加载的class文件
  3. 加载class文件

其实前面2个一直都是防止重复加载在做的操作,也就是已经加载过得calss不会出现重复进行加载。其中有一个概念叫双亲委托查找,这个双亲委托听名字似乎很高大上,说穿了就是创建PathClassLoader类加载器时,通过构造方法传进来的一个成员:

双亲委托机制

       可以看到创建 ClassLoader 需要接收一个 ClassLoader parent 参数。这个 parent 的目的就在于实现类加载的双 亲委托。即:某个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

// java/lang/ClassLoader.java// 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;// 省略部分代码....protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {//如果parent不为null,则调用parent的loadClass进行加载c = parent.loadClass(name, false);} else {//parent为null,则调用BootClassLoader进行加载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: new PathClassLoader("/sdcard/xx.dex", getClassLoader()); 并不仅仅 只能加载 xx.dex中的class。

值得注意的是:

c = findBootstrapClassOrNull(name);

按照方法名理解,应该是当parent为null时候,也能够加载 BootClassLoader 加载的类。

new PathClassLoader("/sdcard/xx.dex", null) ,能否加载Activity.class? 但是实际上,Android当中的实现为:(Java不同)

// java/lang/ClassLoader.java/*** Returns a class loaded by the bootstrap class loader;* or return null if not found.*/
private Class<?> findBootstrapClassOrNull(String name)
{return null;
}

接下来继续class加载的方法findClass:

findClass

可以看到在所有父ClassLoader无法加载Class时,则会调用自己的 findClass 方法。 findClass 在ClassLoader中的定义为:

// java/lang/ClassLoader.java/*** Finds the class with the specified <a href="#name">binary name</a>.* This method should be overridden by class loader implementations that* follow the delegation model for loading classes, and will be invoked by* the {@link #loadClass <tt>loadClass</tt>} method after checking the* parent class loader for the requested class.  The default implementation* throws a <tt>ClassNotFoundException</tt>.** @param  name*         The <a href="#name">binary name</a> of the class** @return  The resulting <tt>Class</tt> object** @throws  ClassNotFoundException*          If the class could not be found** @since  1.2*/
protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);
}

其实任何ClassLoader子类,都可以重写 loadClass 与 findClass 。一般如果你不想使用双亲委托,则重写loadClass 修改其实现。而重写 findClass 则表示在双亲委托下,父ClassLoader都找不到Class的情况下,定义 自己如何去查找一个Class。而我们的 PathClassLoader 会自己负责加载 MainActivity 这样的程序中自己编写的 类,利用双亲委托父ClassLoader加载Framework中的 Activity 。说明 PathClassLoader 并没有重写loadClass ,因此我们可以来看看PathClassLoader中的 findClass 是如何实现的(PathClassLoader中并重写对应的findClass而是使用其父类BaseDexClassLoader中的findClass方法)。

// dalvik/system/BaseDexClassLoader.java/*** Hook for customizing how dex files loads are reported.** This enables the framework to monitor the use of dex files. The* goal is to simplify the mechanism for optimizing foreign dex files and* enable further optimizations of secondary dex files.** The reporting happens only when new instances of BaseDexClassLoader* are constructed and will be active only after this field is set with* {@link BaseDexClassLoader#setReporter}.*/
/* @NonNull */ private static volatile Reporter reporter = null;@UnsupportedAppUsage
private final DexPathList pathList;public BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,ClassLoader[] sharedLibraryLoadersAfter,boolean isTrusted) {super(parent);this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
}// 省略部分代码.....@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {// // 省略部分代码.....// Check whether the class in question is present in the dexPath that// this classloader operates on.List<Throwable> suppressedExceptions = new ArrayList<Throwable>();Class c = pathList.findClass(name, suppressedExceptions);if (c != null) {return c;}// 省略部分代码.....return c;
}

实现非常简单,从 pathList 中查找class。pathList 又是DexPathList 类型,所以我们要看继续查看 DexPathList

// dalvik/system/DexPathList.javaDexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted) {// 省略部分代码......// save dexPath for BaseDexClassLoader// splitDexPath 实现为返回 List<File>.add(dexPath),例如dexPath为:A.dex:B.dex: C.dex .....splitDexPath会以“:”为分隔符,分解dexPath字符串为数组列表List,并且将dex文件转换为File// makeDexElements 会去 List<File>.add(dexPath) 中使用DexFile加载dex文件返回 Element数组this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);// 省略部分代码......}@UnsupportedAppUsage
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {List<File> result = new ArrayList<>();if (searchPath != null) {for (String path : searchPath.split(File.pathSeparator)) {if (directoriesOnly) {try {StructStat sb = Libcore.os.stat(path);if (!S_ISDIR(sb.st_mode)) {continue;}} catch (ErrnoException ignored) {continue;}}result.add(new File(path));}}return result;
}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)) {// 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 {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);}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;
}

以上为findClass的遍历参数dexElements 的实现,说穿了dexElements 是什么呢?就是我们APK中xxx.dex文件的一个集合,只不过是封装了Element而已。那接下来就是重点:

// dalvik/system/DexPathList.java/*** Finds the named class in one of the dex files pointed at by* this instance. This will find the one in the earliest listed* path element. If the class is found but has not yet been* defined, then this method will define it in the defining* context that this instance was constructed with.** @param name of class to find* @param suppressed exceptions encountered whilst finding the class* @return the named class or {@code null} if the class is not* found in any of the dex files*/
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;
}public Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed) {return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;
}

DexPathList的findClass方法就是遍历的Dex文件中的所有class,找到对应的class文件后直接返回,这样就完成了从我们的APK中加载class文件到虚拟机中的过程。

那么我们总结一下:

到这里类是通过加载器加载完了。介绍到这里是不是自然而然的就能够了解通过类加载来实现代码修复功能,也就是我们常说的热修复。

3 热修复

       类加载热修复原理:利用类加载器每个class只加载一次的(上面有分析)特性,那我们如果将有问题的类修复之后的class文件打包成XXX.dex文件,然后插入到刚才介绍的dexElements数组的前面的话,那么类加载器加载了我们已经修复了的class之后,就不会再加载有问题的class文件了,这样就做到了使用类加载机制实现代码的热修复:

那么来实现一下:

public class MyApplication extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//执行热修复。 插入补丁dex// /data/data/xxx/files/xxxx.dex// /sdcard/xxx.dexHotfix.installPatch(this,new File("/sdcard/patch.dex"));}
}
public class Hotfix {private static final String TAG = "Hotfix";public static void installPatch(Application application, File patch) {//1、获得classloader,PathClassLoaderClassLoader classLoader = application.getClassLoader();List<File> files = new ArrayList<>();if (patch.exists()) {files.add(patch);}File dexOptDir = application.getCacheDir();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {try {NewClassLoaderInjector.inject(application, classLoader, files);} catch (Throwable throwable) {throwable.printStackTrace();}} else {try {//23 6.0及以上if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {V23.install(classLoader, files, dexOptDir);} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {V19.install(classLoader, files, dexOptDir); //4.4以上} else {  // >= 14V14.install(classLoader, files, dexOptDir);}} catch (Exception e) {e.printStackTrace();}}}private static final class V23 {private static void install(ClassLoader loader, List<File> additionalClassPathEntries,File optimizedDirectory)throws IllegalArgumentException, IllegalAccessException,NoSuchFieldException, InvocationTargetException, NoSuchMethodException,IOException {//找到 pathListField pathListField = ShareReflectUtil.findField(loader, "pathList");Object dexPathList = pathListField.get(loader);ArrayList<IOException> suppressedExceptions = new ArrayList<>();// 从 pathList找到 makePathElements 方法并执行// 得到补丁创建的 Element[]Object[] patchElements = makePathElements(dexPathList,new ArrayList<>(additionalClassPathEntries), optimizedDirectory,suppressedExceptions);//将原本的 dexElements 与 makePathElements生成的数组合并ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", patchElements);if (suppressedExceptions.size() > 0) {for (IOException e : suppressedExceptions) {Log.w(TAG, "Exception in makePathElement", e);throw e;}}}/*** 把dex转化为Element数组*/private static Object[] makePathElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions)throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {//通过阅读android6、7、8、9源码,都存在makePathElements方法Method makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements",List.class, File.class,List.class);return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory,suppressedExceptions);}}private static final class V19 {private static void install(ClassLoader loader, List<File> additionalClassPathEntries,File optimizedDirectory)throws IllegalArgumentException, IllegalAccessException,NoSuchFieldException, InvocationTargetException, NoSuchMethodException,IOException {Field pathListField = ShareReflectUtil.findField(loader, "pathList");Object dexPathList = pathListField.get(loader);ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();ShareReflectUtil.expandFieldArray(dexPathList, "dexElements",makeDexElements(dexPathList,new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,suppressedExceptions));if (suppressedExceptions.size() > 0) {for (IOException e : suppressedExceptions) {Log.w(TAG, "Exception in makeDexElement", e);throw e;}}}private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions)throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {Method makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements",ArrayList.class, File.class,ArrayList.class);return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,suppressedExceptions);}}/*** 14, 15, 16, 17, 18.*/private static final class V14 {private static void install(ClassLoader loader, List<File> additionalClassPathEntries,File optimizedDirectory)throws IllegalArgumentException, IllegalAccessException,NoSuchFieldException, InvocationTargetException, NoSuchMethodException {Field pathListField = ShareReflectUtil.findField(loader, "pathList");Object dexPathList = pathListField.get(loader);ShareReflectUtil.expandFieldArray(dexPathList, "dexElements",makeDexElements(dexPathList,new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));}private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory)throws IllegalAccessException, InvocationTargetException,NoSuchMethodException {Method makeDexElements =ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class,File.class);return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);}}}
public class ShareReflectUtil {/*** 从 instance 到其父类 找 name 属性** @param instance* @param name* @return* @throws NoSuchFieldException*/public static Field findField(Object instance, String name) throws NoSuchFieldException {for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {try {//查找当前类的 属性(不包括父类)Field field = clazz.getDeclaredField(name);if (!field.isAccessible()) {field.setAccessible(true);}return field;} catch (NoSuchFieldException e) {// ignore and search next}}throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());}/*** 从 instance 到其父类 找  name 方法** @param instance* @param name* @return* @throws NoSuchFieldException*/public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)throws NoSuchMethodException {for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {try {Method method = clazz.getDeclaredMethod(name, parameterTypes);if (!method.isAccessible()) {method.setAccessible(true);}return method;} catch (NoSuchMethodException e) {// ignore and search next}}throw new NoSuchMethodException("Method "+ name+ " with parameters "+ Arrays.asList(parameterTypes)+ " not found in " + instance.getClass());}/*** @param instance* @param fieldName* @param patchElements 补丁的Element数组* @throws NoSuchFieldException* @throws IllegalArgumentException* @throws IllegalAccessException*/public static void expandFieldArray(Object instance, String fieldName, Object[] patchElements)throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {//拿到 classloader中的dexelements 数组Field dexElementsField = findField(instance, fieldName);//old Element[]Object[] dexElements = (Object[]) dexElementsField.get(instance);//合并后的数组Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length + patchElements.length);// 先拷贝新数组System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);//修改 classLoader中 pathList的 dexelementsdexElementsField.set(instance, newElements);}}

(以上热修复代码文件请看资源绑定)那么我们如何将一个calss文件打包成dex文件呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/20256.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 HAL库USART串口DMA IDLE中断编程:避坑指南

HAL_UART_Receive接收最容易丢数据了,STM32 HAL库UART查询方式实例 可以考虑用中断来实现,但是HAL_UART_Receive_IT还不能直接用,容易数据丢失,实际工作中不会这样用,STM32 HAL库USART串口中断编程&#xff1a;演示数据丢失, 需要在此基础优化一下. STM32F103 HAL库USART串口…

NBT群落物种级丰度鉴定新方法sylph

文章目录 简介为什么选择Sylph&#xff1f;Sylph的工作原理 Install使用解析成gtdb格式sylph 能做什么&#xff1f;sylph 不能做什么&#xff1f;ANI定义如何使用 sylph-utils 生成包含分类信息的配置文件耗时&#xff1a;66个样本耗时1h 转成easymicroplot可用数据 简介 Sylp…

VLM 系列——Qwen2.5 VL——论文解读——前瞻(源码解读)

引言 20250212苹果突然被爆将与阿里巴巴合作为中国 iPhone 用户开发人工智能功能。苹果从 2023 年就已经开始测试各类中国头部 AI 大厂开发的 AI 模型。去年&#xff0c;原本选定百度作为主要合作伙伴&#xff0c;但双方的合作并不顺利&#xff0c;百度为“Apple Intelligence”…

DeepSeek R1原理

文章目录 DeepSeek R1原理强化学习介绍Policy ModelCritic ModelReward Model三者关系智能体包含的内容环境包含的内容 知识蒸馏简介数据蒸馏Logits 蒸馏特征蒸馏 训练流程DeepSeek-R1-Zero 训练策略与价值设计奖励方式训练模板**实验观察到模型自我进化**缺点 DeepSeek-R1 训练…

如何使用DeepSeek + PlantUML/Mermaid 生成专业图表

目录 一、工具简介 1.1 DeepSeek简介 1.2 PlantUML简介 1.3 Mermaid在线工具简介 二、在DeepSeek中生成Mermaid语法 2.1 编写提示词 2.2 示例输出 2.3 访问Mermaid在线编辑器 三、在DeepSeek中生成PlantUML语法 3.1 编写提示词 3.2 示例输出 3.3 访问PlantUML在线编…

开源多商户商城源码最新版_适配微信小程序+H5+APP+PC多端

在数字化时代&#xff0c;电子商务已经成为各行业不可或缺的一部分&#xff0c;开源多商户商城源码为中小企业和个人开发者提供了快速搭建和定制电商平台的利器。分享一款最新版的开源多商户商城源码&#xff0c;它能够适配微信小程序、H5、APP和PC等多个端口&#xff0c;满足商…

PHP基础部分

但凡是和输入、写入相关的一定要预防别人植入恶意代码! HTML部分 语句格式 <br> <hr> 分割符 <p>插入一行 按住shift 输入! 然后按回车可快速输入html代码(VsCode需要先安装live server插件) html:<h1>标题 数字越大越往后</h1> <p…

短视频矩阵碰一碰发视频源码技术开发,支持OEM

在短视频矩阵碰一碰发视频的技术开发中&#xff0c;前端设计是直接面向用户的关键部分&#xff0c;它不仅决定了用户体验的好坏&#xff0c;还对整个系统的可用性和吸引力起着重要作用。本文将深入探讨在这一技术开发中前端设计的要点、流程与关键技术。 一、前端设计的整体架构…

大模型 + cursor应用案例

cursor 介绍 cursor是一个集成了GPT4、Claude 3.5等先进LLM的类vscode的编译器&#xff0c;可以理解为在vscode中集成了AI辅助编程助手&#xff0c;从下图中的页面可以看出cursor的布局和vscode基本一致&#xff0c;并且cursor的使用操作也和vscode一致&#xff0c;包括extens…

深入浅出CSS复合选择器:掌控元素关系与层级选择

目录 前言 一、子代选择器&#xff08;Descendant Selector&#xff09; 1. 什么是子代选择器&#xff1f; 2. 基本语法 3. 示例 4. 注意事项 二、直接子元素选择器&#xff08;Child Selector&#xff09; 1. 什么是直接子元素选择器&#xff1f; 2. 基本语法 3. 示例…

CSRF 漏洞原理演示 基本绕过(同源 异源) 配合XSSToken值校验复用删除

前言 CSRF的基本原理 就是 举例 我们伪造一个支付宝的转账请求&#xff08;抓包获取的&#xff09;&#xff0c;受害者 &#xff1a; 正好登录着支付宝&#xff08;后台&#xff09;的 同时点击了我们伪造的&#xff08;包含恶意请求&#xff09;网址 从而导致先我们进行转…

外包干了3天,技术退步太明显了。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;21年通过校招进入武汉某软件公司&#xff0c;干了差不多3年的功能测试&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了3年的功能测试&#xff0c;已经让…

HomeAssistant 发现MQTT设备(温度,湿度,开关)

要通过 MQTT 将温度、湿度数据以及一个灯的开关状态传输到 Home Assistant 并实现设备自动发现&#xff0c;可以按照以下步骤操作&#xff1a; 1.前期准备工作 安装MQTT服务器(EMQX)配置好(可以在HA加载项中安装,也可以在NAS上Docker安装) HA的集成中安装MQTT,并且连接上(EM…

【stm32】DAC实验(stm32f4hal库)

一、DAC简介 1、DAC 数字到模拟转换器&#xff08;DAC&#xff09;是一种电子设备&#xff0c;用于将数字信号转换为模拟信号。它通常用于将数字数据转换为模拟信号&#xff0c;以便在模拟电路中进行处理。DAC在许多领域都有广泛的应用&#xff0c;如音频设备、通信系统、仪器…

云计算架构学习之Ansible-playbook实战、Ansible-流程控制、Ansible-字典循环-roles角色

一、Ansible-playbook实战 1.Ansible-playbook安装软件 bash #编写yml [rootansible ansible]# cat wget.yml - hosts: backup tasks: - name: Install wget yum: name: wget state: present #检查playbook的语法 [rootansible ansible]…

前端工程化的具体实现细节

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Chapter 3 Differential Voltage Current amplifiers

Chapter 3 Differential Voltage & Current amplifiers 这一章介绍差分电压和电流放大器. Current mirrors 我们首先分析电流镜Current Mirror. 由一个diode-connected MOSCS放大MOS组成, diode-MOS将电流转换成电压, 再由CS MOS转换成电流. 考虑沟道调制效应, M1和M2的…

确保设备始终处于最佳运行状态,延长设备的使用寿命,保障系统的稳定运行的智慧地产开源了

智慧地产视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。通过计算机视觉和…

[笔记.AI]大模型的蒸馏、剪枝、量化 | 模型压缩 | 作用与意义

上周简单整理了《deepseek-r1的不同版本&#xff08;满血版、蒸馏版、量化&#xff09;》&#xff0c;这次继续完善对其的认知——补充“剪枝”&#xff0c;并进一步整理蒸馏、剪枝、量化的作用与意义。 以下摘自与DeepSeek-R1在线联网版的对话 蒸馏、剪枝、量化是当前主流的三…

Java 反射机制深度解析:类信息的来源、declared 的区别、赋值操作及暴力反射

在 Java 开发中&#xff0c;反射机制是一个强大且灵活的工具&#xff0c;它允许程序在运行时动态地获取类的信息、创建对象、调用方法和访问字段等。本文将结合代码示例和图示&#xff0c;深入探讨以下四个问题&#xff1a; 类信息来自哪里&#xff1f; 获取类信息时加不加 de…