总结自ClassLoader的机制
一个运行中的APP不仅只有一个类加载器
[onCreate] classLoader 1 : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/me.kaede.anroidclassloadersample-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
[onCreate] classLoader 2 : java.lang.BootClassLoader@14af4e32
如上代码就是在OnCreate调用
ClassLoader classLoader = getClassLoader();
classLoader = classLoader.getParent();
的两个classLoad的打印,一个是BootClassLoader,另一个是PathClassLoader。其中BootClassLoader是系统启动的时候就会调用,用来加载一些Framwork需要的类,而App启动的时候也会把这个Boot类型的传进来。而下面这个PathClassLoader则是应用启动的时候创建的,用来加载base.apk里面的类。因此一个运行的Android应用至少应该有2个ClassLoader
ClassLoader的构造函数为
ClassLoader(ClassLoader parentLoader, boolean nullAllowed)
因此ClassLoader需要一个现有的ClassLoader创建,是一个树形结构,这是ClassLoader的双亲代理模型特点
loadClass方法在加载一个类的实例的时候的步骤如下
在java中,只有当两个实例的类名、包名以及加载的ClassLoader都相同,才会被认为是同一种类型
在Android中,ClassLoader是一个抽象类,具体实现分别是DexClassLoader与PathClassLoader
DexClassLoader可以加载jar/apk/dex,也可以从SD卡中加载未安装的apk
PathClassLoader只能加载系统中已经安装过的apk
其实两者都是继承了BaseDexClassLoader,不同的是,DexClassLoader的构造函数可以多一个optimizedDirectory,PathClassLoader传入的固定是一个null,而这个是dex文件的缓存路径,由于无论是哪种加载,加载的可执行文件一定得放在内部存储。所以DexClassLoader由于可以指定optimizedDirector,则可以加载外部dex,因为这个dex会被复制到内部路径。
而加载类的loadClass方法深究到底,其实就是遍历之前所有的DexFile实例,再用循环调用loadClassBinaryName方法一个个尝试,最终调用到如下native代码
private native static Class defineClass(String name, ClassLoader loader, int cookie);
File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test_dexloader.jar");// 外部路径
File dexOutputDir = this.getDir("dex", 0);// 无法直接从外部路径加载.dex文件,需要指定APP内部路径作为缓存目录(.dex文件会被解压到此目录)
DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),dexOutputDir.getAbsolutePath(), null, getClassLoader());
这样就能加载.dex文件,注意:这里之所以能加载jar文件,是因为这个jar文件已经优化,其中含有.dex文件,.apk也是如此,ClassLoader在加载时会自动把其中的.dex给解压出来
上面的代码已经加载了dex文件,需要调用的话可以使用两种方法
这一段的代码都来自Android动态加载入门 简单加载模式
DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null, getClassLoader());
Class libProviderClazz = null;
try {
libProviderClazz = dexClassLoader.loadClass("me.kaede.dexclassloader.MyLoader");
// 遍历类里所有方法
Method[] methods = libProviderClazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Log.e(TAG, methods[i].toString());
}
Method start = libProviderClazz.getDeclaredMethod("func");// 获取方法
start.setAccessible(true);// 把方法设为public,让外部可以调用
String string = (String) start.invoke(libProviderClazz.newInstance());// 调用方法并获取返回值
Toast.makeText(this, string, Toast.LENGTH_LONG).show();
} catch (Exception exception) {
// Handle exception gracefully here.
exception.printStackTrace();
}
而使用接口的方式如下
pulic interface IFunc{
public String func();
}
// 调用
IFunc ifunc = (IFunc)libProviderClazz;
String string = ifunc.func();
Toast.makeText(this, string, Toast.LENGTH_LONG).show();