Android中的ClassLoader

ClassLoader的类型

Java中的ClassLoader加载的是 class 文件,但是Android中加载的是 dex 文件。在AndroidStudio中打开的ClassLoader.java,依次选择View > Tools Window > Hierarchy,可以调出继承关系图,如下图所示:

里面有很多个ClassLoader,其中几个比较重要的ClassLoader分别是:BootClassLoader、PathClassLoader和DexClassLoader。

  1. BootClassLoader,用于加载Android Framework层的代码。BootClassLoader定义在ClassLoader.java中,代码如下(本文源码基于Android SDK 30):
java 复制代码
class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }
    
    ...
}    

注意BootClassLoader类前面没有用public修饰,所以只有在同一个包中才能访问,在应用程序中是无法直接调用的。

  1. PathClassLoader,用于加载应用程序中的dex。应用的apk安装后会存储在系统的data/app/目录下,启动该应用时,系统会去data/app/目录下找到相应的apk加载到内存中,这个加载动作就是PathClassLoader完成的。PathClassLoader只能加载系统中已经安装的apk,对应到插件化技术中就是加载宿主apk。其代码如下:
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);
    }
}

运行如下代码:

java 复制代码
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ClassLoader classLoader1 = getClassLoader();
        Log.d("MainActivity", "classloader1:" + classLoader1);

        ClassLoader classLoader2 = Activity.class.getClassLoader();
        Log.d("MainActivity", "classloader2:" + classLoader2);
    }
}

打印结果是,classLoader1是PathClassLoader;classLoader2是BootClassLoader。

  1. DexClassLoader,允许app在运行期间加载外部的dex,也可以加载包含dex的压缩文件(jar、zip、apk等),其代码如下:
java 复制代码
public class DexClassLoader extends BaseDexClassLoader {

    //从API 26开始,参数optimizedDirectory没有作用了
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

PathClassLoader与DexClassLoader有什么区别呢?从代码来看唯一的区别是DexClassLoader需要自己传optimizedDirectory(优化路径),PathClassLoader不需要。但是上面的代码中,DexClassLoader中调用父类的构造方法传的optimizedDirectory是null,事实上从API 26开始,参数optimizedDirectory已经废弃,没有作用了,所以从API 26开始,PathClassLoader与DexClassLoader没有区别了。

ClassLoader的继承关系

运行一个应用程序需要用到几种类型的类加载器呢?运行下面的代码:

scala 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while(classLoader != null){
            Log.d("TAG", classLoader.toString());
            classLoader = classLoader.getParent();
        }

    }
}

打印如下:

2024-04-02 20:45:28.247 3255-3255 TAG com.example.test D dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/base.apk"],nativeLibraryDirectories=[/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/lib/x86, /data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test-hYnsAnZzDkTxi8o73f_kmg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]] 2024-04-02 20:45:28.248 3255-3255 TAG com.example.test D java.lang.BootClassLoader@85a3be0

可以看到两种类加载器,一种是PathClassLoader,另一种是BootClassLoader。DexPathList中包含了apk的路径,其中/data/app/~~gKyPKioueEYmKPDRHx5QaQ==/com.example.test- hYnsAnZzDkTxi8o73f_kmg==/base.apk就是应用安装在手机上的位置。DexPathList是在BaseDexClassLoader的构造方法中创建的,里面存储了dex相关文件的路径,在ClassLoader执行双亲委托机制进行查找流程时会从DexPathList中查找。

这几种常用的ClassLoader的继承关系如下:

classDiagram ClassLoader <|-- BootClassLoader ClassLoader <|-- BaseDexClassLoader BaseDexClassLoader <|-- PathClassLoader BaseDexClassLoader <|-- DexClassLoaser

ClassLoader的加载过程

Android的ClassLoader遵循双亲委托机制,所谓双亲委托机制是指加载dex文件的过程中,首先判断自己有没有加载过,如果没有,委托父加载器(注意这里父加载器是指ClassLoader.getParent()拿到的父加载器)去加载,依次递归,如果父加载器可以完成加载任务,就成功返回;只有父加载器无法完成此加载任务或者没有父加载器时,才自己去加载。

ClassLoader的加载方法为loadClass()方法,这个方法被定义在抽象类ClassLoader中,代码如下:

java 复制代码
public abstract class ClassLoader {    
    
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name); //注释1
            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) { //注释2
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }
    
    private Class<?> findBootstrapClassOrNull(String name)
    {
        return null;
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

其中注释1处检查传入的类是否已经加载过,如果已经加载过就直接返回该类。如果没有加载过,判断父加载器是否为null,如果不为null,调用父加载器的loadClass()方法;如果为null,调用findBootstrapClassOrNull()方法来加载,这个方法会直接返回null。如果注释2处代码成立,说明向上委托流程没有检查出类已经被加载,就会执行findClass()方法进行查找,ClassLoader中的findClass()方法直接抛出了异常,其子类BaseDexClassLoader对该方法进行了实现:

java 复制代码
public class BaseDexClassLoader extends ClassLoader {

    private final DexPathList pathList;
    
    public BaseDexClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
            boolean isTrusted) {
        super(parent);
        ...
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
        ...
    }
    
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        ...
        // Check whether the class in question is present in the dexPath that
        // this classloader operates on.
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions); //注释1
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}

在BaseDexClassLoader的构造方法中新建了DexPathList,在注释1处调用了DexPathList的findClass()方法:

java 复制代码
public final class DexPathList {

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) { 
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

}

里面遍历Element数组dexElements,然后调用了Element的findClass()方法,Element是DexPathList的静态内部类:

java 复制代码
public final class DexPathList {

 /*package*/ static class Element {
        private final File path;      
        private final DexFile dexFile;
        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

        public Element(DexFile dexFile, File dexZipPath) {
            if (dexFile == null && dexZipPath == null) {
                throw new NullPointerException("Either dexFile or path must be non-null");
            }
            this.dexFile = dexFile;
            this.path = dexZipPath;
            // Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
            this.pathIsDirectory = (path == null) ? null : path.isDirectory();
        }

        public Element(DexFile dexFile) {
            this(dexFile, null);
        }

        public Element(File path) {
            this(null, path);
        }

        ...
        
        public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }
    }
}    

从Element的构造函数可以看到,其内部封装了DexFile,DexFile是用来加载dex的,findClass()方法中调用了DexFile的loadClassBinaryName()方法:

java 复制代码
public final class DexFile {

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
}   

接着调用了defineClass()方法,里面调用了defineClassNative()方法,这个方法是Native方法,就不再往下分析了。

相关推荐
sweetying2 小时前
30了,人生按部就班
android·程序员
用户2018792831672 小时前
Binder驱动缓冲区的工作机制答疑
android
真夜2 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
用户2018792831673 小时前
浅析Binder通信的三种调用方式
android
用户093 小时前
深入了解 Android 16KB内存页面
android·kotlin
火车叼位4 小时前
Android Studio与命令行Gradle表现不一致问题分析
android
前行的小黑炭6 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭7 小时前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
用户20187928316719 小时前
Android黑夜白天模式切换原理分析
android
芦半山19 小时前
「幽灵调用」背后的真相:一个隐藏多年的Android原生Bug
android