详解Android类加载ClassLoader
基本知识
Java的类加载设计了一套双亲代理的模式,使得用户没法替换系统的核心类,从而让应用更安全。所谓双亲代理就是指,当加载类的时候首先去Bootstrap中加载类,如果没有则去Extension中加载,如果再没有才去AppClassLoader中去加载。从而实现安全和稳定。
JavaClassLoader
BootstrapClassLoader
引导类加载器,用来加载Java的核心库。通过底层代码来实现的,基本上只要parent为null,那就表示引导类加载器。
比如:charsets.jar、deploy.jar、javaws.jar、jce.jar、jfr.jar、jfxswt.jar、jsse.jar、management-agent.jar、plugin.jar、resources.jar、rt.jar
ExtClassLoader
拓展类加载器,用来加载Java的拓展的类库,${JAVA_HOME}/jre/lib/ext/目录中的所有jar。
比如:cldrdata.jar、dnsns.jar、jfxrt.jar、localedata.jar、nashorn.jar、sunec.jar、sunjce_provider.jar、sunpkcs11.jar、zipfs.jar等等
AppClassLoader
系统类加载器(不要被名字给迷惑),用来加载Java应用中的类。一般来说自己写的类都是通过这个加载的。而Java中ClassLoader.getSystemClassLoader()返回的就是AppClassLoader。(Android中修改了ClassLoader的逻辑,返回的会是一个PathClassLoader)
自定义ClassLoader
用户如果想自定义ClassLoader的话,只需要继承自java.lang.ClassLoader即可。
ClassLoader中与加载类相关的方法:
- getParent()返回该类加载器的父类加载器。
- loadClass(Stringname)加载名称为name的类,返回的结果是java.lang.Class类的实例。
- findClass(Stringname)查找名称为name的类,返回的结果是java.lang.Class类的实例。
- findLoadedClass(Stringname)查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例。
- defineClass(Stringname,byte[]b,intoff,intlen)把字节数组b中的内容转换成Java类,返回的结果是java.lang.Class类的实例。这个方法被声明为final的。
也许你不太了解上面几个函数的区别,没关系,我们来看下源码是如何实现的。
//ClassLoader.java protectedClass>loadClass(Stringname,booleanresolve) throwsClassNotFoundException { //First,checkiftheclasshasalreadybeenloaded Classc=findLoadedClass(name); if(c==null){ longt0=System.nanoTime(); try{ if(parent!=null){ c=parent.loadClass(name,false); }else{ c=findBootstrapClassOrNull(name); } }catch(ClassNotFoundExceptione){ //ClassNotFoundExceptionthrownifclassnotfound //fromthenon-nullparentclassloader } if(c==null){ //Ifstillnotfound,theninvokefindClassinorder //tofindtheclass. longt1=System.nanoTime(); c=findClass(name); //thisisthedefiningclassloader;recordthestats } } returnc; }
所以优先级大概如下:
loadClass→findLoadedClass→parent.loadClass/findBootstrapClassOrNull→findClass→defineClass
AndroidClassLoader
在Android中ClassLoader主要有两个直接子类,叫做BaseDexClassLoader和SecureClassLoader。而前者有两个直接子类是PathClassLoader和DexClassLoader(AndroidO添加了InMemoryDexClassLoader,略)。
我们只讨论PathClassLoader和DexClassLoader
PathClassLoader
用来加载安装了的应用中的dex文件。它也是Android里面的一个最核心的ClassLoader了。相当于Java中的那个AppClassLoader。
publicclassPathClassLoaderextendsBaseDexClassLoader{ /** *Createsa{@codePathClassLoader}thatoperatesonagivenlistoffiles *anddirectories.Thismethodisequivalenttocalling *{@link#PathClassLoader(String,String,ClassLoader)}witha *{@codenull}valueforthesecondargument(seedescriptionthere). * *@paramdexPaththelistofjar/apkfilescontainingclassesand *resources,delimitedby{@codeFile.pathSeparator},which *defaultsto{@code":"}onAndroid *@paramparenttheparentclassloader */ publicPathClassLoader(StringdexPath,ClassLoaderparent){ super(dexPath,null,null,parent); } /** *Createsa{@codePathClassLoader}thatoperatesontwogiven *listsoffilesanddirectories.Theentriesofthefirstlist *shouldbeoneofthefollowing: * *
-
*
- JAR/ZIP/APKfiles,possiblycontaininga"classes.dex"fileas *wellasarbitraryresources. *
- Raw".dex"files(notinsideazipfile). *
它的实例化是通过调用ApplicationLoaders.getClassLoader来实现的。
它是在ActivityThread启动时发送一个BIND_APPLICATION消息后在handleBindApplication中创建ContextImpl时调用LoadedApk里面的getResources(ActivityThreadmainThread)最后回到ActivityThread中又调用LoadedApk的getClassLoader生成的,具体的在LoadedApk的createOrUpdateClassLoaderLocked。
那么问题来了,当Android加载class的时候,LoadedApk中的ClassLoader是怎么被调用到的呢?
其实Class里面,如果你不给ClassLoader的话,它默认会去拿Java虚拟机栈里面的CallingClassLoader,而这个就是LoadedApk里面的同一个ClassLoader。
//Class.java publicstaticClass>forName(StringclassName) throwsClassNotFoundException{ returnforName(className,true,VMStack.getCallingClassLoader()); }
查看VMStack的源码发现getCallingClassLoader其实是一个native函数,Android通过底层实现了这个。
//dalvik.system.VMStack /** *Returnsthedefiningclassloaderofthecaller'scaller. * *@returntherequestedclassloader,or{@codenull}ifthisisthe *bootstrapclassloader. */ @FastNative nativepublicstaticClassLoadergetCallingClassLoader();
底层想必最终也是拿到LoadedApk里面的ClassLoader。
DexClassLoader
它是一个可以用来加载包含dex文件的jar或者apk文件的,但是它可以用来加载非安装的apk。比如加载sdcard上面的,或者NetWork的。
publicclassDexClassLoaderextendsBaseDexClassLoader{ /** *Createsa{@codeDexClassLoader}thatfindsinterpretedandnative *code.InterpretedclassesarefoundinasetofDEXfilescontained *inJarorAPKfiles. * *Thepathlistsareseparatedusingthecharacterspecifiedbythe *{@codepath.separator}systemproperty,whichdefaultsto{@code:}. * *@paramdexPaththelistofjar/apkfilescontainingclassesand *resources,delimitedby{@codeFile.pathSeparator},which *defaultsto{@code":"}onAndroid *@paramoptimizedDirectorydirectorywhereoptimizeddexfiles *shouldbewritten;mustnotbe{@codenull} *@paramlibrarySearchPaththelistofdirectoriescontainingnative *libraries,delimitedby{@codeFile.pathSeparator};maybe *{@codenull} *@paramparenttheparentclassloader */ publicDexClassLoader(StringdexPath,StringoptimizedDirectory, StringlibrarySearchPath,ClassLoaderparent){ super(dexPath,newFile(optimizedDirectory),librarySearchPath,parent); } }
比如现在很流行的插件化/热补丁,其实都是通过DexClassLoader来实现的。具体思路是:创建一个DexClassLoader,通过反射将前者的DexPathList跟系统的PathClassLoader中的DexPathList合并,就可以实现优先加载我们自己的新类,从而替换旧类中的逻辑了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。