一、什么是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());
}
}
}
可以看见有2个Classloader实例,一个是BootClassLoader,另一个是PathClassLoader,由此也可以看出,一个运行的Android应用至少有2个ClassLoader。
查看官方文档我们可以知道Android中类加载器有BootClassLoader,URLClassLoader,
PathClassLoader,DexClassLoader,BaseDexClassLoader 等都最终继承自java.lang.ClassLoader。
三、使用ClassLoader加载类
在前面我们看到,Android程序启动过程中,会有一个dalvik.system.PathClassLoader,查看官方文档PathClassLoader,可以得知,PathClassLoader是ClassLoader的一个简单实现,主要用于加载系统类和加载应用中的类。
查看父类BaseDexClassLoader可以看到ClassLoader还有一个实现:DexClassLoader,通过官方文档可以得知,DexClassLoader加载.jar或者apk里面的class.dex文件,可以用于加载未安装的代码。而我们研究动态加载的目的主要是用于插件化或者热更新功能,加载的代码必然也是未安装的,所以我们要用的就是DexClassLoader。
- 实例化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,由上而下的加载机制,防止重复加载类字节码) 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对不上。
这两个问题,将在后续章节讲解。