[原创]Android之DexClassLoader加载class源码解析

写DexClassLoader加载class主要是为写Android插件化做准备:

我们在看DexClassLoader加载class之前,先大概了解下ClassLoader

ClassLoader源码:http://www.cnblogs.com/kangqi001/p/8318113.html

我们都知道Android中Class的加载时执行的

ClassLoader.loadClass(String name)方法()

这里我们看下ClassLoader的loadClass方法:

468    public Class<?> loadClass(String className) throws ClassNotFoundException {
469        return loadClass(className, false);
470    }
498    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
499        Class<?> clazz = findLoadedClass(className);//查看类是否以前被加载个
500
501        if (clazz == null) {
502            ClassNotFoundException suppressed = null;
503            try {
504                clazz = parent.loadClass(className, false);//先调用父类加载器去加载,可有效加载相关系统类(这个是Android插件化的一个重要机制)
505            } catch (ClassNotFoundException e) {
506                suppressed = e;
507            }
508
509            if (clazz == null) {
510                try {
511                    clazz = findClass(className);//父类加载没有找到类 就调用自己的findClass机制
512                } catch (ClassNotFoundException e) {
513                    e.addSuppressed(suppressed);
514                    throw e;
515                }
516            }
517        }
518
519        return clazz;
520    }

这里主要查看它511行代码:直接调用的类本身findClass方法(上面是双亲委派机制)

DexClassLoader.java源码:

public class DexClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @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; must not 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
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

DexClassLoader构造方法参数说明:

dexPath:apk/dex/jar文件路径

optimizedDirectory:文件解压路径(这个路径下保存的是.dex文件不是.class)

libraryPath:加载时用到的so库(可能理解有问题)

parent:父加载器(这个比较重要与Android加载class的机制有关)

从DexClassLoader源码可以知道 DexClassLoader直接实现的是BaseDexClassLoader ,所以需要我们进一步去查看BaseDexClassLoader 的源码

 

BaseDexClassLoader 源码:

1/*
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.net.URL;
21import java.util.ArrayList;
22import java.util.Enumeration;
23import java.util.List;
24
25/**
26 * Base class for common functionality between various dex-based
27 * {@link ClassLoader} implementations.
28 */
29public class BaseDexClassLoader extends ClassLoader {
30    private final DexPathList pathList;
31
32    /**
33     * Constructs an instance.
34     *
35     * @param dexPath the list of jar/apk files containing classes and
36     * resources, delimited by {@code File.pathSeparator}, which
37     * defaults to {@code ":"} on Android
38     * @param optimizedDirectory directory where optimized dex files
39     * should be written; may be {@code null}
40     * @param libraryPath the list of directories containing native
41     * libraries, delimited by {@code File.pathSeparator}; may be
42     * {@code null}
43     * @param parent the parent class loader
44     */
45    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
46            String libraryPath, ClassLoader parent) {
47        super(parent);
48        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
49    }
50
51    @Override
52    protected Class<?> findClass(String name) throws ClassNotFoundException {
53        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
54        Class c = pathList.findClass(name, suppressedExceptions);
55        if (c == null) {
56            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
57            for (Throwable t : suppressedExceptions) {
58                cnfe.addSuppressed(t);
59            }
60            throw cnfe;
61        }
62        return c;
63    }
64
65    @Override
66    protected URL findResource(String name) {
67        return pathList.findResource(name);
68    }
69
70    @Override
71    protected Enumeration<URL> findResources(String name) {
72        return pathList.findResources(name);
73    }
74
75    @Override
76    public String findLibrary(String name) {
77        return pathList.findLibrary(name);
78    }
79
80    /**
81     * Returns package information for the given package.
82     * Unfortunately, instances of this class don't really have this
83     * information, and as a non-secure {@code ClassLoader}, it isn't
84     * even required to, according to the spec. Yet, we want to
85     * provide it, in order to make all those hopeful callers of
86     * {@code myClass.getPackage().getName()} happy. Thus we construct
87     * a {@code Package} object the first time it is being requested
88     * and fill most of the fields with dummy values. The {@code
89     * Package} object is then put into the {@code ClassLoader}'s
90     * package cache, so we see the same one next time. We don't
91     * create {@code Package} objects for {@code null} arguments or
92     * for the default package.
93     *
94     * <p>There is a limited chance that we end up with multiple
95     * {@code Package} objects representing the same package: It can
96     * happen when when a package is scattered across different JAR
97     * files which were loaded by different {@code ClassLoader}
98     * instances. This is rather unlikely, and given that this whole
99     * thing is more or less a workaround, probably not worth the
100     * effort to address.
101     *
102     * @param name the name of the class
103     * @return the package information for the class, or {@code null}
104     * if there is no package information available for it
105     */
106    @Override
107    protected synchronized Package getPackage(String name) {
108        if (name != null && !name.isEmpty()) {
109            Package pack = super.getPackage(name);
110
111            if (pack == null) {
112                pack = definePackage(name, "Unknown", "0.0", "Unknown",
113                        "Unknown", "0.0", "Unknown", null);
114            }
115
116            return pack;
117        }
118
119        return null;
120    }
121
122    /**
123     * @hide
124     */
125    public String getLdLibraryPath() {
126        StringBuilder result = new StringBuilder();
127        for (File directory : pathList.getNativeLibraryDirectories()) {
128            if (result.length() > 0) {
129                result.append(':');
130            }
131            result.append(directory);
132        }
133        return result.toString();
134    }
135
136    @Override public String toString() {
137        return getClass().getName() + "[" + pathList + "]";
138    }
139}
140

