Android插件化(一)之谈谈ClassLoader

一、什么是ClassLoader?

Java程序是由若干.class文件组成,程序运行在虚拟机上时,会调用该程序的入口函数来调用系统的相关功能,而这些功能都被封装在不同的.class文件中。虚拟机根据程序的需要,通过Java的类加载机制来动态加载某个class文件到内存当中,只有class文件被载入到了内存之后,才能被其它class所引用,而完成这一个加载工作的角色就是ClassLoader。

二、Android中的ClassLoader

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    ClassLoader classLoader = getClassLoader();  
    if (classLoader != null) {  
        Log.i(TAG, "onCreate classLoader " + i + " : " + classLoader.toString());  
        while (classLoader.getParent() != null) {  
            classLoader = classLoader.getParent();
            i = i + 1;  
            Log.i(TAG, "onCreate classLoader " + i + " : " + classLoader.toString());  
        }  
    }  
}  

log

可以看见有2个Classloader实例,一个是BootClassLoader,另一个是PathClassLoader,由此也可以看出,一个运行的Android应用至少有2个ClassLoader。
查看官方文档我们可以知道Android中类加载器有BootClassLoader,URLClassLoader,
PathClassLoader,DexClassLoader,BaseDexClassLoader 等都最终继承自java.lang.ClassLoader。

三、使用ClassLoader加载类

在前面我们看到,Android程序启动过程中,会有一个dalvik.system.PathClassLoader,查看官方文档PathClassLoader,可以得知,PathClassLoader是ClassLoader的一个简单实现,主要用于加载系统类和加载应用中的类。
pathClassLoader

查看父类BaseDexClassLoader可以看到ClassLoader还有一个实现:DexClassLoader,通过官方文档可以得知,DexClassLoader加载.jar或者apk里面的class.dex文件,可以用于加载未安装的代码。而我们研究动态加载的目的主要是用于插件化或者热更新功能,加载的代码必然也是未安装的,所以我们要用的就是DexClassLoader。

  1. 实例化DexClassLoader
    首先我们看下DexClassLoader的构造函数
    public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
    super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    
    }
    }
    参数详解:
    dexPath:dex文件路径列表,多个路径使用”:”分隔
    optimizedDirectory:经过优化的dex文件输出目录,必须是应用私有目录
    libraryPath:动态库路径(将被添加到app动态库搜索路径列表中)
    parent:现有的ClassLoader实例,这个参数的主要作用是保留java中ClassLoader的双亲代理模型(优先父类加载器加载classes,由上而下的加载机制,防止重复加载类字节码)
  2. loadClass
    JVM中ClassLoader通过defineClass方法加载jar里面的Class,而Android中这个方法被弃用了。

    @Deprecated
    protected final Class<?> defineClass(byte[] classRep, int offset, int length) throws ClassFormatError {
    throw new UnsupportedOperationException(“can’t load this type of class file”);
    }
    DexClassLoader中使用loadClass方法

    public Class<?> loadClass(String className) throws ClassNotFoundException {

    return loadClass(className, false);
    

    }

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(className);
    if (clazz == null) {

    ClassNotFoundException suppressed = null;
    try {
        clazz = parent.loadClass(className, false);
    } catch (ClassNotFoundException e) {
        suppressed = e;
    }
    if (clazz == null) {
        try {
            clazz = findClass(className);
        } catch (ClassNotFoundException e) {
            e.addSuppressed(suppressed);
            throw e;
        }
    }
    

    }
    return clazz;
    从源码中我们也可以看出,loadClass方法在加载一个类的实例的时候
    1) 会先查询当前ClassLoader实例是否加载过此类,有就返回;

2) 如果没有。查询Parent是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类;

3) 如果继承路线上的ClassLoader都没有加载,才由Child执行类的加载工作;

这样做有个明显的特点,如果一个类被位于树根的ClassLoader加载过,那么在以后整个系统的生命周期内,这个类永远不会被重新加载。也就是双亲代理模型体现。

四、结束语

通过上面的分析,我们知道使用ClassLoader动态加载一个外部的类是非常容易的事情,所以很容易就能实现动态加载新的可执行代码的功能,但是比起一般的Java程序,在Android程序中使用动态加载主要有两个麻烦的问题:

1) Android中许多组件类(如Activity、Service等)是需要在Manifest文件里面注册后才能工作的(系统会检查该组件有没有注册),所以即使动态加载了一个新的组件类进来,没有注册的话还是无法工作;

2) Res资源是Android开发中经常用到的,而Android是把这些资源用对应的R.id注册好,运行时通过这些ID从Resource实例中获取对应的资源。如果是运行时动态加载进来的新类,那类里面用到R.id的地方将会抛出找不到资源或者用错资源的异常,因为新类的资源ID根本和现有的Resource实例中保存的资源ID对不上。
这两个问题,将在后续章节讲解。