Android ClassLoader解析(2) - Andro

作者: sunnyaxin | 来源:发表于2018-10-25 15:10 被阅读14次

    ClassLoader的主要作用就是加载类,且Android应用逻辑也是使用Java语言编写的,但Android ClassLoader和Java ClassLoader的原理却不太一样,关于Java 中ClassLoader的主要原理及使用见上一篇博文,本文主要比较两者的差别后,主要讨论Android ClassLoader的原理及使用。

    Android ClassLoader & Java ClassLoader

    Java中的ClassLoader可以加载jar文件和Class文件;但在Android中不同,Android打包后是apk应用,解压后看到其中包含一个class.dex文件,无论DVM还是ART他们加载的不再是Class文件,而是Dex文件,因此ClassLoader在加载类的过程中也略有不同。

    ClassLoader的继承关系

    其中,他们的关系如下:

    • ClassLoader:抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类;
    • SecureClassLoader: 和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性;
    • URLClassLoader:和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源;但由于dalvik不能直接识别jar,所以在Android中无法使用这个加载器;
    • BaseDexClassLoader:继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它,他们的主要逻辑都在这里;
    • InMemoryDexClassLoader:是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件;

    BootClassLoader

    该类在libcore/ojluni/src/main/java/java/lang/ClassLoader.java,与Java中的BootstrapClassLoader不同,他不是C/C++代码,而是Java代码,且是ClassLoader的内部类,且BootClassLoader是在Zygote进程的入口方法中创建的。(注:BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们无法使用)

    PathClassLoader

    该类在libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java,用来加载系统类和应用程序的类,且不建议开发直接使用。代码如下:

    public class PathClassLoader extends BaseDexClassLoader {
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }
        public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    }
    

    PathClassLoader则是在Zygote进程创建SystemServer进程时创建的,其构造方法有三个参数:

    • dexPath: 目标类所在的APK或jar文件的路径,多个路径用文件分隔符分隔,默认文件分隔符为‘:’。支持加载APK、DEX和JAR,也可以从SD卡进行加载,最后都会生成一个对应的dex文件。且最终将dexPath路径上的文件ODEX优化到内部位置optimizedDirectory,再进行加载;
    • librarySearchPath: 包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null;
    • parent: ClassLoader的parent;

    DexClassLoader

    该类在libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java,可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载。因此可以在应用未安装的情况下加载dex相关文件。因此,它是热修复和插件化技术的基础。代码如下:

    public class DexClassLoader extends BaseDexClassLoader {
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
        }
    }
    

    其中,参数optimizedDirectory表示存储ODEX文件的路径,ClassLoader只能加载内部存储路径中的dex文件,所以这个路径必须是一个内部存储路径。 应用程序第一次被加载的时候,为了提高以后的启动速度和执行效率,Android系统会对dex相关文件做一定程度的优化,并生成一个ODEX文件,此后再运行这个应用程序的时候,只要加载优化过的ODEX文件就行了,省去了每次都要优化的时间。

    PathClassLoader没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的路径为:/data/dalvik-cache

    ClassLoader类加载过程

    阅读源码可知,Android中ClassLoader加载类的过程也符合双亲委派机制。根据之前的分析可知,在查到对象的过程中,如果一直委托到顶层的父加载器依然找不到,则会调用findClass向下查找:

        protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                   }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        c = findClass(name);
                    }
                }
                return c;
        }
    

    且PathClassLoader和DexClassLoader的实现中只有构造函数,其具体实现都在BaseDexClassLoader中,其部分源码为:

    private final DexPathList pathList;
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException { 
        Class clazz = pathList.findClass(name);
        if (clazz == null) { 
            throw new ClassNotFoundException(name); 
        } 
        return clazz;
    }
    

    DexPathList 中的 findClass:

    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;
    }
    

    DexFile 中的loadClassBinaryName:

    public 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,类加载的过程即:遍历这个集合,通过DexFile去寻找,最终调用native方法的defineClass。

    ClassLoader实例有多个

    一个运行的Android应用至少有2个ClassLoader

    1. BootClassLoader:在Android系统启动的时候创建,用于加载系统Framework层级需要的类,而我们的Android应用也有可能需要用到一些系统的类,所以APP启动的时候也会将BootClassLoader传进来;
    2. 自己的ClassLoader实例:应用启动时创建,用于加载应用dex文件中的类;

    Android ClassLoader动态加载可能遇到的问题

    1. 动态加载四大组件,需要提前在Manifest中注册;
    2. Resource资源问题,常见的是资源id问题;

    相关文章

      网友评论

        本文标题:Android ClassLoader解析(2) - Andro

        本文链接:https://www.haomeiwen.com/subject/gllqtqtx.html