DexClassLoader的构造方法执行的是BaseDexClassLoader中的:

45    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
46            String libraryPath, ClassLoader parent) {
47        super(parent);
48        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
49    }

这里主要查看48行代码:

它去实例化一个DexPathList对象(重要)

BaseDexClassLoader重写了findClass方法

51    @Override
52    protected Class<?> findClass(String name) throws ClassNotFoundException {
53        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
54        Class c = pathList.findClass(name, suppressedExceptions);
55        if (c == null) {
56            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
57            for (Throwable t : suppressedExceptions) {
58                cnfe.addSuppressed(t);
59            }
60            throw cnfe;
61        }
62        return c;
63    }

 这里主要看54行代码:Class c = pathList.findClass(name, suppressedExceptions);

BaseDexClassLoader的findClass方法:

实际实现的是DexPathList的findClass方法

到这里我们知道了一个这样的流程:DexClassLoader去加载一个类:

DexClassLoader.loadClass(String className)---->实际执行的是ClassLoader.loadClass(String className)方法

ClassLoader.loadClass(String className)方法中 又调用了   BaseDexClassLoader.findClass(String className)方法(BaseDexClassLoader重写了findClass方法)

而在BaseDexClassLoader的findClass方法中又执行了DexPathList的findClass方法

方法执行流程:ClassLoader.loadClass --BaseDexClassLoader.findClass---DexPathList.findClass

 

小结:到这里我们知道了DexClassLoader加载类的 核心就是 DexPathList类

我们进一步去查看DexPathList的源码:

