一、ClassLoader的类型 java中的ClassLoader和Android中的ClassLoader并不完全相同,因为java的可执行文件为class文件,而Android的可执行文件为dex文件,他们所加载的文件不同,因而所使用的ClassLoader也不相同。
Android中的ClassLoader分为两种类型,分别如下:
系统类加载器:BootClassLoader、PathClassLoader、DexClassLoader。
自定义加载器,即继承自系统的类加载器。
1.1 BootClassLoader Android系统启动时会使用BootClassLoader来预加载常用类,它由Java实现,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class BootClassLoader extends ClassLoader { private static BootClassLoader instance; @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") public static synchronized BootClassLoader getInstance () { if (instance == null ) { instance = new BootClassLoader (); } return instance; } ...... }
BootClassLoader是ClassLoader的内部类,并继承自ClassLoader。由于BootClassLoader的权限声明是默认的,只有在同一包中才能访问,因此我们在应用程序中是无法直接调用的。
1.2 DexClassLoader DexClassLoader是用来加载dex文件以及包含dex文件的压缩包(apk、jar)。它的代码如下:
1 2 3 4 5 6 7 public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super (dexPath, new File (optimizedDirectory), librarySearchPath, parent); } }
参数解析如下:
dexPath
:dex文件或包含dex文件的jar/apk文件的路径集合,多个路径用文件分隔符分隔,默认文件分隔符为”:”。
optimizedDirectory
:dex优化后产生的文件所存放的路径。
librarySearchPath
:native库的路径集合,多个路径用文件分隔符分隔。
parent
:父加载器
DexClassLoader继承自BaseDexClassLoader,它的方法均在BaseDexClassLoader中实现。DexClassLoader通常用来加载已安装的jar、apk、dex以及SD卡中加载未安装的apk。
1.3 PathClassLoader Android系统使用PathClassLoader来加载系统类和应用程序的类,它的代码如下:
1 2 3 4 5 6 7 8 9 10 11 public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader (String dexPath, ClassLoader parent) { super (dexPath, null , null , parent); } public PathClassLoader (String dexPath, String librarySearchPath, ClassLoader parent) { super (dexPath, null , librarySearchPath, parent); } }
同DexClassLoader一样,PathClassLoader继承自BaseDexClassLoader,方法也都在父类中实现。
可以注意到,PathClassLoader的构造方法中都没有optimizedDirectory参数,这是因为PathClassLoader中默认optimizedDirectory的值为/data/dalvik-cache
(是的,默认dex优化文件的存放目录),因此PathClassLoader是用来加载系统中已经安装过的apk的dex文件。
1.4 BaseDexClassLoader 1 2 3 4 5 6 7 8 9 public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader (String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super (parent); this .pathList = new DexPathList (this , dexPath, libraryPath, optimizedDirectory); } }
BaseDexClassLoader继承自ClassLoader,在构造方法中初始化了DexPathList对象,这个对象比较关键,后续会讲解到。
1.5 ClassLoader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class ClassLoader { private ClassLoader parent; private static Void checkCreateClassLoader () { return null ; } private ClassLoader (Void unused, ClassLoader parent) { this .parent = parent; } protected ClassLoader () { this (checkCreateClassLoader(), getSystemClassLoader()); } protected ClassLoader (ClassLoader parent) { this (checkCreateClassLoader(), parent); } }
ClassLoader是一个抽象类,在构造方法中调用了getSystemClassLoader()
方法,获取SystemClassLoader,它的代码同样在ClassLoader类中,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public abstract class ClassLoader { static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } public static ClassLoader getSystemClassLoader () { return SystemClassLoader.loader; } private static ClassLoader createSystemClassLoader () { String classPath = System.getProperty("java.class.path" , "." ); String librarySearchPath = System.getProperty("java.library.path" , "" ); return new PathClassLoader (classPath, librarySearchPath, BootClassLoader.getInstance()); } }
可见SystemClassLoader对应的是PathClassLoader。
从ClassLoader的代码中可以看出,每创建一个ClassLoader实例,都需要一个现有的ClassLoader实例作为新创建的实例的Parent,这样一来,所有的ClassLoader之间的关联就像一棵树一样,这也是ClassLoader的 双亲代理模型的特点。
二、ClassLoader的继承关系 它们之间的继承关系如下图所示:
ClassLoader:它是一个抽象类,其中定义了ClassLoader的主要功能。
BootClassLoader:它是ClassLoader的内部类,并继承自ClassLoader,用来在Android系统启动时预加载常用类。
SecureClassLoader:它与JDK 8 中的SecureClassLoader类的代码是一样的,它继承自ClassLoader。SecureClassLoader扩展了ClassLoader类的权限方面的功能,加强了ClassLoader的安全性。
URLClassLoader:它与JDK 8 中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URL路径从jar文件和文件夹中加载类和资源。
BaseDexClassLoader:它继承自ClassLoader,是抽象类ClassLoader的具体实现类。
InMemoryDexClassLoader:它是Android 8.0 新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。
PathClassLoader:它继承自BaseDexClassLoader,用来加载系统中已经安装过的apk的dex文件。
DexClassLoader:它继承自BaseDexClassLoader,用来加载已安装的jar、apk、dex以及SD卡中加载未安装的apk。
在实际开发过程中,我们一般是使用DexClassLoader和PathClassLoader这两个类加载器来加载类。
三、类加载器的双亲委托模式 类加载器查找Class所采用的是双亲委托模式,具体为:首先判断该Class是否已经加载,如果没有,则委托给父加载器进行查找而不是自己先去查找,这样依次递归,直到委托到最顶层的ClassLoader,如果最顶层的ClassLoader找到了该Class,就会直接返回,如果没找到,则交还给子加载器查找,依次递归,如果还没找到则最后会交还给自己去查找。
请注意,父类加载器和子类加载器之间并不是继承关系的 ,而是使用组合关系来调用父类加载器,即创建类加载器时传入的parent参数。
对应ClassLoader中的核心代码loadClass()
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (c == null ) { try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { c = findClass(name); } } return c; }private Class<?> findBootstrapClassOrNull(String name) { return null ; }
这段代码就很好的诠释了双亲委托模式。
双亲委托模式优点:
避免重复加载。
更加安全。如果不使用双亲委托模式,就可以自定义一个String类来替代系统的String类,这显然会造成安全隐患。(两个类名一致且被同一类加载器加载的类,JVM才会认为它们是同一个类)
四、ClassLoader加载类的过程 ClassLoader加载类的方法为loadClass()
,它被定义在抽象类ClassLoader中,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (c == null ) { try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { c = findClass(name); } } return c; }private Class<?> findBootstrapClassOrNull(String name) { return null ; }
首先调用findLoadedClass()
方法查看本身有没有加载过该类,对应代码如下:
1 2 3 4 5 6 7 8 9 protected final Class<?> findLoadedClass(String name) { ClassLoader loader; if (this == BootClassLoader.getInstance()) loader = null ; else loader = this ; return VMClassLoader.findLoadedClass(loader, name); }
继而调用VMClassLoader的findLoadedClass()
方法,
1 2 native static Class findLoadedClass (ClassLoader cl, String name) ;
这个方法需要在native层实现。
OK!回到loadClass()
的流程中,之后是调用父加载器的loadClass()
方法(如果存在父加载器),其实就是这个方法本身。
接下来看findClass()
方法,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException (name); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return Class.classForName(name, false , null ); }static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;
第一个findClass()
方法直接抛异常,这说明需要子类来实现;第二个findClass()
方法则调用的是Class类中的classForName()
方法,是个native方法。
回到第一个findClass()
方法,它的实现在BaseDexClassLoader类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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; }public BaseDexClassLoader (String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { super (parent); this .pathList = new DexPathList (this , dexPath, librarySearchPath, null ); if (reporter != null ) { reporter.report(this .pathList.getDexPaths()); } }public BaseDexClassLoader (ByteBuffer[] dexFiles, ClassLoader parent) { super (parent); this .pathList = new DexPathList (this , dexFiles); }
在该方法中调用了pathList的findClass()
方法,这个pathList实在BaseDexClassLoader实例初始化时创建的,是一个DexPathList对象,对应的findClass()
方法代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Class<?> findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz != null ) { return clazz; } } if (dexElementsSuppressedExceptions != null ) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null ; }
遍历Element数组dexElements,并对每个元素调用其findClass()
方法。
整个代码其实就是遍历加载过的所有dex文件,看它们里面是否存在需要加载的类。至于为什么是dex文件,详见文章dex文件加载流程(一) 中的DexPathList的创建。
热修复核心逻辑:在DexPathList.findClass()过程,一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。 ——gityuan
OK!接下来就是查看Element类的findClass()
方法,Element类是DexPathList类中的一个静态类,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 static class Element { private final DexFile dexFile; ...... public Class<?> findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) { return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null ; } ...... }
在findClass()
方法中调用了DexFile的loadClassBinaryName()
方法,具体代码如下:
1 2 3 4 public Class loadClassBinaryName (String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, this , suppressed); }
继而调用defineClass()
方法,对应代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static Class defineClass (String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed) { Class result = null ; try { result = defineClassNative(name, loader, cookie, dexFile); } catch (NoClassDefFoundError e) { if (suppressed != null ) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null ) { suppressed.add(e); } } return result; }private static native Class defineClassNative (String name, ClassLoader loader, Object cookie, DexFile dexFile) throws ClassNotFoundException, NoClassDefFoundError;
最终调用的是defineClassNative()
方法来查找所需要加载的类,而这个方法是个native方法,这里就不再分析了。
这里借用huanzhiyazi的文章中的一句话来概括一下native层做的事:先从已加载类的 class_table 中查询,若找到则直接返回;若找不到则说明该类是第一次加载,则执行加载流程,其中可能需要穿插加载依赖的类,加载完成后将其缓存到 class_table 中。
五、预加载类的流程 在ZygoteInit的main()
方法中,会调用preload()
方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String argv[]) { ...... try { ...... if (!enableLazyPreload) { ...... preload(bootTimingsTraceLog); ...... } ...... } ...... }
preload()
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static void preload (TimingsTraceLog bootTimingsTraceLog) { Log.d(TAG, "begin preload" ); bootTimingsTraceLog.traceBegin("BeginIcuCachePinning" ); beginIcuCachePinning(); bootTimingsTraceLog.traceEnd(); bootTimingsTraceLog.traceBegin("PreloadClasses" ); preloadClasses(); bootTimingsTraceLog.traceEnd(); bootTimingsTraceLog.traceBegin("PreloadResources" ); preloadResources(); bootTimingsTraceLog.traceEnd(); Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs" ); nativePreloadAppProcessHALs(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL" ); preloadOpenGL(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); preloadSharedLibraries(); preloadTextResources(); WebViewFactory.prepareWebViewInZygote(); endIcuCachePinning(); warmUpJcaProviders(); Log.d(TAG, "end preload" ); sPreloadComplete = true ; }
在preload()
方法中,会预加载类、资源、共享库等等。其中preloadClasses()
方法用于Zygote进程初始化时预加载常用类,该方法对应代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private static void preloadClasses () { final VMRuntime runtime = VMRuntime.getRuntime(); InputStream is; try { is = new FileInputStream (PRELOADED_CLASSES); } catch (FileNotFoundException e) { ... } ... try { BufferedReader br = new BufferedReader (new InputStreamReader (is), 256 ); int count = 0 ; String line; while ((line = br.readLine()) != null ) { line = line.trim(); if (line.startsWith("#" ) || line.equals("" )) { continue ; } Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line); try { if (false ) { Log.v(TAG, "Preloading " + line + "..." ); } Class.forName(line, true , null ); count++; } catch (ClassNotFoundException e) { Log.w(TAG, "Class not found for preloading: " + line); } catch (UnsatisfiedLinkError e) { Log.w(TAG, "Problem preloading " + line + ": " + e); } catch (Throwable t) { ... } Trace.traceEnd(Trace.TRACE_TAG_DALVIK); } ... } catch (IOException e) { ... } finally { ... } }
常量PRELOADED_CLASSES
的值为/system/etc/preloaded-classes
,在preloaded-classes
文件中存有预加载记录,这个文件在Android源码中的目录为/frameworks/base/preloaded-classes
,这里举例一些文件中的预加载类:
1 2 3 4 5 6 7 8 9 10 11 12 13 android.app .ApplicationLoaders android.app .ApplicationPackageManager android.app .ContentProviderHolder android.app .DexLoadReporter android.app .Dialog android.app .DownloadManager android.app .Fragment android.animation .Animator android.app .Activity android.app .ActivityManager android.app .ActivityThread java.lang .String java.lang .Thread
很眼熟吧,不然怎么叫常见类呢!
回到preloadClasses()
方法中,遍历预加载类记录,对每个预加载类调用Class.forName()
方法,该方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null ) { loader = BootClassLoader.getInstance(); } Class<?> result; try { result = classForName(name, initialize, loader); } catch (ClassNotFoundException e) { Throwable cause = e.getCause(); if (cause instanceof LinkageError) { throw (LinkageError) cause; } throw e; } return result; }
如果没有就通过BootClassLoader.getInstance()
创建BootClassLoader实例,该方法代码如下:
1 2 3 4 5 6 7 public static synchronized BootClassLoader getInstance () { if (instance == null ) { instance = new BootClassLoader (); } return instance; }
然后创建好的BootClassLoader实例作为参数传入classForName()
方法中,该方法是native方法,代码如下
1 2 3 static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;
Java层到此为止,接下来是Native层,不在这里分析。
回顾整个预加载类的流程,我们可以知道,BootClassLoader是在Zygote进程的Zygote入口方法(main)中被创建的,并用来加载preloaded-classes文件中存放的与加载类。
六、PathClassLoader的创建 PathClassLoader的创建也得从Zygote进程开始说起,Zygote进程启动SystemServer进程是会调用ZygoteInit的startSystemServer()
方法,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private static boolean startSystemServer (String abiList, String socketName, ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller, RuntimeException { ... int pid; try { parsedArgs = new ZygoteConnection .Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null , parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException (ex); } if (pid == 0 ) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } zygoteServer.closeServerSocket(); handleSystemServerProcess(parsedArgs); } return true ; }
Zygote通过forkSystemServer()
方法fork自身来创建子进程(SystemServer进程),如果返回的pid=0,说明当前代码是在新创建的SystemServer进程中执行的,接着就会调用handleSystemServerProcess()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private static void handleSystemServerProcess (ZygoteConnection.Arguments parsedArgs) throws Zygote.MethodAndArgsCaller { ... if (parsedArgs.invokeWith != null ) { ... } else { ClassLoader cl = null ; if (systemServerClasspath != null ) { cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); Thread.currentThread().setContextClassLoader(cl); } ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); } }
调用createPathClassLoader()
方法创建PathClassLoader,然后获取当前线程实例,设置新创建的PathClassLoader为上下文的类加载器 。createPathClassLoader()
方法如下:
1 2 3 4 5 6 7 8 9 static PathClassLoader createPathClassLoader (String classPath, int targetSdkVersion) { String libraryPath = System.getProperty("java.library.path" ); return PathClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath, ClassLoader.getSystemClassLoader(), targetSdkVersion, true ); }
java.library.path
是一个系统属性,它指定了 Java 虚拟机 (JVM) 在加载本地库时应搜索的路径。上述代码通过System.getProperty("java.library.path")
获取本地库信息,然后调用PathClassLoaderFactory类的createClassLoader()
方法创建PathClassLoader。createClassLoader()
方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static PathClassLoader createClassLoader (String dexPath, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared) { PathClassLoader pathClassloader = new PathClassLoader (dexPath, librarySearchPath, parent); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace" ); String errorMessage = createClassloaderNamespace(pathClassloader, targetSdkVersion, librarySearchPath, libraryPermittedPath, isNamespaceShared); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); if (errorMessage != null ) { throw new UnsatisfiedLinkError ("Unable to create namespace for the classloader " + pathClassloader + ": " + errorMessage); } return pathClassloader; }
上述代码直接通过new创建PathClassLoader实例。
回顾整个预加载类的流程,我们可以知道,PathClassLoader是在SystemServer进程中采用工厂模式创建的。
参考:
http://aospxref.com/android-8.0.0_r36/
https://github.com/huanzhiyazi/articles/issues/30
http://liuwangshu.cn/application/classloader/2-android-classloader.html
http://gityuan.com/2017/03/19/android-classloader/
Android动态加载基础 ClassLoader工作机制 - 中二病也要开发ANDROID - SegmentFault 思否
Android动态加载技术 简单易懂的介绍方式 - 中二病也要开发ANDROID - SegmentFault 思否
Android动态加载入门 简单加载模式 - 中二病也要开发ANDROID - SegmentFault 思否