public class PathClassLoader extends BaseDexClassLoader {
37 public PathClassLoader(String dexPath, ClassLoader parent) {
38 super(dexPath, null, null, parent);
39 }
40
63 public PathClassLoader(String dexPath, String libraryPath,
64 ClassLoader parent) {
65 super(dexPath, null, libraryPath, parent);
66 }
67}
68
DexClassLoader源码以下:
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
209 /*
210 * Open all files and load the (direct or contained) dex files
211 * up front.
212 */
213 for (File file : files) {//遍历所有的dex文件
214 File zip = null;
215 DexFile dex = null;//这是核心的类,处理将dex文件转化成对应的DexFile对象
216 String name = file.getName();
217
218 if (name.endsWith(DEX_SUFFIX)) {//.dex结尾(针对PathClassLoader处理)
219 // Raw dex file (not inside a zip/jar).
220 try {
221 dex = loadDexFile(file, optimizedDirectory);//核心方法,内部是调用的DexFile的loadDex()方法
222 } catch (IOException ex) {
223 System.logE("Unable to load dex file: " + file, ex);
224 }
225 } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
226 || name.endsWith(ZIP_SUFFIX)) {//.dex .jar .apk 结尾 (针对DexClassLoader处理)
227 zip = file;
228
229 try {
230 dex = loadDexFile(file, optimizedDirectory);
231 } catch (IOException suppressed) {
232 /*
233 * IOException might get thrown "legitimately" by the DexFile constructor if the
234 * zip file turns out to be resource-only (that is, no classes.dex file in it).
235 * Let dex == null and hang on to the exception to add to the tea-leaves for
236 * when findClass returns null.
237 */
238 suppressedExceptions.add(suppressed);
239 }
240 } else if (file.isDirectory()) {
241 // We support directories for looking up resources.
242 // This is only useful for running libcore tests.
243 elements.add(new Element(file, true, null, null));//创建Element对象
244 } else {
245 System.logW("Unknown file type for: " + file);
246 }
247
248 if ((zip != null) || (dex != null)) {
249 elements.add(new Element(file, false, zip, dex));
250 }
251 }
252
253 return elements.toArray(new Element[elements.size()]);
254 }
3.Context的getClassLoader()为何返回是PathClassLoader,探索其中奥秘
我们都知道getApplicationContext()的返回的实现类是ContextImpl,下面来看看ContextImpl的getClassLoader()的代码实现:
public ClassLoader getClassLoader() {
return mPackageInfo != null ?
mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}
我们看到如果mPackageInfo不为null,就调用它的getClassLoader()方法,否则调用ClassLoader.getSystemClassLoader(),这里我们看到了ClassLoader的getSystemClassLoader()方法,但是这里还不是重点,重点是mPackageInfo这个对象,这是甚么呢,它是1个LoadedAPK对象。它又是甚么呢?
官方文档说明以下:
Local state maintained about a currently loaded .apk.
LoaderAPK对象是apk在内存中的表示。通过这个LoaderApk对象可以拿到apk中代码和资源,乃至里面Activity和Service等信息。
那末它又是哪里创建的,又是甚么时候创建的呢,如果你了解Activity的启动进程,你就明白了(如果想了解,推荐老罗的文章),这里就不赘述了。在ActivityThread里面有1个mPackages的map类型的成员变量,根据键值(packageName)存储对应的LoadedApk对象。启动Activity的时候要调用LoadedApk的getClassLoader(),来加载对应的Activity class文件,然后通过反射创建这个activity的实例;那末获得这个对象,会先去mPackages中去查找有无缓存,如果没有就创建1个新的LoadedAPK对象。
下面代码截取自ActivityThread中启动Activity的进程中创建LoadedApk的代码:
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// 获得userid信息
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
// 尝试获得缓存信息
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 缓存没有命中,直接new
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 省略。。更新缓存
return packageInfo;
}
}
下面看上面使用到的LoadedApk的构造函数,其实LoadedApk还有1个构造函数,在ContextImpl创建自己的实例的同时创建其LoadedApk的成员变量的时候使用了。
108 public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
109 CompatibilityInfo compatInfo,
110 ActivityThread mainThread, ClassLoader baseLoader,
111 boolean securityViolation, boolean includeCode) {
<span style="white-space:pre"> </span> .......(省略)
126
127 if (mAppDir == null) {
128 if (ActivityThread.mSystemContext == null) {//这个context很重要,1个ActivityThread只有这1个,是静态全局的
129 ActivityThread.mSystemContext =
130 ContextImpl.createSystemContext(mainThread);
131 ActivityThread.mSystemContext.getResources().updateConfiguration(
132 mainThread.getConfiguration(),
133 mainThread.getDisplayMetricsLocked(compatInfo, false),
134 compatInfo);
135 //Slog.i(TAG, "Created system resources "
136 // + mSystemContext.getResources() + ": "
137 // + mSystemContext.getResources().getConfiguration());
138 }
139 mClassLoader = ActivityThread.mSystemContext.getClassLoader();//这个ClassLoader就是最后返回的那个ClassLoader
140 mResources = ActivityThread.mSystemContext.getResources();
141 }
142 }
143
看到这里,我们只要终究这个CLassLoader的来源是从Context那里后去的,也就是ActivityThread的mSystemContext里面的ClassLoader,我们来看1下这个mSystemContext的创建进程,代码以下:
1458 static ContextImpl createSystemContext(ActivityThread mainThread) {
1459 ContextImpl context = new ContextImpl();
1460 context.init(Resources.getSystem(), mainThread);//这个init操作也没有创建他里面LoadedApk成员变量
1461 return context;
1462 }
所以终究调用的代码,就是最开始的ContextImpl的getClassLoader()方法,并且mPackageInfo(LoadedAPK对象)为null,所以终究调用的是ClassLoader.getSystemClassLoader(),所以终究结论就是系统ClassLoader是通过ClassLoader.getSystemClassLoader()创建。
4.揭开ClassLoader.getSystemClassLoader()在Android中的神秘面纱
其代码以下:
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
return new PathClassLoader(classPath, BootClassLoader.getInstance());
}
对最有1个核心方法createSystemClassLoader(),官方说明以下:
Create the system class loader. Note this is NOT the bootstrap class
loader (which is managed by the VM). We use a null value for the parent
to indicate that the bootstrap loader is our parent.
创建系统的ClassLoader。注释:这不是bootstrap ClassLoader(被虚拟机管理的ClassLoader)。我们使用null作为我们系统ClassLoader的parent来表明bootstrap就是我们的系统ClassLoader的parent。这里也就是充分辩明了Android系统不是使用的java原生的bootstrap来加载,而是使用自己的创建的套机制。取代bootstrap的是用null(bootstrap不是1个ClassLoader对象,所以他的子级ClassLoader调用getParent()返回的是null),而取代java中第2级的ClassLoader使用Android中创建的最基层的BootClassLoader(也就是上面的PathClassLoader的parent)。
这个BootClassLoader是单例的,所以全局只有1个,我们也能够得出,系统所有履行装载类的操作,都会履行到这个对象。代码以下:
public BootClassLoader() {
super(null, true);//他的parent为null(摹拟它的parent是bootstrap)
}
还有我们看到上面PathClassLoader构造函数中传递的第1个参数classPath,这个就是我们之前打印出来的dexPath。为何系统的ClassLoader.getSystemClassLoader()返回的PathClassLoader 的dexPath指向的是当前apk的安装路径;但是我们自己创建的返回的PathClassLoader的dexPath却是. 我们发现是由上面的System.getProperty("java.class.path",".");决定的,看到get操作,相对的也就会有set操作,那末这个值是甚么时候设置的呢。这我们就要追溯到Android
framwork中System中的initSystemProperty()方法中,调用VMRuntime.getRuntime().classPath()的值,这个值应当就是当前apk的安装路径。
代码以下: 路径:/libcore/luni/src/main/java/java/lang/System.java(4.0.4)
private static void initSystemProperties() {
VMRuntime runtime = VMRuntime.getRuntime();
Properties p = new Properties();
String projectUrl = "http://www.android.com/";
String projectName = "The Android Project";
p.put("java.boot.class.path", runtime.bootClassPath());
p.put("java.class.path", runtime.classPath());
}
但是,为何我们自己调用,获得这个值就是. 我的料想就是 系统创建完对应的ClassLoader以后,就将这个值修改成了.(固然这仅仅是我个人的料想,希望了解的大神能帮我解答1下,由于我实在是找不到缘由,困惑好久了)。
5.总结
1.Android系统最顶级的ClassLoader是BootClassLoader(替换java中第2级的ext ClassLoader),而用来加载系统的类是使用的以这儿BootClassLoader为parent的PathClassLoader(替换java中第3级加载classpath的ClassLoader)。
2.Android系统的PathClassLoader的dexPath(要加载类的路径),指向当前apk安装的路径,然后使用DexFile来解析对应的dex文件,装载里面的class
3.DexClassLoader,主要用于加载外部插件,也就是可以直接加载1个apk文件,现在的插件化动态加载机制,热修复等都要使用到它的特性,固然直接使用里面的DexFile直接加载dex文件也是可以(AndFix就是这样做的)。
4.自己遇到的坑:我之前在学习动态化的时候,看完原理以后,自己创建1个DexClassLoader ,它的parent我设置的是系统PathClassLoader(也就是context.getClassLoader()),但是load外部apk中的类的时候,报错:当前apk的安装路径下(也就是这个apk文件对应生成的ODEX文件)找不到对应的类,也就是ClassNotFoundException,但是自己不明白为何我用DexClassLoader加载的外部apk,但是它却到系统中去寻觅。现在明白了由于我传递的是PathClassLoader的dexPath是指向系统的apk的路径的,自然会到那里去找。但是如果传递的是ClassLoader.getSystemClassLoader(),由于dexPath是.
,是Directory,就不会创建DexFile,也就不会走DexFile的loadDexFile()方法,而是直接调用它的parent也就是BootClassLoader的findClass()来装载当前类。
文章终究写完了,鄙人愚钝,水平有限,文章难免有错,欢迎指出。