我简单翻译分析下,如有错误请指正,谢谢!
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
/*作为classLoder实现类的一个拥有公共功能的基类,也就是说其他类可以继承这个类*/
public class BaseDexClassLoader extends ClassLoader {
/*什么是DexPathList呢?解释如下:*/
/**
* A pair of lists of entries, associated with a {@code ClassLoader}.
* One of the lists is a dex/resource path — typically referred
* to as a "class path" — list, and the other names directories
* containing native code libraries. Class path entries may be any of:
* a {@code .jar} or {@code .zip} file containing an optional
* top-level {@code classes.dex} file as well as arbitrary resources,
* or a plain {@code .dex} file (with no possibility of associated
* resources).
*
* <p>This class also contains methods to use these lists look up
* classes and resources.</p>
*/
/*这是关联一个ClassLoder 的条目列表,这其中有dex/resource path
通常称为“class path” ,其他名称目录包含本地代码库,class path条目
可能是包含一个可选的顶级class.dex的zip文件,jar文件,以及任意资源
*/
private final DexPathList pathList;
/**
* 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
*/
/*第一个参数是jar包或apk的路径,
第二个参数是将这些jar包或apk优化后成为dex的存储路径,
第三个参数是本地库的路径,
第四个参数是父装载器,父装载器有什么用呢?当我们loadClass的时候,会优先用父装载器去loadclass*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
/*重写了ClassLoder的这个方法,这个方法会在loadClass中调用,当loadClass时,具体步骤是这样的:
先查找已经加载的类,如果有返回,如果没有,用父装载器去加载这个类,如果失败,则会调用我们重写的findclass这个方法*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) {
return pathList.findResources(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
/**
* Returns package information for the given package.
* Unfortunately, instances of this class don't really have this
* information, and as a non-secure {@code ClassLoader}, it isn't
* even required to, according to the spec. Yet, we want to
* provide it, in order to make all those hopeful callers of
* {@code myClass.getPackage().getName()} happy. Thus we construct
* a {@code Package} object the first time it is being requested
* and fill most of the fields with dummy values. The {@code
* Package} object is then put into the {@code ClassLoader}'s
* package cache, so we see the same one next time. We don't
* create {@code Package} objects for {@code null} arguments or
* for the default package.
*
* <p>There is a limited chance that we end up with multiple
* {@code Package} objects representing the same package: It can
* happen when when a package is scattered across different JAR
* files which were loaded by different {@code ClassLoader}
* instances. This is rather unlikely, and given that this whole
* thing is more or less a workaround, probably not worth the
* effort to address.
*
* @param name the name of the class
* @return the package information for the class, or {@code null}
* if there is no package information available for it
*/
@Override
protected synchronized Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
Package pack = super.getPackage(name);
if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown",
"Unknown", "0.0", "Unknown", null);
}
return pack;
}
return null;
}
/**
* @hide
*/
public String getLdLibraryPath() {
StringBuilder result = new StringBuilder();
for (File directory : pathList.getNativeLibraryDirectories()) {
if (result.length() > 0) {
result.append(':');
}
result.append(directory);
}
return result.toString();
}
@Override public String toString() {
return getClass().getName() + "[" + pathList + "]";
}
}
附上DexPathList源码:
/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package dalvik.system;
18
19import java.io.File;
20import java.io.IOException;
21import java.net.MalformedURLException;
22import java.net.URL;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collections;
26import java.util.Enumeration;
27import java.util.List;
28import java.util.zip.ZipFile;
29import libcore.io.ErrnoException;
30import libcore.io.IoUtils;
31import libcore.io.Libcore;
32import libcore.io.StructStat;
33import static libcore.io.OsConstants.*;
34
35/**
36 * A pair of lists of entries, associated with a {@code ClassLoader}.
37 * One of the lists is a dex/resource path — typically referred
38 * to as a "class path" — list, and the other names directories
39 * containing native code libraries. Class path entries may be any of:
40 * a {@code .jar} or {@code .zip} file containing an optional
41 * top-level {@code classes.dex} file as well as arbitrary resources,
42 * or a plain {@code .dex} file (with no possibility of associated
43 * resources).
44 *
45 * <p>This class also contains methods to use these lists to look up
46 * classes and resources.</p>
47 */
48/*package*/ final class DexPathList {
49 private static final String DEX_SUFFIX = ".dex";
50 private static final String JAR_SUFFIX = ".jar";
51 private static final String ZIP_SUFFIX = ".zip";
52 private static final String APK_SUFFIX = ".apk";
53
54 /** class definition context */
55 private final ClassLoader definingContext;
56
57 /**
58 * List of dex/resource (class path) elements.
59 * Should be called pathElements, but the Facebook app uses reflection
60 * to modify 'dexElements' (http://b/7726934).
61 */
62 private final Element[] dexElements;
63
64 /** List of native library directories. */
65 private final File[] nativeLibraryDirectories;
66
67 /**
68 * Exceptions thrown during creation of the dexElements list.
69 */
70 private final IOException[] dexElementsSuppressedExceptions;
71
72 /**
73 * Constructs an instance.
74 *
75 * @param definingContext the context in which any as-yet unresolved
76 * classes should be defined
77 * @param dexPath list of dex/resource path elements, separated by
78 * {@code File.pathSeparator}
79 * @param libraryPath list of native library directory path elements,
80 * separated by {@code File.pathSeparator}
81 * @param optimizedDirectory directory where optimized {@code .dex} files
82 * should be found and written to, or {@code null} to use the default
83 * system directory for same
84 */
85 public DexPathList(ClassLoader definingContext, String dexPath,
86 String libraryPath, File optimizedDirectory) {
87 if (definingContext == null) {
88 throw new NullPointerException("definingContext == null");
89 }
90
91 if (dexPath == null) {
92 throw new NullPointerException("dexPath == null");
93 }
94
95 if (optimizedDirectory != null) {
96 if (!optimizedDirectory.exists()) {
97 throw new IllegalArgumentException(
98 "optimizedDirectory doesn't exist: "
99 + optimizedDirectory);
100 }
101
102 if (!(optimizedDirectory.canRead()
103 && optimizedDirectory.canWrite())) {
104 throw new IllegalArgumentException(
105 "optimizedDirectory not readable/writable: "
106 + optimizedDirectory);
107 }
108 }
109
110 this.definingContext = definingContext;
111 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
112 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
113 suppressedExceptions);
114 if (suppressedExceptions.size() > 0) {
115 this.dexElementsSuppressedExceptions =
116 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
117 } else {
118 dexElementsSuppressedExceptions = null;
119 }
120 this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
121 }
122
123 @Override public String toString() {
124 return "DexPathList[" + Arrays.toString(dexElements) +
125 ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]";
126 }
127
128 /**
129 * For BaseDexClassLoader.getLdLibraryPath.
130 */
131 public File[] getNativeLibraryDirectories() {
132 return nativeLibraryDirectories;
133 }
134
135 /**
136 * Splits the given dex path string into elements using the path
137 * separator, pruning out any elements that do not refer to existing
138 * and readable files. (That is, directories are not included in the
139 * result.)
140 */
141 private static ArrayList<File> splitDexPath(String path) {
142 return splitPaths(path, null, false);
143 }
144
145 /**
146 * Splits the given library directory path string into elements
147 * using the path separator ({@code File.pathSeparator}, which
148 * defaults to {@code ":"} on Android, appending on the elements
149 * from the system library path, and pruning out any elements that
150 * do not refer to existing and readable directories.
151 */
152 private static File[] splitLibraryPath(String path) {
153 // Native libraries may exist in both the system and
154 // application library paths, and we use this search order:
155 //
156 // 1. this class loader's library path for application libraries
157 // 2. the VM's library path from the system property for system libraries
158 //
159 // This order was reversed prior to Gingerbread; see http://b/2933456.
160 ArrayList<File> result = splitPaths(path, System.getProperty("java.library.path"), true);
161 return result.toArray(new File[result.size()]);
162 }
163
164 /**
165 * Splits the given path strings into file elements using the path
166 * separator, combining the results and filtering out elements
167 * that don't exist, aren't readable, or aren't either a regular
168 * file or a directory (as specified). Either string may be empty
169 * or {@code null}, in which case it is ignored. If both strings
170 * are empty or {@code null}, or all elements get pruned out, then
171 * this returns a zero-element list.
172 */
173 private static ArrayList<File> splitPaths(String path1, String path2,
174 boolean wantDirectories) {
175 ArrayList<File> result = new ArrayList<File>();
176
177 splitAndAdd(path1, wantDirectories, result);
178 splitAndAdd(path2, wantDirectories, result);
179 return result;
180 }
181
182 /**
183 * Helper for {@link #splitPaths}, which does the actual splitting
184 * and filtering and adding to a result.
185 */
186 private static void splitAndAdd(String searchPath, boolean directoriesOnly,
187 ArrayList<File> resultList) {
188 if (searchPath == null) {
189 return;
190 }
191 for (String path : searchPath.split(":")) {
192 try {
193 StructStat sb = Libcore.os.stat(path);
194 if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
195 resultList.add(new File(path));
196 }
197 } catch (ErrnoException ignored) {
198 }
199 }
200 }
201
202 /**
203 * Makes an array of dex/resource path elements, one per element of
204 * the given array.
205 */
206 private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
207 ArrayList<IOException> suppressedExceptions) {
208 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) {
214 File zip = null;
215 DexFile dex = null;
216 String name = file.getName();
217
218 if (name.endsWith(DEX_SUFFIX)) {
219 // Raw dex file (not inside a zip/jar).
220 try {
221 dex = loadDexFile(file, optimizedDirectory);
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)) {
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));
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 }
255
256 /**
257 * Constructs a {@code DexFile} instance, as appropriate depending
258 * on whether {@code optimizedDirectory} is {@code null}.
259 */
260 private static DexFile loadDexFile(File file, File optimizedDirectory)
261 throws IOException {
262 if (optimizedDirectory == null) {
263 return new DexFile(file);
264 } else {
265 String optimizedPath = optimizedPathFor(file, optimizedDirectory);
266 return DexFile.loadDex(file.getPath(), optimizedPath, 0);
267 }
268 }
269
270 /**
271 * Converts a dex/jar file path and an output directory to an
272 * output file path for an associated optimized dex file.
273 */
274 private static String optimizedPathFor(File path,
275 File optimizedDirectory) {
276 /*
277 * Get the filename component of the path, and replace the
278 * suffix with ".dex" if that's not already the suffix.
279 *
280 * We don't want to use ".odex", because the build system uses
281 * that for files that are paired with resource-only jar
282 * files. If the VM can assume that there's no classes.dex in
283 * the matching jar, it doesn't need to open the jar to check
284 * for updated dependencies, providing a slight performance
285 * boost at startup. The use of ".dex" here matches the use on
286 * files in /data/dalvik-cache.
287 */
288 String fileName = path.getName();
289 if (!fileName.endsWith(DEX_SUFFIX)) {
290 int lastDot = fileName.lastIndexOf(".");
291 if (lastDot < 0) {
292 fileName += DEX_SUFFIX;
293 } else {
294 StringBuilder sb = new StringBuilder(lastDot + 4);
295 sb.append(fileName, 0, lastDot);
296 sb.append(DEX_SUFFIX);
297 fileName = sb.toString();
298 }
299 }
300
301 File result = new File(optimizedDirectory, fileName);
302 return result.getPath();
303 }
304
305 /**
306 * Finds the named class in one of the dex files pointed at by
307 * this instance. This will find the one in the earliest listed
308 * path element. If the class is found but has not yet been
309 * defined, then this method will define it in the defining
310 * context that this instance was constructed with.
311 *
312 * @param name of class to find
313 * @param suppressed exceptions encountered whilst finding the class
314 * @return the named class or {@code null} if the class is not
315 * found in any of the dex files
316 */
317 public Class findClass(String name, List<Throwable> suppressed) {
318 for (Element element : dexElements) {
319 DexFile dex = element.dexFile;
320
321 if (dex != null) {
322 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
323 if (clazz != null) {
324 return clazz;
325 }
326 }
327 }
328 if (dexElementsSuppressedExceptions != null) {
329 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
330 }
331 return null;
332 }
333
334 /**
335 * Finds the named resource in one of the zip/jar files pointed at
336 * by this instance. This will find the one in the earliest listed
337 * path element.
338 *
339 * @return a URL to the named resource or {@code null} if the
340 * resource is not found in any of the zip/jar files
341 */
342 public URL findResource(String name) {
343 for (Element element : dexElements) {
344 URL url = element.findResource(name);
345 if (url != null) {
346 return url;
347 }
348 }
349
350 return null;
351 }
352
353 /**
354 * Finds all the resources with the given name, returning an
355 * enumeration of them. If there are no resources with the given
356 * name, then this method returns an empty enumeration.
357 */
358 public Enumeration<URL> findResources(String name) {
359 ArrayList<URL> result = new ArrayList<URL>();
360
361 for (Element element : dexElements) {
362 URL url = element.findResource(name);
363 if (url != null) {
364 result.add(url);
365 }
366 }
367
368 return Collections.enumeration(result);
369 }
370
371 /**
372 * Finds the named native code library on any of the library
373 * directories pointed at by this instance. This will find the
374 * one in the earliest listed directory, ignoring any that are not
375 * readable regular files.
376 *
377 * @return the complete path to the library or {@code null} if no
378 * library was found
379 */
380 public String findLibrary(String libraryName) {
381 String fileName = System.mapLibraryName(libraryName);
382 for (File directory : nativeLibraryDirectories) {
383 String path = new File(directory, fileName).getPath();
384 if (IoUtils.canOpenReadOnly(path)) {
385 return path;
386 }
387 }
388 return null;
389 }
390
391 /**
392 * Element of the dex/resource file path
393 */
394 /*package*/ static class Element {
395 private final File file;
396 private final boolean isDirectory;
397 private final File zip;
398 private final DexFile dexFile;
399
400 private ZipFile zipFile;
401 private boolean initialized;
402
403 public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
404 this.file = file;
405 this.isDirectory = isDirectory;
406 this.zip = zip;
407 this.dexFile = dexFile;
408 }
409
410 @Override public String toString() {
411 if (isDirectory) {
412 return "directory \"" + file + "\"";
413 } else if (zip != null) {
414 return "zip file \"" + zip + "\"";
415 } else {
416 return "dex file \"" + dexFile + "\"";
417 }
418 }
419
420 public synchronized void maybeInit() {
421 if (initialized) {
422 return;
423 }
424
425 initialized = true;
426
427 if (isDirectory || zip == null) {
428 return;
429 }
430
431 try {
432 zipFile = new ZipFile(zip);
433 } catch (IOException ioe) {
434 /*
435 * Note: ZipException (a subclass of IOException)
436 * might get thrown by the ZipFile constructor
437 * (e.g. if the file isn't actually a zip/jar
438 * file).
439 */
440 System.logE("Unable to open zip file: " + file, ioe);
441 zipFile = null;
442 }
443 }
444
445 public URL findResource(String name) {
446 maybeInit();
447
448 // We support directories so we can run tests and/or legacy code
449 // that uses Class.getResource.
450 if (isDirectory) {
451 File resourceFile = new File(file, name);
452 if (resourceFile.exists()) {
453 try {
454 return resourceFile.toURI().toURL();
455 } catch (MalformedURLException ex) {
456 throw new RuntimeException(ex);
457 }
458 }
459 }
460
461 if (zipFile == null || zipFile.getEntry(name) == null) {
462 /*
463 * Either this element has no zip/jar file (first
464 * clause), or the zip/jar file doesn't have an entry
465 * for the given name (second clause).
466 */
467 return null;
468 }
469
470 try {
471 /*
472 * File.toURL() is compliant with RFC 1738 in
473 * always creating absolute path names. If we
474 * construct the URL by concatenating strings, we
475 * might end up with illegal URLs for relative
476 * names.
477 */
478 return new URL("jar:" + file.toURL() + "!/" + name);
479 } catch (MalformedURLException ex) {
480 throw new RuntimeException(ex);
481 }
482 }
483 }
484}
485