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

Android平台的ClassLoader


Android中的ClassLoader

Android中类加载器有BootClassLoader,URLClassLoader,
PathClassLoader,DexClassLoader,BaseDexClassLoader,等都最终继承自java.lang.ClassLoader。

  • ClassLoader

    java.lang.ClassLoader是所有ClassLoader的最终父类。构造方法主要以下两种
    1.传入一个父类构造器

    实际构造器 2.无参默认构造法





    无参构造器 可以看出ClassLoader主要就是传入一个父构造器,而且一般父构造器不能为空,不像java虚拟机里父构造器为空时默认的父构造器为Bootstrap ClassLoader。Android中默认无父构造器传入的情况下,默认父构造器为一个PathClassLoader且此PathClassLoader父构造器为BootClassLoader。
    ClassLoader中重要的方法是loadClass(String name),其他的子类都继承了此方法且没有进行复写。


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

    和java虚拟机中不同的是BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,所以我们没法使用。
  • URLClassLoader

    只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。
  • BaseDexClassLoader

    PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,其中的主要逻辑都是在BaseDexClassLoader完成的。这些源码在java/dalvik/system中。
    先看下BaseDexClassLoader的构造方式:



    BaseDexClassLoader的构造函数包含四个参数,分别为:
    • dexPath,指目标类所在的APK或jar文件的路径,类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得。上面"支持加载APK、DEX和JAR,也可以从SD卡进行加载"指的就是这个路径,最终做的是将dexPath路径上的文件ODEX优化到内部位置optimizedDirectory,然后,再进行加载的。
    • File optimizedDirectory,由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。其实APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件,因为你提取出来了,系统第一次启动的时候就不用去解压程序压缩包的程序,少了一个解压的过程。这样的话系统启动就加快了。为什么说是第一次呢?是因为DEX版本的也只有第一次会解压执行程序到 /data/dalvik-cache(针对PathClassLoader)或者optimizedDirectory(针对DexClassLoader)目录,之后也是直接读取目录下的的dex文件,所以第二次启动就和正常的差不多了。当然这只是简单的理解,实际生成的ODEX还有一定的优化作用。ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须为内部路径。
    • libPath,指目标类中所使用的C/C++库存放的路径
    • classload,是指该装载器的父装载器,一般为当前执行类的装载器,例如在Android中以context.getClassLoader()作为父装载器。
  • DexClassLoader

    在看下DexClassLoader和PathClassLoader的构造器:


    DexClassLoader支持加载APK、DEX和JAR,也可以从SD卡进行加载。
    上面说dalvik不能直接识别jar,DexClassLoader却可以加载jar文件,这难道不矛盾吗?其实在BaseDexClassLoader里对".jar",".zip",".apk",".dex"后缀的文件最后都会生成一个对应的dex文件,所以最终处理的还是dex文件,而URLClassLoader并没有做类似的处理。
    一般我们都是用这个DexClassLoader来作为动态加载的加载器。
  • PathClassLoader




    很简单明了,可以看出PathClassLoader没有将optimizedDirectory置为Null,也就是没设置优化后的存放路径。其实optimizedDirectory为null时的默认路径就是/data/dalvik-cache 目录。
    PathClassLoader是用来加载Android系统类和应用的类,并且不建议开发者使用。


    很多博客里说PathClassLoader只能加载已安装的apk的dex,其实这说的应该是在dalvik虚拟机上,在art虚拟机上PathClassLoader可以加载未安装的apk的dex(在art平台上已验证),然而在/data/dalvik-cache 确未找到相应的dex文件,怀疑是art虚拟机判断apk未安装,所以只是将apk优化后的odex放在内存中,之后进行释放,这只是个猜想,希望有知道的可以告知一下。因为dalvik上无法使用,所以我们也没法使用。

ClassLoader加载class的过程

#BaseDexClassLoader@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {     Class clazz = pathList.findClass(name);    if (clazz == null) {         throw new ClassNotFoundException(name);     }     return clazz;}#DexPathListpublic 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;}#DexFilepublic Class loadClassBinaryName(String name, ClassLoader loader) {     return defineClass(name, loader, mCookie);}private native static Class defineClass(String name, ClassLoader loader, int cookie);

可以看出,BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的数组dexElements,由上面分析知道,dexPath传入的原始dex(.apk,.zip,.jar等)文件在optimizedDirectory文件夹中生成相应的优化后的odex文件,dexElements数组就是这些odex文件的集合,如果不分包一般这个数组只有一个Element元素,也就只有一个DexFile文件,而对于类加载呢,就是遍历这个集合,通过DexFile去寻找。最终调用native方法的defineClass。

ART虚拟机的兼容性问题

Android Runtime(缩写为ART),在Android 5.0及后续Android版本中作为正式的运行时库取代了以往的Dalvik虚拟机。ART能够把应用程序的字节码转换为机器码,是Android所使用的一种新的虚拟机。它与Dalvik的主要不同在于:Dalvik采用的是JIT技术,字节码都需要通过即时编译器(just in time ,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虚拟机的接口。







更多相关文章

  1. Android(安卓)根文件系统启动过程。
  2. Android(安卓)NDK开发之旅(2):Android(安卓)Studio中使用CMake进
  3. Android(安卓)Studio中创建Selector文件的方法
  4. Android与JS代码交互
  5. Android(安卓)HAL 介绍
  6. android定制系统实现app自启
  7. Android(安卓)热修复使用Gradle Plugin1.5改造Nuwa插件
  8. Android(安卓)图片高效加载
  9. NPM 和webpack 的基础使用

随机推荐

  1. Android(安卓)Unity3D 相互交互,及退出继
  2. Android在线开发工具 App Inventor
  3. Android(安卓)中 MVC 的简单理解
  4. Android提高篇之Adapter的抽象整合
  5. 报告 Bug
  6. Android扫描wifi二维码自动连接wifi
  7. Eclipse下修改Android里的apk包名
  8. android app在系统重启绕过USB授权对话框
  9. Android性能优化之使用线程池处理异步任
  10. android键盘遮挡webview的输入框问题