文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android动态加载之ClassLoader加载和插件热修复的机制原理详解

2024-12-02 18:48

关注

ClassLoader类加载,是动态加载机制及现在火热的插件化机制中很基础但同时又很重要的知识点;

今天我们就来讲解下

一、ClassLoader介绍

Android中的ClassLoader

 

 

2、加载原理

ClassLoader使用的是双亲委托机制。双亲委派模型,旨在于让顶级父类加载器先加载类,若不成功,则一层层往下加载,最终到当前加载器。这样做的目的是保持类加载系统的稳定性,不会出现不同加载器加载同一个类时,出现多个类实例;

 

  1. protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { 
  2.  
  3.         Class clazz = findLoadedClass(className); 
  4.         if (clazz == null) { 
  5.             ClassNotFoundException suppressed = null
  6.             try { 
  7.                 clazz = parent.loadClass(className, false); 
  8.             } catch (ClassNotFoundException e) { 
  9.                 suppressed = e; 
  10.             } 
  11.             if (clazz == null) { 
  12.                 try { 
  13.                     clazz = findClass(className); 
  14.                 } catch (ClassNotFoundException e) { 
  15.                     e.addSuppressed(suppressed); 
  16.                     throw e; 
  17.                 } 
  18.             } 
  19.         } 
  20.         return clazz; 
  21.     } 

二、ClassLoader源码分析

1、PathClassLoader

Android主要关心的是PathClassLoader和DexClassLoader;

PathClassLoader用来操作本地文件系统中的文件和目录的集合。并不会加载来源于网络中的类。Android采用这个类加载器一般是用于加载系统类和它自己的应用类。这个应用类放置在data/data/包名下;

