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方法,就不再往下分析了。

相关推荐
万能的小裴同学2 小时前
Android M3U8视频播放器
android·音视频
q***57742 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober3 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿3 小时前
关于ObjectAnimator
android
zhangphil4 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我5 小时前
从头写一个自己的app
android·前端·flutter
lichong9516 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端
用户69371750013847 小时前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
火柴就是我7 小时前
NekoBoxForAndroid 编译libcore.aar
android
Kaede68 小时前
MySQL中如何使用命令行修改root密码
android·mysql·adb