深入理解Android中的类加载器
桑日网站制作公司哪家好,找成都创新互联公司!从网页设计、网站建设、微信开发、APP开发、成都响应式网站建设公司等网站项目制作,到程序开发,运营维护。成都创新互联公司于2013年开始到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选成都创新互联公司。
ClassLoader类加载,是动态加载机制及现在火热的插件化机制中很基础但同时又很重要的知识点;
今天我们就来讲解下
1、Android中的ClassLoader
ClassLoader使用的是双亲委托机制。双亲委派模型,旨在于让顶级父类加载器先加载类,若不成功,则一层层往下加载,最终到当前加载器。这样做的目的是保持类加载系统的稳定性,不会出现不同加载器加载同一个类时,出现多个类实例;
- protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
- Class> clazz = findLoadedClass(className);
- if (clazz == null) {
- ClassNotFoundException suppressed = null;
- try {
- clazz = parent.loadClass(className, false);
- } catch (ClassNotFoundException e) {
- suppressed = e;
- }
- if (clazz == null) {
- try {
- clazz = findClass(className);
- } catch (ClassNotFoundException e) {
- e.addSuppressed(suppressed);
- throw e;
- }
- }
- }
- return clazz;
- }
Android主要关心的是PathClassLoader和DexClassLoader;
PathClassLoader用来操作本地文件系统中的文件和目录的集合。并不会加载来源于网络中的类。Android采用这个类加载器一般是用于加载系统类和它自己的应用类。这个应用类放置在data/data/包名下;
看一下PathClassLoader的源码,只有2个构造方法:
- package dalvik.system;
- public class PathClassLoader extends BaseDexClassLoader {
- public PathClassLoader(String dexPath, ClassLoader parent) {
- super(dexPath, null, null, parent);
- }
- public PathClassLoader(String dexPath, String libraryPath,
- ClassLoader parent) {
- super(dexPath, null, libraryPath, parent);
- }
- }
- package dalvik.system;
- import java.io.File;
- public class DexClassLoader extends BaseDexClassLoader {
- public DexClassLoader(String dexPath, String optimizedDirectory,
- String libraryPath, ClassLoader parent) {
- super(dexPath, new File(optimizedDirectory), libraryPath, parent);
- }
- }
接下来我们看一下BaseDexClassLoader这个类:
BaseDexClassLoader的构造方法有四个参数:
- # dalvik.system.BaseDexClassLoader
- public BaseDexClassLoader(String dexPath, File optimizedDirectory,
- String libraryPath, ClassLoader parent) {
- super(parent);
- this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
- }
- private final Element[] dexElements;
- public DexPathList(ClassLoader definingContext, String dexPath,
- String libraryPath, File optimizedDirectory) {
- if (definingContext == null) {
- throw new NullPointerException("definingContext == null");
- }
- if (dexPath == null) {
- throw new NullPointerException("dexPath == null");
- }
- if (optimizedDirectory != null) {
- if (!optimizedDirectory.exists()) {
- throw new IllegalArgumentException(
- "optimizedDirectory doesn't exist: "
- + optimizedDirectory);
- }
- // 如果文件不是可读可写的也会抛出异常
- if (!(optimizedDirectory.canRead()
- && optimizedDirectory.canWrite())) {
- throw new IllegalArgumentException(
- "optimizedDirectory not readable/writable: "
- + optimizedDirectory);
- }
- }
- this.definingContext = definingContext;
- ArrayList
suppressedExceptions = new ArrayList (); - // 通过makeDexElements方法来获取Element数组
- // splitDexPath(dexPath)方法是用来把我们之前按照“:”分隔的路径转为File集合。
- this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
- suppressedExceptions);
- if (suppressedExceptions.size() > 0) {
- this.dexElementsSuppressedExceptions =
- suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
- } else {
- dexElementsSuppressedExceptions = null;
- }
- this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
- }
makeDexElements方法的作用是获取一个包含dex文件的元素集合;
# dalvik.system.DexPathList
- private static Element[] makeDexElements(ArrayList
files, File optimizedDirectory, - ArrayList
suppressedExceptions) { - ArrayList
elements = new ArrayList (); - // 遍历打开所有的文件并且加载直接或者间接包含dex的文件。
- for (File file : files) {
- File zip = null;
- DexFile dex = null;
- String name = file.getName();
- if (file.isDirectory()) {
- // We support directories for looking up resources.
- // This is only useful for running libcore tests.
- // 可以发现它是支持传递目录的,但是说只测试libCore的时候有用
- elements.add(new Element(file, true, null, null));
- } else if (file.isFile()){
- // 如果文件名后缀是.dex,说明是原始dex文件
- if (name.endsWith(DEX_SUFFIX)) {
- // Raw dex file (not inside a zip/jar).
- try {
- //调用loadDexFile()方法,加载dex文件,获得DexFile对象
- dex = loadDexFile(file, optimizedDirectory);
- } catch (IOException ex) {
- System.logE("Unable to load dex file: " + file, ex);
- }
- } else {
- // dex文件包含在其它文件中
- zip = file;
- try {
- // 同样调用loadDexFile()方法
- dex = loadDexFile(file, optimizedDirectory);
- } catch (IOException suppressed) {
- // 和加载纯dex文件不同的是,会把异常添加到异常集合中
- /*
- * IOException might get thrown "legitimately" by the DexFile constructor if
- * the zip file turns out to be resource-only (that is, no classes.dex file
- * in it).
- * Let dex == null and hang on to the exception to add to the tea-leaves for
- * when findClass returns null.
- */
- suppressedExceptions.add(suppressed);
- }
- }
- } else {
- System.logW("ClassLoader referenced unknown path: " + file);
- }
- // 如果zip或者dex二者一直不为null,就把元素添加进来
- // 注意,现在添加进来的zip存在不为null也不包含dex文件的可能。
- if ((zip != null) || (dex != null)) {
- elements.add(new Element(file, false, zip, dex));
- }
- }
- return elements.toArray(new Element[elements.size()]);
- }
通过上面的代码也可以看到,加载一个dex文件调用的是loadDexFile()方法;
# dalvik.system.DexPathList
- private static DexFile loadDexFile(File file, File optimizedDirectory)
- throws IOException {
- // 如果缓存存放目录为null就直接创建一个DexFile对象返回
- if (optimizedDirectory == null) {
- return new DexFile(file);
- } else {
- // 根据缓存存放目录和文件名得到一个优化后的缓存文件路径
- String optimizedPath = optimizedPathFor(file, optimizedDirectory);
- // 调用DexFile的loadDex()方法来获取DexFile对象。
- return DexFile.loadDex(file.getPath(), optimizedPath, 0);
- }
- }
DexFile的loadDex()方法如下,内部也做了一些调用。抛开这些细节来讲,它的作用就是加载DexFile文件,而且会把优化后的dex文件缓存到对应目录;
# dalvik.system.DexFile
- static public DexFile loadDex(String sourcePathName, String outputPathName,
- int flags)throws IOException {
- /*
- * TODO: we may want to cache previously-opened DexFile objects.
- * The cache would be synchronized with close(). This would help
- * us avoid mapping the same DEX more than once when an app
- * decided to open it multiple times. In practice this may not
- * be a real issue.
- */
- //loadDex方法内部就是调用了DexFile的一个构造方法
- return new DexFile(sourcePathName, outputPathName, flags);
- }
- private DexFile(String sourceName, String outputName, int flags) throws IOException {
- if (outputName != null) {
- try {
- String parent = new File(outputName).getParent();
- if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
- throw new IllegalArgumentException("Optimized data directory " + parent
- + " is not owned by the current user. Shared storage cannot protect"
- + " your application from code injection attacks.");
- }
- } catch (ErrnoException ignored) {
- // assume we'll fail with a more contextual error later
- }
- }
- mCookie = openDexFile(sourceName, outputName, flags);
- mFileName = sourceName;
- guard.open("close");
- //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
- }
- private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
- // Use absolute paths to enable the use of relative paths when testing on host.
- return openDexFileNative(new File(sourceName).getAbsolutePath(),
- (outputName == null) ? null : new File(outputName).getAbsolutePath(),
- flags);
- }
- private static native long openDexFileNative(String sourceName, String outputName, int flags);
- protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
- Class> clazz = findLoadedClass(className);
- if (clazz == null) {
- ClassNotFoundException suppressed = null;
- try {
- clazz = parent.loadClass(className, false);
- } catch (ClassNotFoundException e) {
- suppressed = e;
- }
- if (clazz == null) {
- try {
- clazz = findClass(className);
- } catch (ClassNotFoundException e) {
- e.addSuppressed(suppressed);
- throw e;
- }
- }
- }
- return clazz;
- }
我们可以去看一下BaseDexClassLoader类的findClass()方法;
# dalvik.system.BaseDexClassLoader
- @Override
- protected Class> findClass(String name) throws ClassNotFoundException {
- List
suppressedExceptions = new ArrayList (); - // 调用DexPathList对象的findClass()方法
- 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;
- }
实际上BaseDexClassLoader调用的是其成员变量DexPathList pathList的findClass()方法;
# dalvik.system.DexPathList
- public Class findClass(String name, List
suppressed) { - // 遍历Element
- for (Element element : dexElements) {
- // 获取DexFile,然后调用DexFile对象的loadClassBinaryName()方法来加载Class文件。
- DexFile dex = element.dexFile;
- if (dex != null) {
- Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
- if (clazz != null) {
- return clazz;
- }
- }
- }
- if (dexElementsSuppressedExceptions != null) {
- suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
- }
- return null;
- }
# dalvik.system.DexFile
- public Class loadClassBinaryName(String name, ClassLoader loader, List
suppressed) { - return defineClass(name, loader, mCookie, suppressed);
- }
- private static Class defineClass(String name, ClassLoader loader, long cookie,
- List
suppressed) { - Class result = null;
- try {
- result = defineClassNative(name, loader, cookie);
- } catch (NoClassDefFoundError e) {
- if (suppressed != null) {
- suppressed.add(e);
- }
- } catch (ClassNotFoundException e) {
- if (suppressed != null) {
- suppressed.add(e);
- }
- }
- return result;
- }
# java.lang.ClassLoader
- protected final Class> defineClass(String className, byte[] classRep, int offset, int length,
- ProtectionDomain protectionDomain) throws java.lang.ClassFormatError {
- throw new UnsupportedOperationException("can't load this type of class file");
- }
Android中加载一个类是遍历PathDexList的Element[]数组,这个Element包含了DexFile,调用DexFile的方法来获取Class文件,如果获取到了Class,就跳出循环。否则就在下一个Element中寻找Class;
利用pathClassLoader 的 对dex 文件进行替换,补丁 dex 文件加载到Element对象,并插入到 dexElement前面,具体还是使用反射;
双亲委派:当一个class文件被加载时,classloader发现已经加载过则不会重新加载,如果没加载过则递归地把这个请求委派给父类加载器完成。当父加载器找不到指定的类时,子加载器尝试自己加载
关键是ClassLoader中loadeClass() 方法, loadClass()双亲委托机制
一个dex被加载的步骤
先从自己缓存中取
自己缓存没有,就在 父 ClassLoader 要 (parent.loadClass())
父 ClassLoader 没有,就自加载(findClass)
makeDexElements(将dex文件或压缩包中的信息保存到dexElements中)
findCLass(遍历Element,并将Element转成Dex文件,获取Dex文件中的Class文件,直到找到对应的class文件位置)
了解各种加载流程,还是需要多深入源码,Android-ClassLoader实现逻辑算是非常清晰易懂,但对我们日常开发如插件化方案会有非常大的帮助;
本文转载自微信公众号「Android开发编程」
网站名称:Android动态加载之ClassLoader加载和插件热修复的机制原理详解
地址分享:http://www.shufengxianlan.com/qtweb/news28/294128.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联