从源码分析Android的Classloader加载过程

现在插件化技术十分热门,其核心原理之一是用到了ClassLoader类加载器。因此有必要来了解下Android中的ClassLoader加载原理。
动态加载dex/jar/apk文件的基础是类加载器ClassLoader,它的包路径是java.lang,由此可见其重要性,虚拟机就是通过类加载器加载其需要用的Class,这是Java程序运行的基础。

Java当中的类加载器分类

1.Bootstrp loader:加载jvm中指定路径的字节码文件。
2.AppClassLoader: 加载安装到系统的路径下的classs字节码文件。
3.CusomClassLoader:自定义加载字节码文件

Android中的类加载器分类

1.Baseclassloader:加载一些framework层的字节码文件。
2.DexClassLoader:加载指定路径下的字节码文件。
3.PathClassLoader:加载安装到系统中的app下制定目录的字节码文件。
在Android中classloader这个类是怎样加载字节码文件的呢?下面来分析一下Android中的classloader类如何加载Dex文件。
加载Dex文件主要用到的方法是ClassLoader的loadClass(string)方法。
loadClass(string)其实是执行了loadClass(string,false)。
第 2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行。

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
         Class<?> clazz = findLoadedClass(className);

         if (clazz == null) {
             try {
                 clazz = parent.loadClass(className, false);
               } catch (ClassNotFoundException e) {
                 // Don't want to see this.
             }

             if (clazz == null) {
                 clazz = findClass(className);
             }
         }

         return clazz;
     }

首先去去找当前classloader的父classloader是否加载过这个文件,用父加载器去加载。
如果没有,在去缓存中找当前classsloader是否加载过对应的字节码文件。
ClassLoader在这里使用了双亲委托模式进行类加载。任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。

类加载器的双亲委派机制有什么好处呢?两个字,安全,假设当我们覆盖重写java.lang.String这个类的时候,因为类加载模式会先从bootstrapclassloader加载,于是就防止我们用自己定义的String类覆盖系统的String类。

具体点说就是因为ClassLoader使用以上双亲委派机制,所以当我们使用一个自定义的ClassLoader加载 java.lang.String时,会先判断这个ClassLoader的parent是否为null,当ClassLoader的parent为null 时,ClassLoader的parent就是bootstrap classloader,所以在ClassLoader的最顶层就是bootstrap classloader,因此最终委托到bootstrap classloader的时候,bootstrap classloader就会返回String的Class。

额,回过头来,然后再接着原来的代码往下看,找到findClass方法。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new RuntimeException("Stub!");
}

我们看到这里是空实现,应该是想由子类继承实现。
在android当中,classloader的继承类主要有baseclassloader,DexClassLoader,以及PathClassLoader这三个。
首先来研究一下DexClassLoader,这个类的源码在android studio里面是不能看的。要到源码网站上去看,具体地址:
http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

6public class DexClassLoader extends BaseDexClassLoader {
37    /**
38     * Creates a {@code DexClassLoader} that finds interpreted and native
39     * code.  Interpreted classes are found in a set of DEX files contained
40     * in Jar or APK files.
41     *
42     * <p>The path lists are separated using the character specified by the
43     * {@code path.separator} system property, which defaults to {@code :}.
44     *
45     * @param dexPath the list of jar/apk files containing classes and
46     *     resources, delimited by {@code File.pathSeparator}, which
47     *     defaults to {@code ":"} on Android
48     * @param optimizedDirectory directory where optimized dex files
49     *     should be written; must not be {@code null}
50     * @param libraryPath the list of directories containing native
51     *     libraries, delimited by {@code File.pathSeparator}; may be
52     *     {@code null}
53     * @param parent the parent class loader
54     */
55    public DexClassLoader(String dexPath, String optimizedDirectory,
56            String libraryPath, ClassLoader parent) {
57        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
58    }
59}

然后再来看下pathclassloader
这个类里面代码页比较少,但是就是看下构造函数

     public PathClassLoader(String dexPath, String libraryPath,
             ClassLoader parent) {
         super(dexPath, null, libraryPath, parent);
     }

我们把这个构造函数跟上面这个dexclaasloader的构造函数比较一下。
发现少了一个optimizedDirecotry,即pathclassloader只能加载系统中的dex文件;而DexClassLoader则可以加载指定目录下面的dex文件。
他们两个类都继承了BaseDexClassLoader。
这个BaseDexClassLoader的源码如下:

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    }

主要看下54行findClass方法,通过成员变量pathlist真正的完成了class文件的查找。
这个pathlist成员变量在构造函数里面,我们发现,声明的 类型是DexPathList,第一个参数this表示classloader本身,第二个参数dexPath表示要传入的dex文件的路径。第四个参数 optimizedDirectory表示要复制到的内部文件夹中的位置,可以为空,空的话相当于使用到了PathClassLoader了。
DexPathList类源码:

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

成员变量 DEX_SUFFIX = “.dex”表示我们要加载的是dex文件。DexPathList 有一个非常重要的成员变量private final Element[] dexElements;
这个Element其实就是它的内部类,而内部类Element有个成员变量叫dexfile,表示的正是dex文件在android内存中表现形式。
我们来看一下这个类的构造函数,关键看到112行,这里对dexElements进行初始化,调用的方法是makeDexElements方法。

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    }

所以咯这个类的主要作用就是从制定文件夹路径中的dex加载到dexfile里面,并且存入到elements当中。那么存到elements数组中有什么作用呢?作用就是findclass方法。
我们来看一下

 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    }

findClass 方法主要是遍历成员变量dexElements。
如果每个Element的dexfile对象为空,则进行class类的查找。
我们最终发现,通过 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);这句话完成了类的查找。那么这个Dexfile里面的loadClassBinaryName又干了些什么事情呢。我们找到Dexfile源码

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    }
   private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
236        throws ClassNotFoundException, NoClassDefFoundError;

loadClassBinaryName最终调用defineClassNative方法。
我们可以想象一下defineClassNative的大致作用,通过c
或者c++去查找制定name的Class的相关信息,返回一个Class类型的数据,这个数据就是class文件字节码。
因为源码是在c层,所以这里是看不了。
好了,这样就把ClassLoader如何加载dex文件的全部流程都走了一遍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值