您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > Android

浅谈Android类加载器

时间:2019-09-05 15:49:25  来源:  作者:

一. 概述

Android从5.0开始就采用art虚拟机, 该虚拟机有些类似JAVA虚拟机, 程序运行过程也需要通过ClassLoader 将目标类加载到内存.

传统Jvm主要是通过读取class字节码来加载, 而art则是从dex字节码来读取. 这是一种更为优化的方案, 可以将多个.class文件合并成一个classes.dex文件. 下面直接来看看ClassLoader的关系。

二. 五种类构造器

接下来依次看看PathClassLoader,DexClassLoader,BaseDexClassLoader,BootClassLoader,ClassLoader这5个类加载器。

PathClassLoader和DexClassLoader,它们都继承自BaseDexClassLoader,这两个类有什么区别呢?其实看一下它们的源码注释就一目了然了。因为代码很少,约等于没有,这里直接贴出它们的源码,其实主要是注释:

2.1 PathClassLoader

浅谈Android类加载器

 


浅谈Android类加载器

 

由注释看可以发现PathClassLoader被用来加载本地文件系统上的文件或目录,但不能从网络上加载,关键是它被用来加载系统类和我们的应用程序,这也是为什么它的两个构造函数中调用父类构造器的时候第二个参数传null,具体的参数意义请看接下来DexClassLoader的注释。

2.2 DexClassLoader

浅谈Android类加载器

 

DexClassLoader用来加载jar、apk,其实还包括zip文件或者直接加载dex文件,它可以被用来执行未安装的代码或者未被应用加载过的代码。这里也写出了它需要的四个参数的意思

  1. dexPath:需要被加载的文件地址,可以多个,用File.pathSeparator分割
  2. optimizedDirectory:dex文件被加载后会被编译器优化,优化之后的dex存放路径,不可以为null。注意,注释中也提到需要一个应用私有的可写的一个路径,以防止应用被注入攻击,并且给出了例子 File dexOutputDir = context.getDir("dex", 0);
  3. libraryPath:包含libraries的目录列表,plugin中有so文件,需要将so拷贝到sd卡上,然后把so所在的目录当参数传入,同样用File.pathSeparator分割,如果没有则传null就行了,会自动加上系统so库的存放目录
  4. parent:父类构造器

这里着重看一下第二个参数,之前说过PathClassLoader中调用父类构造器的时候这个参数穿了null,因为加载App应用的时候我们的apk已经被安装到本地文件系统上了,其内部的dex已经被提取并且执行过优化了,优化之后放在系统目录/data/dalvik-cache下。

2.3 BaseDexClassLoader

浅谈Android类加载器

 

BaseDexClassLoader构造函数, 有一个非常重要的过程, 那就是初始化DexPathList对象.

另外该构造函数的参数说明:

  • dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
  • optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
  • libraryPath: native库所在路径列表;当有多个路径则采用:分割;
  • ClassLoader:父类的类加载器.

2.4 BootClassLoader

浅谈Android类加载器

 

2.5 ClassLoader

浅谈Android类加载器

 

再来看看SystemClassLoader,这里的getSystemClassLoader()返回的是PathClassLoader类。

浅谈Android类加载器

 

3. 类加载实例

首先看一段如何使用类加载器加载的调用代码:

1 try {
2 File file = view.getActivity().getDir("dex",0);
3 String optimizedDirectory = file.getAbsolutePath();
4 DexClassLoader loader = new DexClassLoader("需要被加载的dex文件所在的路径",optimizedDirectory,null,context.getClassLoader());
5 loader.loadClass("需要加载的类的完全限定名");
6 } catch (ClassNotFoundException e) {
7 e.printStackTrace();
8 }

这里我们就用了自定义了一个DexClassLoaderLoader,并且调用了它的loadClass方法,这样一个需要被使用的类就被我们加载进来了,接下去就可以正常使用这个类了,具体怎么使用我就不多说了,我们还是来研究研究这个类是怎么被加载进来的吧~

可以看到new DexClassLoader的时候我们用了4个参数,参数意义上面已经讲过了,从上面的源码中可以看到DexClassLoader的构造器中直接调用了父类的构造器,只是将optimizedDirectory路径封装成一个File,具体这些参数是如何被使用的呢,我们往下看。

BaseDexClassLoader类的构造器

浅谈Android类加载器

 

