androiddex加载
① Android-类加载
双亲委托机制
类在进行类加载的时候,把加载任务托管给父类加载器,如能加载成功,则返回,否则依次向子类加载器递归尝试类加载。
意义:
①避免类的重复加载,父类加载已加载该类时,子ClassLoader就没有必要加载一次了。
②安全性,防止核心API被随意篡改。
ClassLoader
ClassLoader本身是一个抽象方法。它的主要实现类有BootClassLoader、PathClassLoader、DexClassLoader.
BootClassLoader:用于加载Android Framwork层(SDK)的class文件
PathClassLoader:用于Android应用程序加载器,可以加载指定的dex和jar、zip、apk中的classes.dex(系统使用)
DexClassLoader:用于加载指定的dex和jar、zip、apk中的classes.dex。(供开发者使用)
拓展:
在API26之前。
optimizedDirectory 参数就是dexopt的产出目录(odex)。那 PathClassLoader 创建时,这个目录为null,就
意味着不进行dexopt?并不是, optimizedDirectory 为null时的默认路径为:/data/dalvik-cache。
在API26之后DexClassLoader也取消了optimizedDirectory
热修复相关
LoadClass:
findClass:PathClassLoader和DexClassLoader的父类BaseDexClassLoader中实现findClass。
BaseDexClassLoader中
PathClassLoader加载过后,pathlist 中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有X个dex,则Element数组就有X个元素。
总结:
可能看到这里我们比较乱了,理一下。一个类的加载经历了哪些。我们以PathClassLoader为例。
①加载一个类的时候,首先通过Class缓存寻找是否已经加载过该类。参考抽象类的loadClass方法。
②若在缓存中未找到该类,则交由父加载器加载该类。参考抽象类的loadClass方法。
③调用父加载器PathClassLoader的父类BaseDexClassLoader实现的findClass方法加载该类。
④PathClassLoader在初始化的时候调用父构造方法实例化DexPathList属性,DexPathList属性初始化时构造方法内通过makePathElements(或makeDexElements 不同API可能不同)加载APK内的dex文件生成Element数组。
⑤BaseDexClassLoader实现的findClass方法中顺序循环已存在的Element数组,通过Element中的DexFile加载类。。
⑥未找到,抛出类未找到异常。
热修复(multide 形式(thinker、qfix))
热修复的原理。我们只需在应用启动的时候,一般是在application方法中(因为class加载首先从缓存中加载),在应用启动后,经过PathClassLoader加载过后所有的类都在 pathList的Element 数组,把生成的Elment数组插入到PathList的Element数组的最前方。在加载类的时候就只会加载到我们需要更新的类了,因为是顺序寻找,找到就返回。(先从我们补丁的dex文件生成的element寻找,找不到再从APK的dex生成的element种寻找)。
热修复基本思路总结:
①获取到当前引用的PathClassLoader
②反射获取其中DexPathList属性:DexPathList pathList.
③获取到补丁包path.dex文件的Element[]数组 pElements。参考PathClassLoader怎么把dex文件转换为Element数组的。于是我们反射执行DexPathList 中的makePathElements方法(视API而定)传入dex路径得到补丁包的element数组。
④获取pathList的dexElements数组。
⑤把补丁包的pElements数组合并到pathList的dexElements数组的前方,即newElements=pElements+dexElements
⑥反射赋值把newElements替换掉pathList的dexElements
热修复没这么简单,还需考虑混淆,API版本不同导致的使用makePathElements方法或makeDexElements方法等因素。
热修复(InstantRun 形式(Robust))待了解。
② android插件化(四)Hook加载插件APK(ClassLoader方式)
前面插件化一和二说了下插桩式加载未安装的APK,主要是重写了getResource和getClassloader两个方法来实现的。以及每个组件要实现一个接口,通过接口注入上下文来达到它的生命周期。
那么插桩式和hook式的实现方式有什么不同呢?
插桩式是怎么加载到插件中的class文件呢,是通过将将APK转化成插件的Classloader,然后想要加载插件的class文件,我们的去拿这个插件的classloader去loadClass。所以是有一个中间者的。
hook式呢是将插件apk融入到了我们的宿主apk,那直接在里面就可以直接loadClass了,在不用这个插件的ClassLoader了,这样的话对于插件和宿主就没什么区别了,不像插桩式有一个中间者。
那么要实现hook式 就要知道android中一个class文件式怎样被加载到内存中去的。其实就是通过PathClassLoader来加载的。
那么我们先看下ClassLoader
任何一个java程序都是由一个或者多个class组成的,在程序运行时,需要将class文件加载到JVM中才可以使用,负责加载这些class文件的就是java的类加载机制。CLassLoader的作用就是加载class文件提供给程序运行时使用,每个Class对象内部都有一个ClassLoader来标示自己是有那个classLoade加载的。
Android app的所有的java文件都是通过PathClassLoader来加载的,那么它的父类是BaseDexClassLoader,还有一个兄弟类是DexClassLoader,那么他们有什么区别呢。
从上面可以看出这两个类的构造函数不同。(在26的源码中DexClassLoader中的optimizedDirectory也废弃了)
PathClassLoader:用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
DexClassLoader:加载指定的dex以及jar、zip、apk中的classes.dex。
可以看到创建ClassLoader的时候需要接收一个CLassLoader parent的参数,这个parent的目的就在于实现类加载的委托。
某个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,一次递归,如果父加载器可以完成加载任务,那么就返回,只有当父加载器无法完成加载任务时,才自己去加载。
因此我们自己创建的ClassLoader:newPathClassLoader("/sdcard/xx.dex",getClassLoader()),并不仅仅只能加载我们的xx.dex中的class。
需要注意的是,findBootstrapClassOrNull 这个方法,当parent为null的时候,去这个BootCLassLoader进行加载,
但是在Android当中的实现:
所以new PathClassLoader("/sdcard/xx.dex",null),是不能加载Activity.class的。
上面分析了加载了一个class,是利用了双亲委托机制,那么要是都找不到那就开始调用自己的findCLass方法
在ClassLoader类中findClass:
任何ClassLoader的子类,都可以重写loadClass和findClass。如果你不想使用双亲委托,就重写loadClas修改实现,重写findClass则表示在双亲委托机制下,父ClassLoader都找不到class的情况下,定义自己去查找一个class。
而我们的PathClassLoader会自己负责加载Activity这样的类,利用双亲委托父类去加载activity,而我们的PathClassLoader没有重写findClass,是在它的父类里面。因此我们可以看看父类的findClass是如何实现的。
可以看到加载PathClassLoader加载class,转化为从DexPathList中加载class了,那么我们看看DexPathList中的findClass
那么从上面分析得到
到这里我们想要加载一个插件的apk ,其实最终加载的是一个dex文件(先说class文件,加载资源后面说),有没有办法吧这个dex文件给转化成一个 Element 对象,给放到 Elemeng数组 当中,这样直接就可以加载我们插件中的类了。
1、首先我们肯定是要得到插件APK的的中DexPathList对象中的dexElement数组
2、插件的dexElements数组我们拿到了,那么是不是要开始拿我们系统里面的 ,我们反射获取,和上面的一样。
3、上面我们获取到了系统和我们插件的dexElement数组,然后我们将这个数组合并到一个新的数组里面去,并且给注入到系统里面
至此,加载插件的一个流程基本就完成了。但是上面只是处理了class文件,没有处理资源。资源的话我们也是采用hook的方式去实现
在宿主的Application中hook这个方法,然后去重写getAsserts和getResources两个方法:
然后在插件的BaseActivity中继续重写getAssets和getResources两个方法
这样就可以完成hook式加载一个未安装的APK了。至此基本就完成了插桩式和Hook式插件化的基本实现。(后面几篇是优化)。
③ android dex何时加载
1 问题
在Android系统中,一个App的所有代码都在一个Dex文件里面。Dex是一个类似Jar的存
储了多有Java编译字节码的归档文件。因为Android系统使用Dalvik虚拟机,所以需要把
使用Java Compiler编译之后的class文件转换成Dalvik能够执行的class文件。这里需要强
调的是,Dex和Jar一样是一个归档文件,里面仍然是Java代码对应的字节码文件。
当Android系统启动一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的
工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。这
个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文
件的效率要高很多。但是在早期的Android系统中,DexOpt有一个问题,也就是这篇文
章想要说明并解决的问题。DexOpt会把每一个类的方法id检索起来,存在一个链表结构
里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过
65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的
Android系统中,DexOpt修复了这个问题,但是我们仍然需要对老系统做兼容。
2 思路
一种有效的解决思路是把Dex文件分割成多个较小的Dex。这就如同很多项目会把自己分
割成多个Jar文件一样,不同的功能在不同的Jar文件里面,通过一些配置和额外的操作,
可以让虚拟机有选择性的加载Jar文件。但是在Android系统中,一个应用是只允许有一
个Dex文件的。也就是说在编译期的时候,所有的Jar文件最终会被合并成一个Dex文件。
我们没有办法在Apk文件里面打包两个Dex,让DexOpt分别对两个Dex文件做处理,而
Android系统也不会同时为一个Apk加载两个Dex。
1
2.1 动态加载
如果我们把Dex分成多个文件,然后在程序运行的时候,再把多的那几个动态的加载进来
是否可行呢?也就是说我们能否在运行时阶段把代码加入虚拟机中。对于虚拟机来说,其
实所有的代码都是在运行时被加载进来的。而不同于C语言还存在着静态链接。虚拟机在
所有Java代码执行之前被启动,然后开始把字节码加载到环境中执行,我们可以理解成所
有的代码都是动态加载到虚拟机里的。
而说到加载,不得不说的是ClassLoader。它的工作就是加载.class文件。在Android的
Dalvik环境中,对应的是DexClassLoader,它们的功能是完全一样的。ClassLoader的
一大特点就是它是一个树状结构。每个ClassLoader都有一个父亲ClassLoader。也就是
说,ClassLoader不是把所有的Class放到一个巨大的数组或别的什么数据结构中来处理。
ClassLoader在加载一个Jar中的类的时候,需要制定另一个ClassLoader作为父亲节点,
当我们需要通过ClassLoader得到一个类类型的时候,ClassLoader会把请求优先交给父
亲ClassLoader来处理,而父亲ClassLoader又会交给它的父亲,一直到根ClassLoader。
如果根ClassLoader有这个类,而返回这个类的类类型,否则把这个请求交给这个请求的
来源子ClassLoader。这是一种向上传递,向下分发的机制。这种情况下,对于调用着来
说,子ClassLoader永远都是包含最多Class的ClassLoader。有一点我们需要注意,父亲
ClassLoader只会向请求来源分发自己的处理结果。所以如果来源是自己,那么如果没有
请求类它就会返回空,而不是遍历所有子ClassLoader去请求是否有被请求的类。
在Android系统中,对于一个应用来说,其实有两个ClassLoader,一个是SystemClassLoader,这个ClassLoader里面除了Java标准的类库之外,还有一个android.jar,所有
Android Framework层的类都在这里。而另外一个重要的ClassLoader就是基于Android
Context的ClassLoader。所有属于当前应用的类都是用这个ClassLoader来加载的,我们
可以在Android源码中看到,所有的Activity,Service,View都是使用这个ClassLoader
来反射并创建的。我们暂时把它叫做ContextClassLoader。
3 加载外部Dex
3.1 构建一个Dex文件
这一步并不复杂,首先我们把所需要的.class文件或者是Jar文件和一些源码一起编译生
成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。我们可以
提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工
作,从而加快加载的过程。
2
3.2 DexClassLoader
现在的工作就是在运行时加载这个Dex文件了。我们可以在Application启动的onCreate
方法里面加载Dex,但是如果你的Dex太大,那么它会让你的App启动变慢。我们也可以
使用线程去加载,但我们必须保证加载完成之后再进行某个外部类的请求。当然也可以真
正等到需要某个外部类的时候再进行Dex加载。这根本上取决于Dex文件本身的大小,太
大了可以预加载,而比较小可以等到实际需要的时候再加载。我们暂且把这个加载了外部
Dex的ClassLoader成为ExternalClassLoader
上面我们提到了树形结构和系统中的多个ClassLoader,当我们加载外部Dex的时候,我
们是否需要指定一个父ClassLoader呢?我们当然需要一个父ClassLoader,否则我们ExternalClassLoader连一些基本的Java类都没有,它根本不可能成功的加载一个Dex。进一
步的,我们要选择哪一个ClassLoader来作为我们的父亲呢?是SystemClassLoader还是
ExternalClassLoader?这是根据情况来定的,如果外部的Dex文件里没有任何和Android
相关的代码,那么SystemClassLoader是我们的首选,否则我们就应该用ContextClassLoader。如果是后者的情况,我们的树可以被看成一个链表。
3.3 外部的View, Acitivity等
我们知道,我们编写的四大组建都不是由我们自己来创建的,是由系统来给我们构造并
管理其生命周期的。那么这个过程是什么样的呢?拿Activity来举例,我们需要通过调用
当前Activity/Context的startActivity,传入一个Intent来调用启动一个新的Activity。系
统有一个ActivityManager来处理这里的逻辑。这里的逻辑相当的复杂,但简单来说,
ActivityManager会收到并处理这个Intent,从而决定是是启动一个新的,还是把旧的放
到前台。它会先查找这个Activity在哪个应用里面,这是通过扫描每个应用的AndroidManifest来确定。这些信息是在PackageManager里面被检索的。总之如果这个Activity
不再任何的manifest里面,它就不可能被启动。所以仅有一个Activity类是不够的,我们
需要在manifest里面声明它。
上面是Activity的情况,Service之类的也是同理。那么View怎么办?尽管我们可以直接创
建View,但是大部分的View都不是我们创建的,而是通过XML布局文件Inflate出来的。
也就是说,我们在XML定义了一些外部Dex里面的View,那么显然这个XML是不能被成
功的Inflate的。因为除非系统会使用我们的ExternalClassLoader,否则它肯定是找不到
我们的类的:ContextClassLoader里面并没有外部Dex中的类。
也就是说问题的根本在于,对于那些Android系统为我们创建的对象,它是不能包含在外
部Dex里面的。而Android系统中大部分的组建类的生命周期都交给了系统来管理。我们
3
不可能自己来创建这些类对象。那么另一种思路:我们是不是可以通过使用我们的ExternalClassLoader来代替ContextClassLoader呢?尽管系统的ContextClassLoader是私有
的,但是我们可以通过反射强制的把它替换成我们的ExternalClassLoader。而对于那些
外部的组建(Activity等),尽管我们没有它们的类,但是并不影响我们在AndroidManifest里面声明这个Activity。因为Android系统只是把它作为一个检索,并不会真正检查它
里面的组建是不是真的在虚拟机环境中已经被加载了,只有真正使用Intent启动某个组建
的时候才会去检查。而只要我们保证这个时候我们已经加载了外部的ClassLoader,那么
这个组建就可以被正常的启动。
还有一点,除了我们要为外部可能有的组建在AndroidManifest里面做声明一外,那些外
部组建可能用到的权限我们也需要一一声明,例如如果外部Activity使用了相机功能,那
么如果我们的Manifest里面没有声明使用相机功能的权限的话,即便这个Activity能成功
为加载出来,仍然是不能使用的。
4 核心代码段
加载外部Dex
mClassLoader = new DexClassLoader ( f . getAbsolutePath ( ) ,
mContext . getCacheDir ( ) . getAbsolutePath ( ) ,
null , mContext . getClassLoader ( ) ) ;
让系统使用ExternalClassLoader
t r y {
F ie ld mMainThread = ge t F ie ld ( A c t i v i t y . class , ”mMainThread”) ;
Object mainThread = mMainThread . get ( a c t i v i t y ) ;
Class t hreadClass = mainThread . get Class ( ) ;
F ie ld mPackages = ge t F ie ld ( threadClass , ”mPackages”) ;
WeakReference<?> r e f ;
Map< St ring , ?> map =(Map< St ring , ? >) mPackages . get (mainThread ) ;
r e f = (WeakReference<? >) map . get (mContext . getPackageName ( ) ) ;
Object apk = r e f . get ( ) ;
Class apkClass = apk . get Class ( ) ;
F ie ld mClassLoader = ge t F ie ld ( apkClass , ”mClassLoader”) ;
mClassLoader . set (apk , classLoader ) ;
} catch ( I llegalArgument Except ion e) {
i f (DEBUG) {
e . print St ackTrace ( ) ;
}
} catch ( I llega lAc c essEx c ept ion e) {
i f (DEBUG) {
4
e . print St ackTrace ( ) ;
}
}
5
④ Android动态加载dex技术初步了解
此处需要注意DexClassLoader的四个参数:
参数1 dexPath:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> ),否则会报与上面一样的错误,这点参考文章2中说这个权限可有可无是错误的。(更正下:Android4.4 KitKat及以后的版本需要此权限,之前的版本不需要权限)
⑤ 【Android】Android中的类加载
前文: 【Java】ClassLoader与双亲委派机制
Android中的类加载器有三种, DexClassLoader 、 PathClassLoader 、 BootClassLoader 。
其中 BootClassLoader 是系统启动时预加载常用类的,一般使用不到。 DexClassLoader 、 PathClassLoader 都是继承自 BaseDexClassLoader 。
但 DexClassLoader 和 PathClassLoader 并没有重写 BaseDexClassLoader 中的任何方法,所以源码只需要看 BaseDexClassLoader 即可。
由于Android SDK并没有包含 BaseDexClassLoader ,所以需要到源码查询网站查询源码,如下:
复制这个java文件到对应源码文件夹下就可以在Android Studio中查看了。
通过调试可以看到,Android中普通类的加载器其实是 PathClassLoader 。追踪 PathClassLoader.findClass 方法,即可获取Android的类加载过程:
PathClassLoader.findClass -- 继承自 --> BaseDexClassLoader.findClass()
-> BaseDexClassLoader.pathList.findClass()
-> DexPathList.dexElements.foreach { element.findClass() }
-> Element.findClass()
-> Element.dexFile.loadClassBinaryName()
-> DexFile.defineClass()
即类加载过程通过 BaseDexClassLoader.findClass 、 DexPathList.findClass 、 Element.findClass 、 DexFile.loadClassBinaryName ,最终会落到 DexFile.defineClass 方法中,然后就交给native层了。
其中需要注意的是,在 BaseDexClassLoader.findClass 的开头有这么一段:
这段是在Android 10新加入的,据称是为了实现 shared library 功能的,在之前的版本中没有这一段。
在上一节中知道了,类加载的流程如下:
BaseDexClassLoader.findClass() ->
BaseDexClassLoader.pathList.findClass() ->
DexPathList.dexElements.foreach { element.findClass() } ->
Element.findClass() -> ...
看 DexPathList.findClass 方法:
可以发现, DexPathList 加载类的方法是遍历 dexElements 数组依次加载,知道获取到值为止。所以可以通过修改这个数组,把新的dex文件放在数组的前面,使其加载修改后的类,从而实现热修复。
根据以上原理,写下这个工具类,有效性待验证:
⑥ Android类加载机制
Android手写热修复(一)--ClassLoader
我们平时编写的 .java 文件不是可执行文件,需要先编译成 .class 文件才可以被虚拟机执行。所谓类加载是指通过 类加载器 把class文件加载到虚拟机的内存空间,具体来说是方法区。类通常是按需加载,即第一次使用该类时才加载。
首先,Java与Android都是把类加载到虚拟机内存中,然后由虚拟机转换成设备识别的机器码。但是由于二者使用的虚拟机不同,所以在类加载方面也是有所区别的。Java的虚拟机是JVM,Android的虚拟机是dalvik/art(5.0以后虚拟机是art,是对dalvik的一种升级)。 Java虚拟机运行的是class文件,而Android 虚拟机运行的是dex文件。 dex其实是class文件的集合,是对class文件优化的产物,是为了避免出现重复的class。
从上面的讲解中,我们已经知道我们平时写的类是被 类加载器 加载尽虚拟机内存才能运行。下面就通过Framework源码来为大家讲解Android中最主要的5个类加载器。
在Activity做个简单验证:
结果:
可以看出系统类由BootClassLoader加载,apk中的类由PathClassLoader加载,PathClassLoader的父类加载器是BootClassLoader。如果暂时不能理解父类加载器是什么,没关系,后面讲双亲委托机制的时候会理解的。
下面的源码解析基于 Android SDK API28 ,这几个类加载器(除了ClassLoader)没办法直接在AS上查看源码,AS搜索到的是反编译的class的内容,是不可信的,为大家推荐一个在线工具查看, 在线查看Android Framework源码 。
用来加载本地文件系统上的文件或目录,通常是用来加载apk中我们自己写的类,而像 Activity.class 这种系统的类不是由它加载。注意:这里,并不像很多网上文章说的那样只能加载apk,本地的其他目录的文件也是可以的,这一点我会在后面验证说明。
也是被用来加载 jar 、apk、dex,通常用来加载未安装到应用中的文件。注意,它需要一个应用私有的可写的目录来存放优化后的dex文件。千万不要选择外部存储路径,因为这样可能会导致你的应用遭到注入攻击。
关于dex文件优化,可能很多人还是不理解,水平有限,我简单解释一下,
构造器参数解释:
关于optimizedDirectory:
1、这是dex优化后的路径,它必须是一个应用私有的可写的目录否则会存在注入攻击的风险;
2、这个参数在API 26(8.0)之前是有值的,之后的话,这个参数已经没有影响了,因为在调用父构造器的时候这个参数始终为null,也就是说Android 8.0 以后DexClassLoader和PathClassLoader基本一样的来;
3、在加载app的时候,apk内部的dex已经执行过优化了,优化之后放在系统目录/data/dalvik-cache下。
这个构造器的关键是初始化了一个DexPathList对象,这个是后面加载class的关键类。
这个构造方法等关键是通过 makeDexElements() 方法来获取Element数组,这个Element数组非常关键,后面查找class就会用到它,也是热修复的关键点之一。
splitDexPath(dexPath) 方法是把dexPath目录下的所有文件转换成一个File集合,如果是多个文件的话,会用 : 作为分隔符。
makeDexElements()
小结一下,这个方法就是把指定目录下的文件apk/jar/zip/dex按不同的方式封装成Element对象,然后按顺序添加到Element[]数组中。
DexPathList#loadDexFile()
可以看到 DexFile 最终是调用了openDexFile、native方法openDexFileNative去打开Dex文件的,如果outputName为空,则自动生成一个缓存目录,具体来说是 /data/dalvik-cache/[email protected] 。openDexFileNative这个native方法就不具体分析了,主要是对dex文件进行了优化操作,将优化后得odex文件通过mmap映射到内存中。感兴趣的同学可以参考:
《DexClassLoader和PathClassLoader加载Dex流程》
现在在回头看看DexClassLoader与PathClassLoader的区别。DexClassLoader可以指定odex的路径,而PathClassLoader则采用系统默认的缓存路径,在8.0以后没有区别。
ClassLoader是一个抽象类,有3个构造方法,最终调用的还是第一个构造方法,主要功能是保存实现类传入的parent参数,也就是父类加载器。ClassLoader的实现类主要有2个,一个是前面讲过的BaseDexClassLoader,另一个是BootClassLoader。
BootClassLoader是ClassLoader的内部类,而且继承了ClassLoader。
这是加载一个类的入口,流程如下:
1、 先检查这个类是否已经被加载,有的话直接返回Class对象;
2、如果没有加载过,通过父类加载器去加载,可以看出parent是通过递归的方式去加载class的;
3、如果所有的父类加载器都没有加载过,就由当前的类加载器去加载。
通常我们自己写的类是通过当前类加载器调用 findClass 方法去加载的,但是在 ClassLoader 中这是个空方法,具体的实现在它的子类 BaseDexClassLoader 中。
BaseDexClassLoader # findClass
可以看到是通过pathList去查找class的,这个对象其实之前讲过,它是在BaseDexClassLoader 的构造方法中初始化的,它实际上是一个 DexPathList 对象。
DexPathList # findClass()
对Element数组遍历,再通过Element对象的 findClass 方法去查找class,有的话就直接返回这个class,找不到则返回null。 这里可以看出获取Class是通过DexFile来实现的,而各种类加载器操作的是Dex。Android虚拟机加载的dex文件,而不是class文件。
1、加载一个类是通过双亲委托机制来实现的。
2、如果是第一次加载class,那是通过 BaseDexClassLoader 中的findClass方法实现的;接着进入 DexPathList 中的findClass方法,内部通过遍历Element数组,从Element对象中去查找类;Element实际上是对Dex文件的包装,最终还是从dexfile去查找的class。
3、一般app运行主要用到2个类加载器,一个是PathClassLoader:主要用于加载自己写的类;另一个是BootClassLoader:用于加载Framework中的类;
4、热修复和插件化一般是利用DexClassLoader来实现。
5、PathClassLoader和DexClassLoader其实都可以加载apk/jar/dex,区别是 DexClassLoader 可以指定 optimizedDirectory ,也就是 dex2oat 的产物 .odex 存放的位置,而 PathClassLoader 只能使用系统默认位置。但是在8.0 以后二者是没有区别的,只能使用系统默认的位置了。
这张图来源于:
Android虚拟机框架:类加载机制
在类加载流程分析中,我们已经知道,查找class是通过DexPathList来完成的,实际上DexPathList最终还是遍历其Element数组,获取DexFile对象来加载Class文件。 由于数组是有序的,如果2个dex文件中存在相同类名的class,那么类加载器就只会加载数组前面的dex中的class。如果apk中出现了有bug的class,那只要把修复的class打包成dex文件并且放在 DexPathList 中Element数组`的前面,就可以实现bug修复了 。下一篇为大家带来的手写热修复。
Android类加载机制的细枝末节
从JVM到Dalivk再到ART(class,dex,odex,vdex,ELF)
类加载机制系列2——深入理解Android中的类加载器
Android 热修复核心原理,ClassLoader类加载