DVM还是ART它们加载的不再是Class文件,而是dex文件,所以Java的类加载器不再适用
系统ClassLoader包括三种分别是BootClassLoader、PathClassLoader 和 DexClassLoader。
BootClassLoader:
由Java实现,是ClassLoader的内部类,并继承自ClassLoader
BootClassLoader是一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用
的。
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
}
DexClassLoader
DexClassLoader可以加载dex文件以及包含dex的.jar.zip
文件和apk文件
,主要针对的是未安装的类
;插件化和动态加载都是基于DexClassLoader来实现的
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
PathClassLoader:
加载系统类和应用程序的类,无法定义解压的dex文件存储路径(相当于把第二个参数固定为null的DexClassLoader)通常用来加载已安装的apk的dex
文件
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
Android加载器类图:
BaseDexClassLoader源码:
1.加载解析所有的类和库文件
BaseDexClassLoader的构造方法:
//dexPath:要加载的文件路径,可能是多个dex问文件的路径,多个路径间用:隔开
//optimizedDirectory:优化后的dex文件的路径
//librarySearchPath:库文件的搜索路径,一般是.so库文件
//parent:父加载器
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath,ClassLoader parent) {
//连接父加载器
super(parent);
//创建一个DexPathList对象
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
DexPathList对象的构造过程:
class DexPathList {
private ClassLoader definingContext;
private Element[] dexElements;
private Element[] nativeLibraryPathElements; //本地方法库
//构造方法主要做了这么三件事:
//1.把dexPath拆分成多个路径,并把这些路径解析成一个Element数组
//2.把系统本地方法库路径拆分并解析成一个List<File>
//3.把库路径librarySearchPath拆分成多个路径,并把这些路径解析成一个Element数组
public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) {
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
suppressedExceptions,definingContext);
}
}
Dalvik 利用 DexPathList 来查找并加载类,DexPathList的构造方法主要目的是根据几个参数构造两个 Element[]
//把系统本地方法库解析成一个FileList
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
List<File> result = new ArrayList<>();
if (searchPath != null) {
for (String path : searchPath.split(File.pathSeparator)) {
//...
result.add(new File(path));
}
}
return result;
}
//作用是把dexPath列表转化成Element列表
private static Element[] makeElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions,
boolean ignoreDexFiles,
ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
for (File file : files) { //把每个dexPathFile转化成Element
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
if (path.contains(zipSeparator)) { //dexPath对应一个压缩文件
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) { //dexPath对应一个目录
elements[elementsPos++] = new Element(file, true, null, null);
} else if (file.isFile()) { //dexPath对应一个普通文件
if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
// dexfile不在zip/jar文件中
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} else {
//dexfile在zip/jar文件中
zip = file;
dex = loadDexFile(file, optimizedDirectory, loader, elements);
}
}
if ((zip != null) || (dex != null)) { //构造一个Element对象
elements[elementsPos++] = new Element(dir, false, zip, dex);
}
}
return elements;
}
总结:BaseDexClassLoader在构造方法中把路径中给出的文件解析成了Element数组
2.BaseDexClassLoader如何根据类名寻找并加载特定的类型:
BaseDexClassLoader类加载器通过findClass找到所需要的类:
//class = BaseDexClassLoader
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
//空校验略
return c;
}
BaseDexClassLoader寻找类的过程委托给DexPathList对象:
//class = DexPathList
public Class findClass(String name, List<Throwable> suppressed) {
//遍历Element数组,通过在这个数组头部插入其他Element对象可以改变类的解析结果,这是热修复的实现方法之一
for (Element element : dexElements) {
DexFile dex = element.dexFile;
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
return null;
}
DexPathList又把寻找类的任务委托给DexFile:
//class = DexFile
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements) throws IOException {
String parent = new File(outputName).getParent();
//把dex文件加载到内存中
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
}
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
//最后寻找类是交给native层完成的
Class result = defineClassNative(name, loader, cookie, dexFile);
return result;
}
如何动态加载Jar文件:
使用DexClassLoader方式加载类:
//dex压缩文件的路径(可以是apk,jar,zip格式)
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";
//dex解压释放后的目录
String dexOutputDirs = Environment.getExternalStorageDirectory().toString();
//定义DexClassLoader
//第一个参数:是dex压缩文件的路径
//第二个参数:是dex解压缩后存放的目录
//第三个参数:是C/C++依赖的本地库文件目录,可以为null
//第四个参数:是上一级的类加载器
DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());
try {
//使用DexClassLoader加载类
Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
lib = (IDynamic)libProviderClazz.newInstance();
} catch (Exception exception) {
exception.printStackTrace();
}
使用PathClassLoader方法加载类:
//创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>
Intent intent = new Intent("com.dynamic.impl", null);
//获得包管理器
PackageManager pm = getPackageManager();
List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
//获得指定的activity的信息
ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
//获得apk的目录或者jar的目录
String apkPath = actInfo.applicationInfo.sourceDir;
//native代码的目录
String libPath = actInfo.applicationInfo.nativeLibraryDir;
//创建类加载器,把dex加载到虚拟机中
//第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取
//第二个参数:是C/C++依赖的本地库文件目录,可以为null
//第三个参数:是上一级的类加载器
PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());
try {
//Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
//使用PathClassLoader加载类
Class libProviderClazz = pcl.loadClass("com.dynamic.impl.Dynamic");
lib = (IDynamic)libProviderClazz.newInstance();
} catch (Exception exception) {
exception.printStackTrace();
}
总结:
DexClassLoader和PathClassLoader实际上只有构造方法,加载的过程都是完全使用BaseDexClassLoader的逻辑,BaseDexClassLoader加载类实际上是委托给DexFile的