首先也是调用了父类的构造器,但这里只将parent传给父类,即ClassLoader,ClassLoader中做的也很很简单,它内部有个parent属性,正好保存传进来的参数parent,这里可以稍微看一下第二个参数的注释,最后一句说到可以为null,而是否为null又刚好是PathClassLoader和DexClassLoader的区别,那是否为null最终又意味着什么呢?

浅谈Android类加载器

 

接下来BaseDexClassLoader给originalPath 和 pathList赋了值,originalPath就是我们传进入的dex文件路径,pathList 是一个new 出来的DexPathList对象。

浅谈Android类加载器

 

别的先不说,先看注释。第四个参数中说到如果optimizedDirectory 为null则使用系统默认路径代替,这个默认路径也就是/data/dalvik-cache/目录,这个一般情况下是没有权限去访问的,所以这也就解释了为什么我们只能用DexClassLoader去加载类而不用PathClassLoader。

然后接着看代码,显然,前面三个if判断都是用来验证参数的合法性的,之后同样只是做了三个赋值操作,第一个就不说了,保存了实例化DexPathList的classloader,第二个参数的声明是一个Element数组,第三个参数是lib库的目录文件数组。

浅谈Android类加载器

 

看它们之前先看看几个split小函数:

浅谈Android类加载器

 

这两个顾名思义就是拿来分割dexPath和libPath,它们内部都调用了splitPaths方法,只是三个参数不一样,其中splitLibraryPath方法中调用splitPaths时的第二个参数仿佛又透露了什么信息,没错,之前介绍DexClassLoader参数中的libraryPath的时候说过,会加上系统so库的存放目录,就是在这个时候添加上去的。

浅谈Android类加载器

 

什么啊,原来这个方法也没做什么事啊,只是把参数path1和参数path2又分别调用了下splitAndAdd方法,但是这里创建了一个ArrayList,而且调用splitAndAdd方法的时候都当参数传入了,并且最终返回了这个list,所以我们大胆猜测下,path1和path2最后被分割后的值都存放在了list中返回,看下是不是这么一回事吧:

浅谈Android类加载器

 

果然,跟我们猜的一样,只是又加上了文件是否存在以及是否可读的验证,然后根据参数wantDirectories判断是否文件类型是被需要的类型,最终加入list。现在我们回过头去看看splitDexPath方法和splitLibraryPath方法,是不是一目了然了。

再往上看DexPathList的构造器,nativeLibraryDirectories的最终值也已经知道了,就差dexElements了,makeDexElements方法的两个参数我们也已经知道了,那我们就看看makeDexElements都干了些什么吧。

浅谈Android类加载器

 


浅谈Android类加载器

 

方法也不长,我们一段段看下去。首先创建了一个elememt 列表,然后遍历由dexpath分割得来的文件列表,其实一般使用场景下也就一个文件。循环里面针对每个file 声明一个zipfile和一个dexfile,判断file的文件后缀名,如果是".dex"则使用loadDexFile方法给dex赋值,如果是“.apk”,“.jar”,“.zip”文件的则把file包装成zipfile赋值给zip,然后同样是用loadDexFile方法给dex赋值,如果是其他情况则不做处理,打印日志说明文件类型不支持,而且下一个if判断中由于zip和dex都未曾赋值,所以也不会添加到elements列表中去。注意下:这里所谓的文件类型仅仅是指文件的后缀名而已,并不是文件的实际类型,比如我们将.zip文件后缀改成.txt,那么就不支持这个文件了。而且我们可以看到对于dexpath目前只支持“.dex”、“.jar”、“.apk”、“.zip”这四种类型。

现在还剩下两个东西可能还不太明确,一个是什么是DexFile以及这里的loadDexFile方法是如何创建dexfile实例的,另一个是什么是Elememt,看了下Element源码,哈哈,so easy,就是一个简单的数据结构体加一个方法,所以我们就先简单把它当做一个存储了file,zip,dex三个字段的一个实体类。那么就剩下DexFile了。

浅谈Android类加载器

 

很简洁,如果optimizedDirectory == null则直接new 一个DexFile,否则就使用DexFile.loadDex来创建一个DexFile实例。

浅谈Android类加载器

 

这个方法获取被加载的dexpath的文件名,如果不是“.dex”结尾的就改成“.dex”结尾,然后用optimizedDirectory和新的文件名构造一个File并返回该File的路径,所以DexFile.loadDex方法的第二个参数其实是dexpath文件对应的优化文件的输出路径。比如我要加载一个dexpath为“sdcard/coder_yu/plugin.apk”,optimizedDirectory 为使用范例中的目录的话,那么最终优化后的输出路径为/data/user/0/com.coder_yu.test/app_dex/plugin.dex,具体的目录在不同机型不同rom下有可能会不一样。

