Android类装载机制
发布日期:2021-08-25 15:35:14 浏览次数:10 分类:技术文章

本文共 7824 字,大约阅读时间需要 26 分钟。

前言

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

Android中ClassLoader

上图为Android中ClassLoader的类图,与JVM不同,Dalvik的虚拟机不能用ClassCload直接加载.dex,Android从ClassLoader派生出了两个类:DexClassLoaderPathClassLoader。而这两个类就是我们加载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文件。

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

参考资料

转载地址:https://blog.csdn.net/weixin_33858336/article/details/87986672 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Autorelease 详解
下一篇:表单序列化之serialize && serializeArray

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月07日 08时56分49秒