本文共 4616 字,大约阅读时间需要 15 分钟。
在Java环境中,有个概念叫做”类装载器(Class Loader)”,其作用是动态加载Class文件.标准的Java SDK中有一个ClassLoader类,借助他可以装载想要的Class文件,每个ClassLoader对象在初始化的时候必须指定Class文件的路径.
但我们在使用java的时候,基本上没有使用过ClassLoader,仅仅使用import就可以加载类文件了,简单的讲,import中所引用的类文件有两个特点:
1:必须存在于本地,当程序运行需要该类的时候,内部类装载器会自动装载该类,这对程序员来说是透明的,即程序员感知不到该过程
2:编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译.但在有些情况下,所需要的类却不能满足以上两个条件.比如当该类是从远程下载并在本地执行的时候,典型的例子就是通过浏览器中的AppletLet执行的java程序,这些要执行的程序是在服务器端.另一种情况是,要引用的Class文件不方便在编译的时候直接参与,而只能在运行时动态调用.例如,在Android Framework中,所包含的Class文件是一些通用的类文件,但对于一些设备商而言,他们需要扩充Framework,扩充的具体工作包括两点:
1:需要增加一些额外的类文件,这些类文件提供厂商自定义的功能,这些文件一般以独立的jar包存在
2:需要修改Framework中的已有的类文件,比如WindowManagerService类,在该类中添加使用自定义jar包中的代码.使用自定义jar常用的方法是使用import关键字包含自定义的类,但为了保持和原生Framework的兼容性,对原声Framework最少化修改,可以使类装载器动态装载自定义jar包.
这就是使用ClassLoader的原因.
在一般情况下,应用程序不需要创建一个全新的ClassLoader对象,而是使用当前环境已经存在的ClassLoader.因为java的Runtime环境在初始化时,其内部会创建一个ClassLoader对象用于加载Runtime所需的各种java类.
每个ClassLoader必须有一个父ClasLoader,在装载Class文件的时候,子ClassLoader会先请求其父ClassLoader加载该Class文件,只有当其父ClassLoader找不到该Class的时候,子ClassLoader才会急促装载该类,这是一种安全机制.
对于Android的应用程序,本质上虽然也是用Java开发,并且使用标准的Java编译器编译出Class文件,但最终的APK文件中包含的确实dex类型的文件.dex文件是将所需的所有Class文件重新打包,打包的规则不是简单的压缩,而是完全对Class文件内部的各种函数表,变量表等进行优化,并产生一个新的文件,这就是dex文件.由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class文件就需要特殊的类装载器,这就是DexClassLoader.Android SDK中提供了DexClassLoader类就是处于这个目的.
下面我们就来看一下DexClassLoader的调用.
首先我们新建一个Android Project,命名为Plugin.我们的包名设置为:
com.chen.plugin在我们的包com.chen.plugin中创建一个activity,这个随便创建,我们用不到这个,该activity的用处仅仅就是用于启动android程序的.
在包com.chen.plugin中创建一个新的class,命名为PluginClass.
在类PluginClass中我们添加一个函数,名称为function1(int a,int b).
看一下我们的代码:
package com.chen.plugin;import android.util.Log;public class PluginClass { public PluginClass(){ Log.e("Plugin","PluginClass client initialized"); } public int function1(int a,int b){ return a+b; }}
然后运行我们这个项目,在手机中安装.
第二步,我们创建一个新的Android Project,命名为Host.同样创建一个新的Activity.
在MainActivity中添加一个方法,useDexClassLoader(),下面看一下这个方法的具体实现.
public void useDexClassLoader(){ Intent intent = new Intent(Intent.ACTION_MAIN,null); intent.addCategory(Intent.CATEGORY_LAUNCHER); PackageManager pm = getPackageManager(); final Listplugins = pm.queryIntentActivities(intent, 0); ResolveInfo rinfo = null; if(plugins != null){ for(int i = 0;i < plugins.size();i++){ ResolveInfo r = plugins.get(i); ActivityInfo ainfo = r.activityInfo; String div = System.getProperty("path.seperator"); String packageName = ainfo.packageName; if(packageName.equals("com.chen.plugin")){ rinfo = plugins.get(i); } } } ActivityInfo ainfo = rinfo.activityInfo; String div = System.getProperty("path.seperator"); String packageName = ainfo.packageName; String dexPath = ainfo.applicationInfo.sourceDir; String dexOutputDir = getApplicationInfo().dataDir; String libPath = ainfo.applicationInfo.nativeLibraryDir; DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath, this.getClass().getClassLoader()); try{ Class clazz = cl.loadClass(packageName+".PluginClass"); Object obj = clazz.newInstance(); Class[] params = new Class[2]; params[0] = Integer.TYPE; params[1] = Integer.TYPE; Method action = clazz.getMethod("function1", params); Integer ret = (Integer)action.invoke(obj, 12,34); Log.e("Host","return value is"+ret); }catch(Exception e){ Log.e("errpr",e.getMessage()); } }
在我们的MainActivity的OnCreate()方法中调用该方法,然后在手机中运行Host,就可以看到我们的调试信息.
我们先来看一下结果:
很明显,Plugin中的函数在Host中被调用了.
下面我们看一下DexClassLoader构造函数的参数的意义:
创建了DexClassLoader对象之后,就可以调用loadClass()来装载指定的类了.该函数返回的是一个Class对象,注意区分Class对象和目标类PluginClass对象,Class对象是ClassLoader所能识别的类,而PluginClass是程序执行后所能识别的类,此时仅仅装载了PluginClasss的程序代码,但是还没有创建 PluginClass对象,因此接下来调用Class对象的newInstance()方法,该方法内部会调用PluginClass的构造函数,并返回i一个真正的PluginClass对象.
虽然生成了PluginClass对象,但是Host本地并没有其函数,所以只能使用反射机制来调用PluginClass的方法.
关于反射机制,后面文章中会有更加详细的使用方式.