05
2017
10

类加载器ClassLoader

类加载器ClassLoader

总结自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(ClassLoader parentLoader, boolean nullAllowed)
因此ClassLoader需要一个现有的ClassLoader创建,是一个树形结构,这是ClassLoader的双亲代理模型特点

loadClass方法在加载一个类的实例的时候的步骤如下

  1. 会先查询当前的ClassLoader是否加载过此类,有就直接返回
  2. 会查询parent的ClassLoader是否加载过此类,如果加载过,就直接返回parent加载的类
  3. 如果整个继承路线上的ClassLoader都没有加载,才会由当前的ClassLoader来加载此类

在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);

使用DexClassLoader代码

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文件,需要调用的话可以使用两种方法

  1. 使用反射的方式
  2. 使用接口的方式

这一段的代码都来自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();
上一篇:iOS 多任务下载(支持离线 下一篇:GridView布局