看一下PathClassLoader的源码,只有2个构造方法:

 

  1. package dalvik.system; 
  2. public class PathClassLoader extends BaseDexClassLoader { 
  3.     public PathClassLoader(String dexPath, ClassLoader parent) { 
  4.         super(dexPath, nullnull, parent); 
  5.     } 
  6.     public PathClassLoader(String dexPath, String libraryPath, 
  7.             ClassLoader parent) { 
  8.         super(dexPath, null, libraryPath, parent); 
  9.     } 

2、DexClassLoader

 

  1. package dalvik.system; 
  2. import java.io.File; 
  3. public class DexClassLoader extends BaseDexClassLoader { 
  4.     public DexClassLoader(String dexPath, String optimizedDirectory, 
  5.             String libraryPath, ClassLoader parent) { 
  6.         super(dexPath, new File(optimizedDirectory), libraryPath, parent); 
  7.     } 

3、BaseDexClassLoader

接下来我们看一下BaseDexClassLoader这个类:

BaseDexClassLoader的构造方法有四个参数:

 

  1. # dalvik.system.BaseDexClassLoader 
  2.     public BaseDexClassLoader(String dexPath, File optimizedDirectory, 
  3.             String libraryPath, ClassLoader parent) { 
  4.         super(parent); 
  5.         this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); 
  6.     } 

4、DexPathList

  1. private final Element[] dexElements; 
  2. public DexPathList(ClassLoader definingContext, String dexPath, 
  3.         String libraryPath, File optimizedDirectory) { 
  4.     if (definingContext == null) { 
  5.         throw new NullPointerException("definingContext == null"); 
  6.     } 
  7.     if (dexPath == null) { 
  8.         throw new NullPointerException("dexPath == null"); 
  9.     } 
  10.     if (optimizedDirectory != null) { 
  11.         if (!optimizedDirectory.exists())  { 
  12.             throw new IllegalArgumentException( 
  13.                     "optimizedDirectory doesn't exist: " 
  14.                     + optimizedDirectory); 
  15.         } 
  16.         // 如果文件不是可读可写的也会抛出异常 
  17.         if (!(optimizedDirectory.canRead() 
  18.                         && optimizedDirectory.canWrite())) { 
  19.             throw new IllegalArgumentException( 
  20.                     "optimizedDirectory not readable/writable: " 
  21.                     + optimizedDirectory); 
  22.         } 
  23.     } 
  24.     this.definingContext = definingContext; 
  25.     ArrayList suppressedExceptions = new ArrayList(); 
  26.     // 通过makeDexElements方法来获取Element数组 
  27.     // splitDexPath(dexPath)方法是用来把我们之前按照“:”分隔的路径转为File集合。 
  28.     this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 
  29.                                        suppressedExceptions); 
  30.     if (suppressedExceptions.size() > 0) { 
  31.         this.dexElementsSuppressedExceptions = 
  32.             suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 
  33.     } else { 
  34.         dexElementsSuppressedExceptions = null
  35.     } 
  36.     this.nativeLibraryDirectories = splitLibraryPath(libraryPath); 

5、makeDexElements

makeDexElements方法的作用是获取一个包含dex文件的元素集合;

# dalvik.system.DexPathList

  1. private static Element[] makeDexElements(ArrayList files, File optimizedDirectory, 
  2.                                          ArrayList suppressedExceptions) { 
  3.     ArrayList elements = new ArrayList(); 
  4.     // 遍历打开所有的文件并且加载直接或者间接包含dex的文件。 
  5.     for (File file : files) { 
  6.         File zip = null
  7.         DexFile dex = null
  8.         String name = file.getName(); 
  9.         if (file.isDirectory()) { 
  10.             // We support directories for looking up resources. 
  11.             // This is only useful for running libcore tests. 
  12.             // 可以发现它是支持传递目录的,但是说只测试libCore的时候有用 
  13.             elements.add(new Element(file, truenullnull)); 
  14.         } else if (file.isFile()){ 
  15.             // 如果文件名后缀是.dex,说明是原始dex文件 
  16.             if (name.endsWith(DEX_SUFFIX)) { 
  17.                 // Raw dex file (not inside a zip/jar). 
  18.                 try { 
  19.                     //调用loadDexFile()方法,加载dex文件,获得DexFile对象 
  20.                     dex = loadDexFile(file, optimizedDirectory); 
  21.                 } catch (IOException ex) { 
  22.                     System.logE("Unable to load dex file: " + file, ex); 
  23.                 } 
  24.             } else { 
  25.                 // dex文件包含在其它文件中 
  26.                 zip = file; 
  27.                 try { 
  28.                     // 同样调用loadDexFile()方法 
  29.                     dex = loadDexFile(file, optimizedDirectory); 
  30.                 } catch (IOException suppressed) { 
  31.                     // 和加载纯dex文件不同的是,会把异常添加到异常集合中 
  32.                      
  33.                     suppressedExceptions.add(suppressed); 
  34.                 } 
  35.             } 
  36.         } else { 
  37.             System.logW("ClassLoader referenced unknown path: " + file); 
  38.         } 
  39.         // 如果zip或者dex二者一直不为null,就把元素添加进来 
  40.         // 注意,现在添加进来的zip存在不为null也不包含dex文件的可能。 
  41.         if ((zip != null) || (dex != null)) { 
  42.             elements.add(new Element(file, false, zip, dex)); 
  43.         } 
  44.     } 
  45.     return elements.toArray(new Element[elements.size()]); 

6、loadDexFile()、loadDex

通过上面的代码也可以看到,加载一个dex文件调用的是loadDexFile()方法;

# dalvik.system.DexPathList

 

  1. private static DexFile loadDexFile(File file, File optimizedDirectory) 
  2.         throws IOException { 
  3.     // 如果缓存存放目录为null就直接创建一个DexFile对象返回 
  4.     if (optimizedDirectory == null) { 
  5.         return new DexFile(file); 
  6.     } else { 
  7.         // 根据缓存存放目录和文件名得到一个优化后的缓存文件路径 
  8.         String optimizedPath = optimizedPathFor(file, optimizedDirectory); 
  9.         // 调用DexFile的loadDex()方法来获取DexFile对象。 
  10.         return DexFile.loadDex(file.getPath(), optimizedPath, 0); 
  11.     } 

DexFile的loadDex()方法如下,内部也做了一些调用。抛开这些细节来讲,它的作用就是加载DexFile文件,而且会把优化后的dex文件缓存到对应目录;

# dalvik.system.DexFile

 

  1. static public DexFile loadDex(String sourcePathName, String outputPathName, 
  2.     int flags)throws IOException { 
  3.      
  4.     //loadDex方法内部就是调用了DexFile的一个构造方法 
  5.     return new DexFile(sourcePathName, outputPathName, flags); 
  6. private DexFile(String sourceName, String outputName, int flags) throws IOException { 
  7.     if (outputName != null) { 
  8.         try { 
  9.             String parent = new File(outputName).getParent(); 
  10.             if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { 
  11.                 throw new IllegalArgumentException("Optimized data directory " + parent 
  12.                         + " is not owned by the current user. Shared storage cannot protect" 
  13.                         + " your application from code injection attacks."); 
  14.             } 
  15.         } catch (ErrnoException ignored) { 
  16.             // assume we'll fail with a more contextual error later 
  17.         } 
  18.     } 
  19.     mCookie = openDexFile(sourceName, outputName, flags); 
  20.     mFileName = sourceName; 
  21.     guard.open("close"); 
  22.     //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); 
  23. private static long openDexFile(String sourceName, String outputName, int flags) throws IOException { 
  24.     // Use absolute paths to enable the use of relative paths when testing on host. 
  25.     return openDexFileNative(new File(sourceName).getAbsolutePath(), 
  26.                              (outputName == null) ? null : new File(outputName).getAbsolutePath(), 
  27.                              flags); 
  28. private static native long openDexFileNative(String sourceName, String outputName, int flags); 

7、loadClass

 

  1. protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { 
  2.     Class clazz = findLoadedClass(className); 
  3.     if (clazz == null) { 
  4.         ClassNotFoundException suppressed = null
  5.         try { 
  6.             clazz = parent.loadClass(className, false); 
  7.         } catch (ClassNotFoundException e) { 
  8.             suppressed = e; 
  9.         } 
  10.         if (clazz == null) { 
  11.             try { 
  12.                 clazz = findClass(className); 
  13.             } catch (ClassNotFoundException e) { 
  14.                 e.addSuppressed(suppressed); 
  15.                 throw e; 
  16.             } 
  17.         } 
  18.     } 
  19.     return clazz; 

我们可以去看一下BaseDexClassLoader类的findClass()方法;

# dalvik.system.BaseDexClassLoader

  1. @Override 
  2. protected Class findClass(String name) throws ClassNotFoundException { 
  3.     List suppressedExceptions = new ArrayList(); 
  4.     // 调用DexPathList对象的findClass()方法 
  5.     Class c = pathList.findClass(name, suppressedExceptions); 
  6.     if (c == null) { 
  7.         ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); 
  8.         for (Throwable t : suppressedExceptions) { 
  9.             cnfe.addSuppressed(t); 
  10.         } 
  11.         throw cnfe; 
  12.     } 
  13.     return c; 

实际上BaseDexClassLoader调用的是其成员变量DexPathList pathList的findClass()方法;

# dalvik.system.DexPathList

  1. public Class findClass(String name, List suppressed) { 
  2.     // 遍历Element 
  3.     for (Element element : dexElements) { 
  4.         // 获取DexFile,然后调用DexFile对象的loadClassBinaryName()方法来加载Class文件。 
  5.         DexFile dex = element.dexFile; 
  6.         if (dex != null) { 
  7.             Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); 
  8.             if (clazz != null) { 
  9.                 return clazz; 
  10.             } 
  11.         } 
  12.     } 
  13.     if (dexElementsSuppressedExceptions != null) { 
  14.         suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 
  15.     } 
  16.     return null

# dalvik.system.DexFile

  1. public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) { 
  2.     return defineClass(name, loader, mCookie, suppressed); 
  3. private static Class defineClass(String name, ClassLoader loader, long cookie, 
  4.                                  List suppressed) { 
  5.     Class result = null
  6.     try { 
  7.         result = defineClassNative(name, loader, cookie); 
  8.     } catch (NoClassDefFoundError e) { 
  9.         if (suppressed != null) { 
  10.             suppressed.add(e); 
  11.         } 
  12.     } catch (ClassNotFoundException e) { 
  13.         if (suppressed != null) { 
  14.             suppressed.add(e); 
  15.         } 
  16.     } 
  17.     return result; 

# java.lang.ClassLoader

 

  1. protected final Class defineClass(String className, byte[] classRep, int offset, int length, 
  2.         ProtectionDomain protectionDomain) throws java.lang.ClassFormatError { 
  3.     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开发编程内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