我们在上一篇文章中,学习到Java中的ClassLoader的加载顺序以及双亲委托机制。但是在Android中的ClassLoader又有点不一样,Android重写了整个ClassLoader。我们来了解一下Android的ClassLoader机制

概述

Java的虚拟机是JVM,Android虽说是基于JAVA,但是为了更适应手机的特性,Android使用了自己特有的Dalvik/ART虚拟机

虽说是另一个虚拟机,但是ClassLoader的机制依旧存在,而且相似,Android的ClassLoader一样有特定的加载顺序和双亲委托机制

Dalvik/ART 虚拟机同样依靠ClassLoader来加载对应的类,但是不同于Java,Android在打包apk时并不是直接把class文件打包,而是对class文件优化之后生成dex文件,Android将所有的class文件打包成一个或多个(multiDex)文件。

然后在安装App时,Android虚拟机会进一步对apk中的dex文件进行优化

  • Dalivk虚拟机会使用DexOpt提取apk中的dex文件进一步优化,生成一个ODEX文件存储在缓存路径(/data/dalvik-cache/)下,而后打开APP可以直接加载ODEX文件而不用解析apk

  • 而ART虚拟机则会将apk中的dex文件优化为机器指令,保存为OAT文件于缓存路径下(/data/dalvik-cache/),不同于ODEX文件,CPU不需要再去解析OAT文件,因为里面已经是机器指令,这样的机制大大提高了运行效率,不过相对的占用空间就变大了

Android特有的ClassLoader

ClassLoader

Android重写了ClassLoader,我们先来看一下ClassLoader的重点代码:

