android PathClassLoader DexClassLoader BaseDexClassLoader
Android类加载器:
BootClassLoader,URLClassLoader,PathClassLoader,DexClassLoader,BaseDexClassLoader等最终都继承自java.lang.ClassLoader,
//1、ClassLoader中loadClass()函数
Android中的ClassLoader与Java有些不同,Android中ClassLoader加载的是dex文件,而Java中加载的是jar文件.相同的是两者都采用了双亲委派模型.
其他的子类都继承了此方法且没有进行复写.
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
// 检查Class是否已加载过
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
//使用parent ClassLoader去加载Class
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
// 加载不成功,调用findClass函数来获取class对象.
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
2、PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,其主要逻辑都是在BaseDexClassLoader完成
public class BaseDexClassLoader extends ClassLoader{
…
/**
* Constructs an instance.
*
* @param dexPath:the list of jar/apk files containing classes and resources, delimited by {@code File.pathSeparator}, which defaults to {@code “:”} on Android
* @param optimizedDirectory:directory where optimized dex files should be written; may be {@code null}
* @param libraryPath:the list of directories containing native libraries, delimited by {@code File.pathSeparator}; may be{@code null}
* @param parent:the parent class loader
*/
/*
dexPath:
指目标类所在的APK或jar文件的路径, 类装载器将从该路径中寻找指定的目标类, 该类必须是APK或jar的全路径.
如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得。
上面”支持加载APK、DEX和JAR,也可以从SD卡进行加载”指的就是这个路径,
最终做的是将dexPath路径上的文件ODEX优化到内部位置optimizedDirectory,然后再进行加载的。
optimizedDirectory: (必须是一个内部存储路径,是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,直接使用dex文件原有的路径来创建DexFile对象)
由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex文件存放的路径。
这也是对apk中dex根据平台进行ODEX优化的过程。其实APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件,
只有第一次会解压执行程序到 /data/dalvik-cache(针对PathClassLoader)或者optimizedDirectory(针对DexClassLoader)目录,之后是直接读取目录下的的dex文件。
libPath:
指目标类中所使用的C/C++库存放的路径
classload:
是指该装载器的父装载器,一般为当前执行类的装载器,例如在Android中以context.getClassLoader()作为父装载器
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
//BaseDexClassLoder的构造函数使用入参的三个路径构造了一个DexPathList对象.
// DexPathList -> Element(makeDexElements()) -> DexFile(loadDexFile()->loadDex()->DexFile()->openDexFile())
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
//使用DexPathList的findClass()函数,返回找到的.
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
//more code
}
//DexClassLoader还需要指定一个生成优化后的apk的路径optimizedDirectory。而PathClassLoader则不需要,因为在安装阶段已经生成了/data/dalvik-cache/xxx@classes.dex
//可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等
public class DexClassLoader extends BaseDexClassLoader { //可以加载jar/apk/dex,可以从SD卡中加载未安装的apk
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
//直接调用BaseDexClassLoader的构造函数
}
}
//PathClassLoader的super调用中,optimizedDirectory一直为null.
(也就是没设置优化后的存放路径optimizedDirectory,实际上optimizedDirectory为null时的默认路径就是/data/dalvik-cache目录,PathClassLoader是用来加载Android系统类和应用的类,不建议开发者直接使用)
public class PathClassLoader extends BaseDexClassLoader { //只能加载系统中已经安装过的apk
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
//直接调用BaseDexClassLoader的构造函数
}
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
3、
final class DexPathList {
/** list of dex/resource (class path) elements */
private final Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
// some error checking
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory); //传入的dexPath、optimizedDirectory路径转化为Element数组
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
public Class findClass(String name) {
for (Element element : dexElements) { //遍历dexElements中的DexFile来加载Class
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
static class Element {
public final File file;
public final ZipFile zipFile;
public final DexFile dexFile;
}
}
//Element数组的生成过程
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) { // 文件后缀为.dex
try {
dex = loadDexFile(file, optimizedDirectory); //1、 loadDexFile()来生成dex对象
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) || name.endsWith(ZIP_SUFFIX)) {// 文件后缀为apk,jar,zip
try {
zip = new ZipFile(file); //构造zip对象
} catch (IOException ex) {
System.logE("Unable to open zip file: " + file, ex);
}
try {
dex = loadDexFile(file, optimizedDirectory); //2、loadDexFile()来生成dex对象
} catch (IOException ignored) {
// 如果压缩文件中没有dex文件,抛出这个异常,可以直接无视
}
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) { //如果同时有zip和dex文件,就构造对应的Element
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
typedef struct DexFile {
/* directly-mapped "opt" header */
const DexOptHeader* pOptHeader;
/* pointers to directly-mapped structs and arrays in base DEX */
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
/*
* These are mapped out of the "auxillary" section, and may not be
* included in the file.
*/
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
/* points to start of DEX file data */
const u1* baseAddr;
/* track memory overhead for auxillary structures */
int overhead;
/* additional app-specific data structures associated with the DEX */
//void* auxData;
} DexFile;
//loadDexFile()调用loadDex()创建 DexFile
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory); //根据optimizedDirectory是否为null调用不同的方法来构造DexFile
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
*/
private static String optimizedPathFor(File path, File optimizedDirectory) {
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
//loadDex()实例化DexFile类
static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app decided to open it multiple times. In practice this may not be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags);
}
//DexFile类调用openDexFile()
private DexFile(String sourceName, String outputName, int flags) throws IOException {
String wantDex = System.getProperty("android.vm.dexfile", "false");
if (!wantDex.equals("true"))
throw new UnsupportedOperationException("No dex in this VM");
mCookie = openDexFile(sourceName, outputName, flags); // 调用openDexFile() (native函数)
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie);
}
// java code
native private static int openDexFile(String sourceName, String outputName, int flags) throws IOException; // outputName就是optimizedDirectory,PathClassLoader和DexClassLoader的区别是optimizedDirectory是否为null,对应openDexFile()中outputName是否为null
// native code
static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args, JValue* pResult) //加载dex文件的过程在dvmRawDexFileOpen()和dvmJarFileOpen()方法中完成
{
......
if (dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
LOGV("Opening DEX file '%s' (DEX)\n", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
LOGV("Opening DEX file '%s' (Jar)\n", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
} else {
LOGV("Unable to open DEX file '%s'\n", sourceName);
dvmThrowException("Ljava/io/IOException;", "unable to open DEX file");
}
......
RETURN_PTR(pDexOrJar);
}