前言

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

Android中ClassLoader

Android类装载机制_第1张图片

上图为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文件。

Android类装载机制_第2张图片

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

参考资料

Android动态加载之ClassLoader详解

Dexopt优化和验证Dalvik (Dalvik Optimization and Verification With dexopt)

更多相关文章

  1. 布局中文件中【控件间距参数详解以及单位选择】
  2. Android文件操作中的openFileOutPut和openFileInput
  3. Android 为什么使用DVM虚拟机,而不使用Java JVM??
  4. Android:apk文件结构
  5. Android Layout文件的属性说明
  6. 在Android中建立Android project没有R.java文件
  7. Android——Bitmap的加载和Cache

随机推荐

  1. android Notification 简单工具类
  2. Android中的一些小技巧
  3. ReactNative与Android中的屏幕适配
  4. PhoneGap Android 环境配置
  5. ubuntu14.04搭建Android-NDK开发环境
  6. Modbus在Android上的应用之Modbus RTU Ma
  7. Android如何发送Get请求
  8. Android中用代码设置ImageView的src和bac
  9. EditText的字体和大小
  10. Android--自定义Dialog,仿IOS对话框样式