public abstract class ClassLoader {    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {        // ……省略        parent = parentLoader;           }        protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {        // 省略部分代码        // 查找已经加载的类        Class<?> clazz = findLoadedClass(className);        if (clazz == null) {            // 委托parent加载            clazz = parent.loadClass(className, false);            if (clazz == null) {                // 自己加载,空方法,交由子类实现                clazz = findClass(className);            }        }        return clazz;    }    }

可以看到ClassLoader同样是拥有parent和双亲委托原则,逻辑基本和Java的一样。
不过可以看到Android中废弃了Java中将jar文件转换为Class的方法defineClass,而一般子类会将该过程交由JNI实现。

BootClassLoader

不过,我们可以看到ClassLoader文件下还有另一个类:

/** *   位于其他ClassLoader的顶层,内部基于JNI实现 */class BootClassLoader extends ClassLoader {    private static BootClassLoader instance;    public static synchronized BootClassLoader getInstance() {        if (instance == null) {            instance = new BootClassLoader();        }        return instance;    }    public BootClassLoader() {        // parent置为null        super(null, true);    }        @Override    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {        Class<?> clazz = findLoadedClass(className);        // 因为parent为null,所以跳过了调用parent.loadClass这一步        if (clazz == null) {            clazz = findClass(className);        }        return clazz;    }        @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        // 通过JNI实现        return Class.classForName(name, false, null);    }}

看这个名字,我们立刻就联想到JVM中的BootStrapClassLoader,没错,这个ClassLoader正是位于其他ClassLoader的顶层,也就是虚拟机第一个加载的ClassLoader,同样这个类的实际实现是基于JNI的。

该类负责加载Android的核心类库,如StringActivity等。

BaseDexClassLoader

看完这个类,我们再来看看ClassLoader的子类:BaseDexClassLoader:

/* *  解析Dex文件的ClassLoader的基类 */public class BaseDexClassLoader extends ClassLoader {    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {        super(parent);        this.originalPath = dexPath;        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);    }        @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        //……省略,主要基于JNI    }    }

这里提取了只提取了其中重要部分的代码,可以看到他的构造函数传入了dexPath等参数,而findClass方法主要基于JNI,正如他的注释所说,这是findClass通过JNI解析路径下的dex文件。

我们来重点看一下他的参数:

  • dexPath
    待解析文件所在的全路径,classloader将在该路径中指定的dex文件寻找指定目标类
  • optimzedDirectory
    优化路径,指的是虚拟机对于apk中的dex文件进行优化后生成文件存放的路径,如dalvik虚拟机生成的ODEX文件路径和ART虚拟机生成的OAT文件路径。
    这个路径必须是当前app的内部存储路径,Google认为如果放在公有的路径下,存在被恶意注入的危险
  • libraryPath
    指定native层代码存放路径
  • parent
    当前ClassLoaderparent,和java中classloaderparent含义一样

我们前面说了,Dalvik/ART虚拟机在第一次安装apk时,会对dex文件进行优化,存放到缓存路径,后续是直接读取缓存路径下的文件,而不再读取原文件,这里的optimzedDirectory正是指的优化后的缓存路径。

所以BaseDexClassLoaderloadClass会执行的一个流程大致如下(因为底层的JNI实现所以这里不看源码了,有兴趣可自行了解,这里只说结论):

  1. 判断optimzedDirectory路径下是否有对应的优化过的文件(ODEX/OAT)
  2. 如果步骤1判断否,那么解析dexPath路径指定的dex文件,进行优化并存储到optimzedDirectory路径下,否则直接进入步骤3
  3. 读取optimzedDirectory路径下对应的文件
  4. 解析为Class

PathDexClassLoader

接下我们看一下BaseDexClassLoader的子类,他有两个子类,DexClassLoaderPathClassLoader,我们分别看一下:

/** * 提供一个简单的ClassLoader去加载路径下指定的dex/jar/apk文件 * Android系统通过该ClassLoader去加载系统应用类和App应用 * Android建议我们不应该使用该类去加载我们自定义的类而是使用 * DexClassLoader */public class PathClassLoader extends BaseDexClassLoader {    /**     * @param dexPath 指定的dex/jar/apk文件的路径,可以包含多个路径,     * 用{File.pathSeparator}分割。     * @param parent      */    public PathClassLoader(String dexPath, ClassLoader parent) {        super(dexPath, null, null, parent);    }        // 与上一个构造函数类似,只是多了一个libraryPath表示Native库路径    public PathClassLoader(String dexPath, String libraryPath,            ClassLoader parent) {        super(dexPath, null, libraryPath, parent);    }}

可以看到,PathClassLoader里除了构造函数没有其他方法,所以根本的逻辑还是基于BaseDexClassLoader来完成。
特殊的一点是,我们发现PathClassLoader指定了optimzedDirectory为null??
这是为什么?

这个问题需要我们进入JNI才能解答,这里贴上Native层解析class的一段注释:

注释.png

这段注释的意思是,如果输入的optimzedDirectory为空,那么会使用默认的cache路径,也就是我们刚才提到的/data/dalvik-cache/。但是我们要注意到一点,一般情况下,我们的App对于这个文件夹是没有读写权限的,因此我们也就没有办法使用PathClassLoader去加载自定义的类。正如注释说的这个类一般由系统调用加载系统类和App应用。也就是说我们App中的类MainActivity等都是由其加载。

DexClassLoader

而我们如果要加载自定义的类应该使用DexClassLoader,也就是BaseDexClassLoader的另一个子类:

/** * 一个用于加载路径下指定的dex/jar/apk文件的ClasLoader * 可以加载沒有安装过的APK * 这个ClassLoader需要一个应用内私有,且可写入的路径去存储优化后的dex文件(optimizedDirectory) * 不要将优化后的文件存储在外部存储区,因为这将有可能导致你的App被恶意注入 */public class DexClassLoader extends BaseDexClassLoader {    /**     * @param dexPath 指定的dex/jar/apk文件的路径,可以包含多个路径,     * 用{File.pathSeparator}分割。     * @param optimizedDirectory 储存优化后文件的路径,必须是可写入的,不能为null     * @param libraryPath 表示Native库路径     * @param parent the parent class loader     */    public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);    }}

可以看到,DexClassLoaderPathClassLoader类似,都只是重写了构造方法,我们可以看到,其实只是对于optimizedDirectory转换成File而已。这也真是他和PathClassLoader的不同之处,他可以自定义optimizedDirectory,我们可以指向一个我们有访问权限的文件,所以我们可以利用他来加载自定义的类。

总结

  1. Android重写了Java层的ClassLoader,延续了parent和双亲委派机制
  2. Android中同样的ClassLoader同样有一个最顶层的parent,不过不同于Java中用JNI实现,在Android中是Java实现的BootClassLoader,该类负责加载Android的核心类库
  3. BaseDexClassLoader,封装了一些列解析dex/jar/apk文件方法的基类。其在loadClass的时候会将dex文件解析并优化到optimizedDirectory路径下,再进行解析。
  4. PathClassLoader,其实他的位置有点想Java中的AppClassLoader,Android系统会通过他来加载系统应用类和App类。由于没有权限访问他的文件夹,所以不适用于我们加载自定义类,一般用于加载已经安装的Apk等
  5. DexClassLoader,用于加载自定义的类,包括没有安装过的APK也可以加载,需要指定optimizedDirectory来存储优化dex后的文件。
ClassLoader结构图

更多相关文章

  1. android ART-逆向研究者的福音?
  2. [Android][Android(安卓)Studio] *.jar 与 *.aar 的生成与*.aar
  3. manifest文件
  4. 基于 Android(安卓)NDK 的学习之旅-----JNI LOG 打印(附源码)
  5. Android(安卓)WebView介绍
  6. 【Android(安卓)FFMPEG 开发】Android(安卓)中使用 FFMPEG 将 PC
  7. android环境搭建及改变默认avd路径
  8. Android逆向之旅---Android中的sharedUserId属性详解
  9. Android(安卓)Building System

随机推荐

  1. API 23 view.View——属性分析
  2. Android(安卓)实时网路监测类 步骤一(Net
  3. android 得到手机MAC
  4. Android(安卓)加密解密字符串
  5. Android(安卓)混淆JS交互没反应
  6. android中SharedPreferences使用
  7. Android(安卓)Activity 窗口大小縮放
  8. Android(安卓)Studio 3.x找不到Android(
  9. Android拍照以及前后摄像头切换
  10. android 代码设置间距