符合中小企业对网站设计、功能常规化式的企业展示型网站建设
本套餐主要针对企业品牌型网站、中高端设计、前端互动体验...
商城网站建设因基本功能的需求不同费用上面也有很大的差别...
手机微信网站开发、微信官网、微信商城网站...
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类加载
因为对手机伤害大。android手机热修复不能百分百用户修复成功,手机影响极大而且手机很容易出现bug,所以手机厂商不允许热修复。
深入探索Android热修复技术原理这本书主要讲解了Android的热修复中的热部署,冷部署以及资源和so库的修复技巧。全文主要讲Sophix应对以上四个方面的技术解析,不管是自家产品还是业界其他方案的横纵对比,Sophix技术目前都是最优的。
在事件分发流中,通过Hook钩子在事件传送到终点前截获并监控事件的传输,从而处理一些特定干预事件。
Sophix同时使用了热启动的底层替换方案及冷启动的类加载方案,两个方案使用的补丁是相同的。优先热启动。
基本参考InstantRun的实现:构造一个包含所有新资源的新的AssetManager。并在所有之前引用到原来的AssetManager通过反射替换掉。
Sophix不修改AssetManager的引用,构造的补丁包中只包含有新增或有修改变动的资源,在原AssetManager中addAssetPath这个包就可以了。资源包不需要在运行时合成完整包。
本质是对native方法的修复和替换。类似类修复反射注入方式,将补丁so库的路径插入到nativeLibraryDirectories数据最前面。
注意:在5.0之前会有这个问题,5.0之后没有了
我们进行插桩的时机,便是上图中javac之后,dx之前。 另外,任何一个Task,都有input元素和output元素,以及可以设置doFirst闭包,表示执行任务之前先执行一段逻辑,设置doLast,表示执行任务执行之后再执行一段逻辑。
我们的思路是 在java变成class之后,在class变成 dex之前,将class进行ASM插桩。所以,我们要找的 gradle task 是 : transformClassesWithDexBuilderForRelease 或者 transformClassesWithDexBuilderForDebug 给它重写doFirst。 也可以 找到 gradle task : compileReleaseJavaWithJavac 或者 compileDebugJavaWithJavac. 给它重写 doLast。效果相同。
jar文件的插桩:
本文是在Android8.1.0上进行的源码分析,首先明确几个classloader的定义。
dexFile相当于一个classes.dex,而每一个dexFile也是一个Element,热修复的补丁包一定要插到classes.dex中的最前面的一个dex开始查找,因为dexFile是从classes.dex classes2.dex依次向后进行查找的.前面如果找到就不会找后面的类了。
反射工具类:
热修复:
成功使用: