android类加载过程,Android类装载机制

本文聚焦Android类加载过程,介绍了Android中ClassLoader的两个关键类DexClassLoader和PathClassLoader及其区别,分析了BaseDexClassLoader和DexPathList的源码,阐述了类查找逻辑。还对比了ART和Dalvik虚拟机,提及安装优化时dexopt和dexoat的作用,为热修复、插件化框架剖析做铺垫。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

上两篇文章分析了资源的加载和进程,Activity启动相关的内容,这篇是Dex加载相关的内容了,本篇结束,我们也就可以开始对于一些热修复,插件化框架的实现剖析了。

Android中ClassLoader

1460000014135323

上图为Android中ClassLoader的类图,与JVM不同,Dalvik的虚拟机不能用ClassCload直接加载.dex,Android从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader。而这两个类就是我们加载dex文件的关键,这两者的区别是:

DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk。

PathClassLoader:要传入系统中apk的存放Path,所以只能加载已经安装的apk文件。

Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流。因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。

只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,如果我们的Android应用不进行分dex处理,最后一个应用的apk只会有一个dex文件。

1460000014135324

PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,其中的主要逻辑都是在BaseDexClassLoader完成的。

可以看出在加载类时首先判断这个类是否之前被加载过,如果有则直接返回,如果没有则首先尝试让parent ClassLoader进行加载,加载不成功才在自己的findClass中进行加载。这和java虚拟机中常见的双亲委派模型一致的,这种模型并不是一个强制性的约束模型,比如你可以继承ClassLoader复写loadCalss方法来破坏这种模型,只不过双亲委派模是一种被推荐的实现类加载器的方式,而且jdk1.2以后已经不提倡用户在覆盖loadClass方法,而应该把自己的类加载逻辑写到findClass中。

ClassLoader源码分析

BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {

private final String originalPath;

private final DexPathList pathList;

public BaseDexClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(parent);

this.originalPath = dexPath;

//构造DexPahtList对象

this.pathList =

new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

//Class 查找在PathList中根据类名进行查找

Class clazz = pathList.findClass(name);

if (clazz == null) {

throw new ClassNotFoundException(name);

}

return clazz;

}

@Override

protected URL findResource(String name) {

return pathList.findResource(name);

}

@Override

protected Enumeration findResources(String name) {

return pathList.findResources(name);

}

@Override

public String findLibrary(String name) {

return pathList.findLibrary(name);

}

@Override

protected synchronized Package getPackage(String name) {

if (name != null && !name.isEmpty()) {

Package pack = super.getPackage(name);

if (pack == null) {

pack = definePackage(name, "Unknown", "0.0", "Unknown",

"Unknown", "0.0", "Unknown", null);

}

return pack;

}

return null;

}

@Override

public String toString() {

return getClass().getName() + "[" + originalPath + "]";

}

}

通过源码可以看出,在BaseDexClassLoader的构造函数中创建了DexPathList实例,在BaseDexClassLoader中,对于类的查找和资源的查找,都是通过其中的DexPathList实例来进行的。对于类的等相关信息的查找是在DexPathList中实现的,接下来,我们看一下DexPathList的源码实现。

final class DexPathList {

private static final String DEX_SUFFIX = ".dex";

private static final String JAR_SUFFIX = ".jar";

private static final String ZIP_SUFFIX = ".zip";

private static final String APK_SUFFIX = ".apk";

/** class definition context */

private final ClassLoader definingContext;

//Dex,Resource元素

private final Element[] dexElements;

//Native库的地址路径

private final File[] nativeLibraryDirectories;

public DexPathList(ClassLoader definingContext, String dexPath,

String libraryPath, File optimizedDirectory) {

if (definingContext == null) {

throw new NullPointerException("definingContext == null");

}

if (dexPath == null) {

throw new NullPointerException("dexPath == null");

}

if (optimizedDirectory != null) {

if (!optimizedDirectory.exists()) {

throw new IllegalArgumentException(

"optimizedDirectory doesn't exist: "

+ optimizedDirectory);

}

if (!(optimizedDirectory.canRead()

&& optimizedDirectory.canWrite())) {

throw new IllegalArgumentException(

"optimizedDirectory not readable/writable: "

+ optimizedDirectory);

}

}

this.definingContext = definingContext;

this.dexElements =

makeDexElements(splitDexPath(dexPath), optimizedDirectory);

this.nativeLibraryDirectories = splitLibraryPath(libraryPath);

}

}