是时候看看DexFile了。在上面的loadDexFile方法中我们看到optimizedDirectory参数为null的时候直接返回new DexFile(file)了,否则返回 DexFile.loadDex(file.getPath(), optimizedPath, 0),但其实他们最终都是使用了相同方法去加载dexpath文件,因为DexFile.loadDex方法内部也是直接调用的了DexFile的构造器,以下:

浅谈Android类加载器

 

然后看看DexFile的构造器吧

浅谈Android类加载器

 

可以看到直接new DexFile(file)和DexFile.loadDex(file.getPath(), optimizedPath, 0)最终都是调用了openDexFile(sourceName, outputName, flags)方法,只是直接new的方式optimizedPath参数为null,这样openDexFile方法会默认使用 /data/dalvik-cache目录作为优化后的输出目录,第二个构造器的注释中写的很明白了。mCookie是一个int值,保存了openDexFile方法的返回值,openDexFile方法是一个native方法,我们就不深入了。

四. 总结

几种类加载器:

  • PathClassLoader: 主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/
  • DexClassLoader: 可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载,但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多插件化方案都是采用DexClassLoader;
  • BaseDexClassLoader: 比较基础的类加载器, PathClassLoader和DexClassLoader都只是在构造函数上对其简单封装而已.
  • BootClassLoader: 作为父类的类构造器。

热修复核心逻辑:在DexPathList.findClass()过程,一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。

类加载过程常见的ClassNotFound原因:

  • ABI异常:常见在系统APP,为了减小system分区大小会将apk源文件中的classes.dex文件移除,对于既然可运行在64位又可运行在32位模式的应用,当被强制设置32位时,openDexFileNative在查找不到oat文件时会运行在解释模式,而classes.dex文件不再则出现ClassNotFound异常。
  • MultiDex处理不当,由于每个Dex文件中方法个数不能超过65536,引入MultiDex机制。dex2oat会自动查找Apk文件中的classes.dex,classes2.dex,…classesN.dex等文件,编译到/data/dalvik-cache下生成oat文件。这里需要文件名跟classesN.dex格式,并且一定要与classes.dex一起放置在第一级目录,有些APP不按照要求来,导致ClassNotFound异常。

好啦,文章写到这里就结束了,如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。



