详解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合并,就可以实现优先加载我们自己的新类,从而替换旧类中的逻辑了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。