新网创想网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
前文: 【Java】ClassLoader与双亲委派机制
创新互联专注为客户提供全方位的互联网综合服务,包含不限于成都网站设计、做网站、西湖网络推广、小程序设计、西湖网络营销、西湖企业策划、西湖品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联为所有大学生创业者提供西湖建站搭建服务,24小时服务热线:18980820575,官方网址:www.cdcxhl.com
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文件放在数组的前面,使其加载修改后的类,从而实现热修复。
根据以上原理,写下这个工具类,有效性待验证:
1、Applications
Android装配一个核心应用程序集合,包括电子邮件客户端、SMS程序、日历、地图、浏览器、联系人和其他设置。所有应用程序都是用Java编 程语言写的。更加丰富的应用程序有待我们去开发! 从上面我们知道Android的架构是分层的,非常清晰,分工很明确。Android本身是一套软件堆迭(Software Stack),或称为「软件迭层架构」,迭层主要分成三层:操作系统、中间件、应用程序。从上面我们也看到了开源的力量,一个个熟悉的开源软件在这里贡献 了自己的一份力量。
2、Linux Kernel
Android基于Linux 2.6提供核心系统服务,例如:安全、内存管理、进程管理、网络堆栈、驱动模型。Linux Kernel也作为硬件和软件之间的抽象层,它隐藏具体硬件细节而为上层提供统一的服务。 如果你学过计算机网络知道OSI/RM,就会知道分层的好处就是使用下层提供的服务而为上层提供统一的服务,屏蔽本层及以下层的差异,当本层及以下层发生 了变化不会影响到上层。也就是说各层各尽其职,各层提供固定的SAP(Service Access Point),专业点可以说是高内聚、低耦合。 如果你只是做Android应用开发,就不需要深入了解Linux Kernel层。
3、Application Framework
通过提供开放的开发平台,Android使开发者能够编制极其丰富和新颖的应用程序。开发者可以自由地利用设备硬件优势、访问位置信息、运行后台服 务、设置闹钟、向状态栏添加通知等等,很多很多。 开发者可以完全使用核心应用程序所使用的框架APIs。应用程序的体系结构旨在简化组件的重用,任何应用程序都能发布他的功能且任何其他应用程序可以使用 这些功能(需要服从框架执行的安全限制)。这一机制允许用户替换组件。 所有的应用程序其实是一组服务和系统,包括: 视图(View)--丰富的、可扩展的视图集合,可用于构建一个应用程序。包括包括列表、网格、文本框、按钮,甚至是内嵌的网页浏览器 内容提供者(Content Providers)--使应用程序能访问其他应用程序(如通讯录)的数据,或共享自己的数据 资源管理器(Resource Manager)--提供访问非代码资源,如本地化字符串、图形和布局文件 通知管理器(Notification Manager)--使所有的应用程序能够在状态栏显示自定义警告 活动管理器(Activity Manager)--管理应用程序生命周期,提供通用的导航回退功能。
4、Libraries
Android包含一个C/C++库的集合,供Android系统的各个组件使用。这些功能通过Android的应用程序框架 (application framework)暴露给开发者。下面列出一些核心库: 系统C库--标准C系统库(libc)的BSD衍生,调整为基于嵌入式Linux设备 媒体库--基于PacketVideo的OpenCORE。这些库支持播放和录制许多流行的音频和视频格式,以及静态图像文件,包括MPEG4、 H.264、 MP3、 AAC、 AMR、JPG、 PNG 界面管理--管理访问显示子系统和无缝组合多个应用程序的二维和三维图形层 LibWebCore--新式的Web浏览器引擎,驱动Android 浏览器和内嵌的web视图 SGL--基本的2D图形引擎 3D库--基于OpenGL ES 1.0 APIs的实现。库使用硬件3D加速或包含高度优化的3D软件光栅 FreeType --位图和矢量字体渲染 SQLite --所有应用程序都可以使用的强大而轻量级的关系数据库引擎。
双亲委托机制
类在进行类加载的时候,把加载任务托管给父类加载器,如能加载成功,则返回,否则依次向子类加载器递归尝试类加载。
意义:
①避免类的重复加载,父类加载已加载该类时,子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))待了解。
1、Android客户端应用程序
如新浪微博、网银客户端、凡客、淘宝客户端,快盘客户端。Android在这里的应用还是界面层的东西为主。核心还在WEB。客户端界面很重要,用户体验度很重要。从应用需求上来讲,几乎大一点的网站,都需要有手机客户端程序。
2、Android通用类程序
如基于LBS(基于位置的服务)的应用(这类一般会嵌入到客户端应用程序中),流媒体播放应用。由于移动设备的方便便捷、3G、4G网络的发展,这类应用有不错的前景。
3、Android游戏开发
需要掌握的游戏引擎LGame,游戏框架等。手机上的游戏会是一大块内容,有前途。
4、Android底层开发
需要掌握C、Linux等较底层的东西,发展方向应该是驱动、协议开发,嵌入式开发。
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。(这段话是抄来的)
咳咳,大体的意思的就是Collection是所有List的国际标准了,那我们可以看一下Collection接口需要记一下的方法。
这个方法是用来遍历Collection中所有的元素的,用法如下:
这个可以遍历出一个Collection中所有的元素。
List接口是有序的Collection接口的实现。此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,类似于Java的数组。
顺序是 List 重要的特性;它可保证元素按照规定的顺序排列。
List 为 Collection 添加了大量方法,以便我们在 List 中部插入和删除元素(只推荐对 LinkedList 这样做)。List 也会生成一个 ListIterator(列表反复器),利用它可在一个列表里朝两个方向遍历,同时插入和删除位于列表中部的元素(同样地,只建议对 LinkedList 这样做)
ArrayList 由一个数组后推得到的 List。作为一个常规用途的对象容器使用,用于替换原先的 Vector。允许我们快速访问元素,但在从列表中部插入和删除元素时,速度却嫌稍慢。一般只应该用 ListIterator 对一个 ArrayList 进行向前和向后遍历,不要用它删除和插入元素;与 LinkedList 相比,它的效率要低许多
LinkedList 提供优化的顺序访问性能,同时可以高效率地在列表中部进行插入和删除操作。但在进行随机访问时,速度却相当慢,此时应换用 ArrayList。也提供了 addFirst(),addLast(),getFirst(),getLast(),removeFirst()以及 removeLast() (未在任何接口或基础类中定义),以便将其作为一个规格、队列以及一个双向队列使用
Vector和ArrayList
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/xxx@classes.dex 。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类加载