Tags:Android   点击:()  评论:()
声明:本站部分内容来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除,谢谢。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
有时候因为工作需要,或者是出于娱乐目的,需要把手机投屏到电脑,如果只是投屏到电脑就相对简单了。要实现既能投屏还能用电脑操作手机,就稍微有点难度了。今天教大家怎样用电脑控...【详细内容】
2020-07-06   Android  点击:(1)  评论:(0)  加入收藏
诶,我发你的东西看了没有?没有呢,网速真慢。如果你也是 Android 用户,或许会和我这位朋友一样,经历相同的事情,不同手机品牌之间传输文件实在是太麻烦了,最常使用的微信分享不仅要...【详细内容】
2020-07-05   Android  点击:(3)  评论:(0)  加入收藏
背景看了好多android技术博客,写android分层架构的博客越来越多,有mvc、mvp、mvvm、clean等各式各样的,而mvp异常火热,然而每个人对mvp的定义又是不同,写法自然也是千紫万红。目...【详细内容】
2020-07-01   Android  点击:(2)  评论:(0)  加入收藏
项目的长期积累将导致一些资源不被使用,这些资源仍然存在于项目中并且没有被清理。这些无用的资源只会占用空间,不会有任何用处。如何清理这些无用的资源?这是使用下一个工具的...【详细内容】
2020-06-30   Android  点击:(1)  评论:(0)  加入收藏
因为32位的固有限制,不少主流操作系统已经切换到64位环境,比如iOS 11、macOS Catalina等,Windows 10也自v2004版本开始停止向OEM分发32位系统。下面,该Android了。据开发者爆料,A...【详细内容】
2020-06-27   Android  点击:(1)  评论:(0)  加入收藏
1、引言IM在Android上的保活问题经常在即时通讯网的论坛和技术群里被讨论,自从Android 8.0后系统大大降低了后台运行应用的保活容忍度(详见《Android P正式版即将到来:后台应用...【详细内容】
2020-06-25   Android  点击:(0)  评论:(0)  加入收藏
对于那些期待 Surface Duo 的消费者,现在可以通过这篇文章了解下微软自家 Android 应用在设备上运行的效果了。根据外媒 Windows Central 分享的截图,让我们看到了包括 Outloo...【详细内容】
2020-06-22   Android  点击:(0)  评论:(0)  加入收藏
如今几乎所有的Android系统手机都支持面部识别和指纹解锁功能,为了防止因手机丢失而被人盗取隐私和恶意消费,我们首先要做的就是利用系统设置中的安全选项构筑一道防线。开启...【详细内容】
2020-06-15   Android  点击:(3)  评论:(0)  加入收藏
作为OPPO方面今年推出的首款旗舰级产品,Find X2系列自亮相以来就凭借着产品端几无短板的表现,获得了众多消费者的青睐。继今天谷歌方面已向Pixel系列机型正式推送Android 11 B...【详细内容】
2020-06-12   Android  点击:(1)  评论:(0)  加入收藏
中关村在线消息:北京时间6月11日消息,今天凌晨,谷歌正式推送了Android 11的第一个测试版本。在这个测试版本中,人们发现Android 11的许多新特性。其中,Android 11就要求强制应用...【详细内容】
2020-06-12   Android  点击:(1)  评论:(0)  加入收藏
微软刚刚发布了久违的 Android 版 Authenticator 验证器 App 的更新,加入了可允许用户直接在 App 内修改密码等功能改进。今年早些时候,软件巨头已经为 iPhone 版验证器应用推...【详细内容】
2020-06-09   Android  点击:(5)  评论:(0)  加入收藏
前言Android技术迭代更新很快,各种新出的技术和名词也是层出不穷。不知从什么时候开始,总是会时不时听到AndroidX这个名词,这难道又是什么新出技术吗?相信有很多朋友也会存在这...【详细内容】
2020-06-09   Android  点击:(2)  评论:(0)  加入收藏
本次主要给大家带来一个kali实现远程控制手机的例子,包括打开摄像头,定位,查看短信,查看联系人,录音等。意在向大家传授随意点击别发的链接或扫描别人二维码的危害,如有用做其他用...【详细内容】
2020-06-07   Android  点击:(2)  评论:(0)  加入收藏
Android图形显示系统从软件层面到硬件层面主要分为三个部分。1、应用层2、系统层3、硬件层这三部分就像工厂的流水线作业一样,完成了界面内容的展现。 上图便是我对Android图...【详细内容】
2020-06-04   Android  点击:(1)  评论:(0)  加入收藏
eBPF 网络流量工具结合使用内核与用户空间实现来监控设备自上次启动以来的网络使用情况。它提供了额外的功能(如套接字标记、分离前台/后台流量,以及按 UID 划分的防火墙),以根...【详细内容】
2020-06-04   Android  点击:(3)  评论:(0)  加入收藏
日期:2020.06.01说明:简单描述如何编译1 在Module的build.gradle添加编译jar代码task makeJar(type: Copy) { //删除存在的 delete '../CommonLib/libXXX.jar'...【详细内容】
2020-06-02   Android  点击:(6)  评论:(0)  加入收藏
HTTP协议发展至今已经有二十多年的历史,整个发展的趋势主要是两个方向:效率和安全。效率方面,从HTTP1.0的一次请求一个连接,到HTTP1.1的连接复用,到SPDY/HTTP2的多路复用,到QUIC/HTTP3的基于UDP传输,在效率方面越来越高效。...【详细内容】
2020-05-27   Android  点击:(5)  评论:(0)  加入收藏
近日,索尼发布了一款全新的车载中控屏幕设备——XAV-AX5500,官方表示XAV-AX5500提供了强大的声音性能、流畅灵敏的触控屏,并且可以与用户的智能设备无缝集成。规格上...【详细内容】
2020-05-24   Android  点击:(3)  评论:(0)  加入收藏
据外媒消息,Android 11将6GHz以下的网络标记为“5G”,mmWave将标记为“5G+”,但“5Ge”仍为LTE。谷歌更新了Android 11的“功能概述”页面,其中的图标将用于指示手机是否已连接...【详细内容】
2020-05-23   Android  点击:(6)  评论:(0)  加入收藏
前年的时候搞过一点Android逆向,好久没搞了,最近有个哥们让我帮他做个Android逆向的小题目,于是拾起来Android逆向的知识重新来搞搞吧,这个apk十分简单,属于入门级的Android逆向分析程序,所以本文面向的对象主要是想涉足And...【详细内容】
2020-05-19   Android  点击:(1)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条