Android提供动态加载机制,允许从SD卡中加载dex格式的文件,其中,DexClassLoader类起了关键作用。
首先看下Android Developer关于DexClassLoader的介绍,
A class loader that loads classes from
This class loader requires an application-private, writable directory to cache optimized classes. Use
.jar
and
.apk
files containing a
classes.dex
entry. This can be used to execute code not installed as part of an application.
This class loader requires an application-private, writable directory to cache optimized classes. Use
Context.getCodeCacheDir()
to create such a directory:
File dexOutputDir = context.getCodeCacheDir();
Do not cache optimized classes on external storage.
External storage does not provide access controls necessary to protect your application from code injection attacks.
简单翻译一下,DexClassLoader是一个加载.jar或.apk文件的类加载器,其中该文件是dex格式。其可以用来执行程序之外的其他代码。该加载器需要一个程序私有的文件,Google推荐使用context.getCodeChaceDir()目录来使用。
我们来看下其构造函数,
public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
Added in
API level 3
Creates a DexClassLoader
that finds interpreted and native code. Interpreted classes are found in a set of DEX files contained in Jar or APK files.
The path lists are separated using the character specified by the path.separator
system property, which defaults to :
.
Parameters
dexPath | the list of jar/apk files containing classes and resources, delimited by File.pathSeparator , which defaults to ":" on Android |
---|---|
optimizedDirectory | directory where optimized dex files should be written; must not be null |
libraryPath | the list of directories containing native libraries, delimited by File.pathSeparator ; may be null |
parent | the parent class loader |
其中,dexPath指定了要加载的文件目录;
optimizedDirectory
是优化文件存放的路径,也就是上面提到的应该是程序私有的路径(其他程序无法访问的),libraryPath包含的native库路径,parent,指定父加载器。
该类除了构造器之外,没有其他方法,我们看下其继承关系。
java.lang.Object | |||
↳ | java.lang.ClassLoader | ||
↳ | dalvik.system.BaseDexClassLoader | ||
↳ | dalvik.system.DexClassLoader |
看到,其继承了BaseDexClassLoader类,
BaseDexClassLoader继承了ClassLoaer类,我们找到其源码,进行分析,
1. DexClassLoader类只有一个构造函数,将4个构造参数传给
BaseDexClassLoader,接下来分析
BaseDexClassLoader,
2. BaseDexClassLoader有两个成员变量,String originalPath和DexPathList pathList;看其注释,
originalPath指的是其文件路径,pathlist是路径元素列表(原文是,structured lists of path elements,下文会分析),
BaseDexClassLoader
重载了ClassLoader父类的一些方法,我们注重分析findclass,其代码如下,
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
然而其并没有重载父类的loadClass方法(加载类时最主要用到的方法),我们接着分析ClassLoader类
3. ClassLoader
其包含了很多方法,我们注重分析loadClass方法,
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
// Don't want to see this.
}
if (clazz == null) {
clazz = findClass(className);
}
}
return clazz;
}
我们可以看到,当loadClass时,会先判断是否已加载过,若加载过,则不需要重新加载,若没有加载过,则调用parent.loadClass方法,若其返回为null(说明parent未加载成功),则调用findClass自己加载,上面的机制称为双亲委派机制。+
好,接下来我们测试一下DexClassLoader能否成功加载外部代码。
1. 生成外部加载代码
我们新建一个Android工程,其目录如下,
其中,ILib定义了一个接口,关于定义接口的好处,下面会提到。
public interface ILib {
public void sayHello();
}
Lib实现了ILIb接口,在sayHello中输出一句话
public class Lib implements ILib {
@Override
public void sayHello() {
// TODO Auto-generated method stub
System.out.println("Hello from Thrid Dex Lib");
}
}
然后,让我们导出Lib类,选中Lib.java,右键export,选择java-jar file导出,命名为lib.jar,注意导出时,不要选择接口文件(ILib),否则会出错。
上面提到DexClassLoader加载的是Dex格式的文件,但是Eclipse导出的是jar格式,所以,我们得用Android的dx命令来将lib.jar编译为dex格式。dx命令位于Android sdk/build-tools/版本号/目录,为了方便可以将其路径加入到系统变量中,然后在cmd中切换到lib.jar所在目录,执行一下命令
dx --dex --output=mylib.jar lib.jar
得到mylib.jar文件
2. 动态加载
得到test.jar文件后,我们将其放入到工程的aseet目录,由于DexClassLoader是从SD卡中加载代码的,所以我们在程序运行的时候,将test.jar从asset目录copy到SD卡中,注意copy到的路径必须是该程序私有的。
private void copy2SDCard(File dexInternalStoragePath) {
BufferedInputStream bis;
try {
bis = new BufferedInputStream(getAssets().open(LIB_JAR));
OutputStream dexWriter = new BufferedOutputStream(
new FileOutputStream(dexInternalStoragePath));
byte[]