您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > 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   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
今天面试遇到同学说做过内存优化,于是我一般都会问那 Bitmap 的像素内存存在哪?大多数同学都回答在 java heap 里面,就比较尴尬,理论上你做内存优化,如果连图片这个内存大户内存...【详细内容】
2021-12-23  Tags: Android  点击:(8)  评论:(0)  加入收藏
Android logcat日志封装logcat痛点在Android开发中使用logcat非常频繁,logcat能帮我们定位问题,但是在日常使用中发现每次使用都需要传递tag,并且会遇到输出频率很高的log,在多...【详细内容】
2021-12-22  Tags: Android  点击:(8)  评论:(0)  加入收藏
对项目的基本介绍 1.整个框架主要是给MVVM框架使用的,自己写完interface接口后,通过自定义的注解就能自动生成接口方法 2.用Kotlin的Flow去代替Rxjava,因为我发现RxJava功能很...【详细内容】
2021-12-08  Tags: Android  点击:(17)  评论:(0)  加入收藏
前言在Android开发过程中,有些时候会根据需要引用别的项目到当前项目里面,而且以Module形式引用。所以本篇博文就来分享一下怎么以Module形式引用别的项目到当前项目中,方便开...【详细内容】
2021-12-07  Tags: Android  点击:(22)  评论:(0)  加入收藏
新型Android恶意木马程序伪装成数十款街机、射击和策略游戏,通过华为应用市场AppGallery进行分发,从而窃取设备信息和用户的手机号码,全球目前至少有930万台Android设备被该恶...【详细内容】
2021-12-01  Tags: Android  点击:(24)  评论:(0)  加入收藏
作者:fundroid这篇文章偏阅读一些,大家可以了解下 Android 的一些最新动向。每年9/10月份 Google 都会举行约为期2天的 Android Dev Summit,在活动上 Google 的技术专家们会分...【详细内容】
2021-11-30  Tags: Android  点击:(15)  评论:(0)  加入收藏
一、 准备工作1、安装JDK,下载地址(可能需要一个oracle账号,大家百度一下或者自行注册一个就行。尽可能选择8或者11,这两个是长期版本)Java SE | Oracle Technology Network | Or...【详细内容】
2021-11-23  Tags: Android  点击:(28)  评论:(0)  加入收藏
如果你是一名忠实的Android玩家,那么可能会知道,今年的Android 12系统在版本规划上与“往届”相比可以说是很有些特殊。具体来说,除了前段时间刚刚推出正式版的Android 12外,谷...【详细内容】
2021-11-10  Tags: Android  点击:(24)  评论:(0)  加入收藏
使用Maven Publish Plugin插件。(官方支持)一、在Library的build.gradle中配置plugins { id 'com.android.library' id 'kotlin-android' id 'k...【详细内容】
2021-11-05  Tags: Android  点击:(37)  评论:(0)  加入收藏
今年5月,谷歌推出了Android 12,这是原生安卓系统史上最大的设计变化,10月4日,谷歌推出全新的Android12正式版本,并且宣布会在今年晚些时候应用于安卓设备,对比Android11的挤牙膏式...【详细内容】
2021-10-29  Tags: Android  点击:(125)  评论:(0)  加入收藏
▌简易百科推荐
今天面试遇到同学说做过内存优化,于是我一般都会问那 Bitmap 的像素内存存在哪?大多数同学都回答在 java heap 里面,就比较尴尬,理论上你做内存优化,如果连图片这个内存大户内存...【详细内容】
2021-12-23  像程序那样思考    Tags:Android开发   点击:(8)  评论:(0)  加入收藏
Android logcat日志封装logcat痛点在Android开发中使用logcat非常频繁,logcat能帮我们定位问题,但是在日常使用中发现每次使用都需要传递tag,并且会遇到输出频率很高的log,在多...【详细内容】
2021-12-22  YuCoding    Tags:Android   点击:(8)  评论:(0)  加入收藏
对项目的基本介绍 1.整个框架主要是给MVVM框架使用的,自己写完interface接口后,通过自定义的注解就能自动生成接口方法 2.用Kotlin的Flow去代替Rxjava,因为我发现RxJava功能很...【详细内容】
2021-12-08  网易Leo    Tags:Android开发   点击:(17)  评论:(0)  加入收藏
前言在Android开发过程中,有些时候会根据需要引用别的项目到当前项目里面,而且以Module形式引用。所以本篇博文就来分享一下怎么以Module形式引用别的项目到当前项目中,方便开...【详细内容】
2021-12-07  网易Leo    Tags:Android开发   点击:(22)  评论:(0)  加入收藏
作者:fundroid这篇文章偏阅读一些,大家可以了解下 Android 的一些最新动向。每年9/10月份 Google 都会举行约为期2天的 Android Dev Summit,在活动上 Google 的技术专家们会分...【详细内容】
2021-11-30  像程序那样思考    Tags:Android开发   点击:(15)  评论:(0)  加入收藏
一、 准备工作1、安装JDK,下载地址(可能需要一个oracle账号,大家百度一下或者自行注册一个就行。尽可能选择8或者11,这两个是长期版本)Java SE | Oracle Technology Network | Or...【详细内容】
2021-11-23  永沧    Tags:Android   点击:(28)  评论:(0)  加入收藏
使用Maven Publish Plugin插件。(官方支持)一、在Library的build.gradle中配置plugins { id 'com.android.library' id 'kotlin-android' id 'k...【详细内容】
2021-11-05  羊城小阳    Tags:Android   点击:(37)  评论:(0)  加入收藏
谷歌离推出Play Store应用程序的新数据隐私部分又近了一步。应用程序开发人员现在可以通过谷歌在Play控制台的新 "数据安全表 "填写相关细节。该公司表示,所需信息将从2022年...【详细内容】
2021-10-20    中关村在线  Tags:安卓   点击:(58)  评论:(0)  加入收藏
架构究竟是什么?如何更好的理解架构?我们知道一个APP通常是由class组成,而这些class之间如何组合,相互之间又如何产生作用,就是影响这个APP的关键点。细分的话我们可以将其分为类...【详细内容】
2021-09-17  像程序那样思考    Tags:Android架构   点击:(52)  评论:(0)  加入收藏
概述当Android应用程序需要访问设备上的敏感资源时,应用程序开发人员会使用权限模型。虽然该模型使用起来非常简单,但开发人员在使用权限时容易出错,从而导致安全漏洞。本文中,...【详细内容】
2021-09-07  SecTr安全团队    Tags:Android开发   点击:(66)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条