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

相关推荐
ChinaDragonDreamer2 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院4 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下4 小时前
android navigation 用法详细使用
android
小比卡丘7 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭8 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss9 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.10 小时前
数据库语句优化
android·数据库·adb
GEEKVIP12 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200514 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68914 小时前
Android广播
android·java·开发语言