在构造函数中,根据dexPath,构建一个DexElement数组,在后面对于类的查找就会在该数组中进行查找。

makeDexElements

/**

* 根据给予的地址构建一个Element数组

*/

private static Element[] makeDexElements(ArrayList files,

File optimizedDirectory) {

ArrayList elements = new ArrayList();

/*

* Open all files and load the (direct or contained) dex files

* up front.

*/

for (File file : files) {

ZipFile zip = null;

DexFile dex = null;

String name = file.getName();

if (name.endsWith(DEX_SUFFIX)) {

// Raw dex file (not inside a zip/jar).

try {

dex = loadDexFile(file, optimizedDirectory);

} catch (IOException ex) {

System.logE("Unable to load dex file: " + file, ex);

}

} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)

|| name.endsWith(ZIP_SUFFIX)) {

try {

zip = new ZipFile(file);

} catch (IOException ex) {

}

try {

dex = loadDexFile(file, optimizedDirectory);

} catch (IOException ignored) {

}

} else {

}

if ((zip != null) || (dex != null)) {

elements.add(new Element(file, zip, dex));

}

}

return elements.toArray(new Element[elements.size()]);

}

根据传递的路径,然后解析路径下的内容,根据dex,zip等构建Element实例,然后将这个实例添加到Element数组之中。

loadDexFile

/**

* 构造一个DexFile对象

*/

private static DexFile loadDexFile(File file, File optimizedDirectory)

throws IOException {

if (optimizedDirectory == null) {

return new DexFile(file);

} else {

String optimizedPath = optimizedPathFor(file, optimizedDirectory);

return DexFile.loadDex(file.getPath(), optimizedPath, 0);

}

}

Element数据结构

static class Element {

public final File file;

public final ZipFile zipFile;

public final DexFile dexFile;

public Element(File file, ZipFile zipFile, DexFile dexFile) {

this.file = file;

this.zipFile = zipFile;

this.dexFile = dexFile;

}

}

类的查找

/**

* 类的查找,从DexElements中,逐个DexFile中查找,如果找到,

* 就加载这个类,然后返回

*/

public Class findClass(String name) {

for (Element element : dexElements) {

DexFile dex = element.dexFile;

if (dex != null) {

Class clazz = dex.loadClassBinaryName(name, definingContext);

if (clazz != null) {

return clazz;

}

}

}

return null;

}

根据传递的目录,加载相应的dex,apk来构建层Element实例。

ART和Dalvik

Android Runtime(缩写为ART),在Android 5.0及后续Android版本中作为正式的运行时库取代了以往的Dalvik虚拟机。ART能够把应用程序的字节码转换为机器码,是Android所使用的一种新的虚拟机。它与Dalvik的主要不同在于:Dalvik采用的是JIT技术,字节码都需要通过即时编译器转换为机器码,这会拖慢应用的运行效率,而ART采用Ahead-of-time(AOT)技术,应用在第一次安装的时候,字节码就会预先编译成机器码,这个过程叫做预编译。ART同时也改善了性能、垃圾回收(Garbage Collection)、应用程序出错以及性能分析。但是请注意,运行时内存占用空间较少同样意味着编译二进制需要更高的存储。

ART模式相比原来的Dalvik,会在安装APK的时候,使用Android系统自带的dex2oat工具把APK里面的.dex文件转化成OAT文件,OAT文件是一种Android私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。这使得我们无需重新编译原有的APK就可以让它正常地在ART里面运行,也就是我们不需要改变原来的APK编程接口。ART模式的系统里,同样存在DexClassLoader类,包名路径也没变,只不过它的具体实现与原来的有所不同,但是接口是一致的。实际上,ART运行时就是和Dalvik虚拟机一样,实现了一套完全兼容Java虚拟机的接口。

dexopt还是dexoat

在安装的优化过程,dexopt函数会启动一个子进程来执行优化,同时会根据目前系统使用的是Dalvik虚拟机还是ART来决定将apk转换层何种格式,如果转换层odex格式,则调用/system/bin/dexopt文件来执行转化,如果转换层ART的oat格式,则调用/system/bin/dex2oat来执行转换。

则dexopt和dexoat中会执行一些优化操作,这个优化操作也会影响到我们后续类的动态加载。但由于上层接口一致,因此,作为开发者无需关心安装时具体的优化方式。

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值