最近突发奇想,想做一个插件框架,就是实现一个微内核,所有扩展功能都通过下载插件加载的方式来实现,原理跟现在用的eclipse差不多。要做到这些动态加载类少不了,就是内核上事先定义好接口,通过网络或其他方式下载的插件要实现这些接口,只要定义好规则,系统就可以使用这些插件了。
Android动态加载不是程序本身的类,要用到PathClassLoader和DexClassLoader, 两个类的构造方法分别是:
PathClassLoader(String path, ClassLoader parent);
DexClassLoader(String dexPath, String dexOutputDir, String libPath, ClassLoader parent);
网上查了一下资料,PathClassLoader的类加载路径必须在/data/app路径下。DexClassLoader可以加载sdcard目录下的apk或jar文件,编译好的类不能立即使用,要通过dx工具转换为.dex格式的。一切实验都很顺利,但到类型转换这步出错了,代码如下:DexClassLoader loader = new DexClassLoader("/sdcard/dog.apk", "/sdcard", null, ClassLoader.getSystemClassLoader());
Class<?> clazz = loader.loadClass("czh.plugin.Dog");
Animal dog = (Animal)clazz.newInstance();
dog.sayHello();
上述代码中类Dog继承自类Animal,执行这些代码的apk定义了Animal,但Dog是在dog.apk定义的,dog.apk里面也定义了Animal。问题就出来了,在clazz实例化一个对象想转换为Animal时,程序报了ClassCastException异常。想了一下,实例化的Dog对象应该只认识dog.apk里的Animal类是它的父类,而认为执行上述代码的apk里Animal类不是它的父类。于是俺把dog.apk里的Animal类去掉,重新用dx打包放到sdcard目录下,结果这次报了不能找到类czh.plugin.Dog的异常,也就是无法加载类Dog。
查看了Class类forName(String name)方法的源代码,发现它们加载类用的不是ClassLoader.getSystemClassLoader(), 而是VMStack.getCallingClassLoader(), 于是俺把代码改了一番,终于运行通过了:
DexClassLoader loader = new DexClassLoader("/sdcard/dog.apk", "/sdcard", null, VMStack.getCallingClassLoader());
Java的ClassLoader其实是一个链式结构,加载类的时候优先从最顶层的ClassLoader开始加载,若无法找到该类才尝试下一级ClassLoader加载。上述代码保证了类Animal能被VMStack.getCallingClassLoader()加载,类Dog被loader加载,同时这两个类在同一个ClassLoader加载链中,确保了它们的父子关系能正常识别,这样就不会发生ClassCastException异常了。
不过VMStack这个类被标识为deprecated, 将来可能被google去除不给我们使用,到时候就要想些方法来克服这点困难了。