1/*
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 &mdash; typically referred
38 * to as a "class path" &mdash; 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}

这里我们先看它的构造方法:

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);//解压APK/jar添加dex到dexElements数组中去
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    }
参数说明:
参数一:BaseDexClassLoader本身
参数二:apk/dex/jar路径
参数三:系统的一些文件库
参数四:dex文件路径(参数二apk或jar解压输出文件库路径)

通过DexPathList的构造方法
112行通过makeDexelements(...)方法我们获取了一个 Element[] dexElements数组。保存了dex文件的相关信息

我们上面说到:DexClassLoader加载类实际调用的是DexPathList的findClass进行加载的。我们再来看下DexPathList的findClass方法
317    public Class findClass(String name, List<Throwable> suppressed) {
318        for (Element element : dexElements) {//这里的代码比较重要 有一种插件化的思想就是把修改过的dex进行插队 
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    }
重要思想:dex插队处理,比如A.dex中包含了一个a.class文件。我们通过插入一个B.dex在A.dex文件之前,
findClass方法在遍历DEXeLEMENTS文件会先在B.dex中查找,如果找到了a.class文件就会直接返回该a.class,如果没有找到才去B.dex中查找
言归正传:
从322行代码可知,类在dex中的加载 是执行的DexFile类的
loadClassBinaryName方法
我们就需要进一步查看DexFile源码:
public final class DexFile {
37    private int mCookie;
38    private final String mFileName;
39    private final CloseGuard guard = CloseGuard.get();
40
41    /**
42     * Opens a DEX file from a given File object. This will usually be a ZIP/JAR
43     * file with a "classes.dex" inside.
44     *
45     * The VM will generate the name of the corresponding file in
46     * /data/dalvik-cache and open it, possibly creating or updating
47     * it first if system permissions allow.  Don't pass in the name of
48     * a file in /data/dalvik-cache, as the named file is expected to be
49     * in its original (pre-dexopt) state.
50     *
51     * @param file
52     *            the File object referencing the actual DEX file
53     *
54     * @throws IOException
55     *             if an I/O error occurs, such as the file not being found or
56     *             access rights missing for opening it
57     */
58    public DexFile(File file) throws IOException {
59        this(file.getPath());
60    }
61
62    /**
63     * Opens a DEX file from a given filename. This will usually be a ZIP/JAR
64     * file with a "classes.dex" inside.
65     *
66     * The VM will generate the name of the corresponding file in
67     * /data/dalvik-cache and open it, possibly creating or updating
68     * it first if system permissions allow.  Don't pass in the name of
69     * a file in /data/dalvik-cache, as the named file is expected to be
70     * in its original (pre-dexopt) state.
71     *
72     * @param fileName
73     *            the filename of the DEX file
74     *
75     * @throws IOException
76     *             if an I/O error occurs, such as the file not being found or
77     *             access rights missing for opening it
78     */
79    public DexFile(String fileName) throws IOException {
80        mCookie = openDexFile(fileName, null, 0);
81        mFileName = fileName;
82        guard.open("close");
83        //System.out.println("DEX FILE cookie is " + mCookie);
84    }
85
86    /**
87     * Opens a DEX file from a given filename, using a specified file
88     * to hold the optimized data.
89     *
90     * @param sourceName
91     *  Jar or APK file with "classes.dex".
92     * @param outputName
93     *  File that will hold the optimized form of the DEX data.
94     * @param flags
95     *  Enable optional features.
96     */
97    private DexFile(String sourceName, String outputName, int flags) throws IOException {
98        if (outputName != null) {
99            try {
100                String parent = new File(outputName).getParent();
101                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
102                    throw new IllegalArgumentException("Optimized data directory " + parent
103                            + " is not owned by the current user. Shared storage cannot protect"
104                            + " your application from code injection attacks.");
105                }
106            } catch (ErrnoException ignored) {
107                // assume we'll fail with a more contextual error later
108            }
109        }
110
111        mCookie = openDexFile(sourceName, outputName, flags);
112        mFileName = sourceName;
113        guard.open("close");
114        //System.out.println("DEX FILE cookie is " + mCookie);
115    }
116
117    /**
118     * Open a DEX file, specifying the file in which the optimized DEX
119     * data should be written.  If the optimized form exists and appears
120     * to be current, it will be used; if not, the VM will attempt to
121     * regenerate it.
122     *
123     * This is intended for use by applications that wish to download
124     * and execute DEX files outside the usual application installation
125     * mechanism.  This function should not be called directly by an
126     * application; instead, use a class loader such as
127     * dalvik.system.DexClassLoader.
128     *
129     * @param sourcePathName
130     *  Jar or APK file with "classes.dex".  (May expand this to include
131     *  "raw DEX" in the future.)
132     * @param outputPathName
133     *  File that will hold the optimized form of the DEX data.
134     * @param flags
135     *  Enable optional features.  (Currently none defined.)
136     * @return
137     *  A new or previously-opened DexFile.
138     * @throws IOException
139     *  If unable to open the source or output file.
140     */
141    static public DexFile loadDex(String sourcePathName, String outputPathName,
142        int flags) throws IOException {
143
144        /*
145         * TODO: we may want to cache previously-opened DexFile objects.
146         * The cache would be synchronized with close().  This would help
147         * us avoid mapping the same DEX more than once when an app
148         * decided to open it multiple times.  In practice this may not
149         * be a real issue.
150         */
151        return new DexFile(sourcePathName, outputPathName, flags);
152    }
153
154    /**
155     * Gets the name of the (already opened) DEX file.
156     *
157     * @return the file name
158     */
159    public String getName() {
160        return mFileName;
161    }
162
163    /**
164     * Closes the DEX file.
165     * <p>
166     * This may not be able to release any resources. If classes from this
167     * DEX file are still resident, the DEX file can't be unmapped.
168     *
169     * @throws IOException
170     *             if an I/O error occurs during closing the file, which
171     *             normally should not happen
172     */
173    public void close() throws IOException {
174        if (mCookie != 0) {
175            guard.close();
176            closeDexFile(mCookie);
177            mCookie = 0;
178        }
179    }
180
181    /**
182     * Loads a class. Returns the class on success, or a {@code null} reference
183     * on failure.
184     * <p>
185     * If you are not calling this from a class loader, this is most likely not
186     * going to do what you want. Use {@link Class#forName(String)} instead.
187     * <p>
188     * The method does not throw {@link ClassNotFoundException} if the class
189     * isn't found because it isn't reasonable to throw exceptions wildly every
190     * time a class is not found in the first DEX file we look at.
191     *
192     * @param name
193     *            the class name, which should look like "java/lang/String"
194     *
195     * @param loader
196     *            the class loader that tries to load the class (in most cases
197     *            the caller of the method
198     *
199     * @return the {@link Class} object representing the class, or {@code null}
200     *         if the class cannot be loaded
201     */
202    public Class loadClass(String name, ClassLoader loader) {
203        String slashName = name.replace('.', '/');
204        return loadClassBinaryName(slashName, loader, null);
205    }
206
207    /**
208     * See {@link #loadClass(String, ClassLoader)}.
209     *
210     * This takes a "binary" class name to better match ClassLoader semantics.
211     *
212     * @hide
213     */
214    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
215        return defineClass(name, loader, mCookie, suppressed);
216    }
217
218    private static Class defineClass(String name, ClassLoader loader, int cookie,
219                                     List<Throwable> suppressed) {
220        Class result = null;
221        try {
222            result = defineClassNative(name, loader, cookie);
223        } catch (NoClassDefFoundError e) {
224            if (suppressed != null) {
225                suppressed.add(e);
226            }
227        } catch (ClassNotFoundException e) {
228            if (suppressed != null) {
229                suppressed.add(e);
230            }
231        }
232        return result;
233    }
234
235    private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
236        throws ClassNotFoundException, NoClassDefFoundError;
237
238    /**
239     * Enumerate the names of the classes in this DEX file.
240     *
241     * @return an enumeration of names of classes contained in the DEX file, in
242     *         the usual internal form (like "java/lang/String").
243     */
244    public Enumeration<String> entries() {
245        return new DFEnum(this);
246    }
247
248    /*
249     * Helper class.
250     */
251    private class DFEnum implements Enumeration<String> {
252        private int mIndex;
253        private String[] mNameList;
254
255        DFEnum(DexFile df) {
256            mIndex = 0;
257            mNameList = getClassNameList(mCookie);
258        }
259
260        public boolean hasMoreElements() {
261            return (mIndex < mNameList.length);
262        }
263
264        public String nextElement() {
265            return mNameList[mIndex++];
266        }
267    }
268
269    /* return a String array with class names */
270    native private static String[] getClassNameList(int cookie);
271
272    /**
273     * Called when the class is finalized. Makes sure the DEX file is closed.
274     *
275     * @throws IOException
276     *             if an I/O error occurs during closing the file, which
277     *             normally should not happen
278     */
279    @Override protected void finalize() throws Throwable {
280        try {
281            if (guard != null) {
282                guard.warnIfOpen();
283            }
284            close();
285        } finally {
286            super.finalize();
287        }
288    }
289
290    /*
291     * Open a DEX file.  The value returned is a magic VM cookie.  On
292     * failure, an IOException is thrown.
293     */
294    private static int openDexFile(String sourceName, String outputName,
295        int flags) throws IOException {
296        return openDexFileNative(new File(sourceName).getCanonicalPath(),
297                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),
298                                 flags);
299    }
300
301    native private static int openDexFileNative(String sourceName, String outputName,
302        int flags) throws IOException;
303
304    /*
305     * Close DEX file.
306     */
307    native private static void closeDexFile(int cookie);
308
309    /**
310     * Returns true if the VM believes that the apk/jar file is out of date
311     * and should be passed through "dexopt" again.
312     *
313     * @param fileName the absolute path to the apk/jar file to examine.
314     * @return true if dexopt should be called on the file, false otherwise.
315     * @throws java.io.FileNotFoundException if fileName is not readable,
316     *         not a file, or not present.
317     * @throws java.io.IOException if fileName is not a valid apk/jar file or
318     *         if problems occur while parsing it.
319     * @throws java.lang.NullPointerException if fileName is null.
320     * @throws dalvik.system.StaleDexCacheError if the optimized dex file
321     *         is stale but exists on a read-only partition.
322     */
323    native public static boolean isDexOptNeeded(String fileName)
324            throws FileNotFoundException, IOException;
325}
loadClassBinaryName(name,loader, suppressed)方法直接调用了
---》
defineClass(name, loader, mCookie, suppressed)方法,而该方法 又调用了
---》
private static native Class defineClassNative(String name, ClassLoader loader, int cookie) throws ClassNotFoundException, NoClassDefFoundError;方法
defineClassNative是C或c++的方法

总结:
第一步:执行ClassLoader的loadClass(String className)方法
第二步:执行BaseDexClassLoader的findClass(String classNmae)方法
第三步:执行DexPathList的findClass(String className)方法
第四步:执行DexFile的loadClassBinaryName方法
第五步:执行DexFile的defindClass方法
第六步:C或C++的defineClassNative方法

DexClassLoader加载class的基本流程到此完成。



 

 

 





 
 
posted @ 2018-01-19 19:29  不一样的农民  阅读(6873)  评论(0编辑  收藏  举报