Android 虚拟机与ClassLoader类加载

本编文章是热修复的入门知识点。

先说一下android的虚拟机运行与java的jvm运行有什么区别,如果有朋友看过我之前写java内存管理的话应该知道jvm的内存管理运行大概是一个什么样,其实android的虚拟机这里暂时称为Dalvik和jvm是非常相似的,因为就是仿着它的特性做的,其中有一些不同。
在这里插入图片描述

jvm是基于栈的运行,一个线程运行到某个方法的时候,首先会方法入虚拟机栈,根据操作数栈进行操作,android的Dalvik也是差不多,但是android是虚拟寄存器。

虚拟寄存器说白了就是块内存,它不是真的CPU的寄存器,从它代替操作数栈的本质和它的名字我们可以很容易理解它的作用,它也是记录着方法的操作指令,但是它可以直接连通CPU来运算,且不用运算完压局部变量表再拉出来运算。首先是块内存,所以有一定存储空间,可以连通CPU来运算,不用类似jvm从栈顶拉出去运算完再拉出来,再压回局部变量表,中间少了不少步骤,但是它的指令稍微就复杂了一些。

在这里插入图片描述

ART与Dalvik

Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT即时编译(Just In Time)
在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。

而ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。

Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART 引入了预先编译机制(Ahead Of Time),在安装时,ART 使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。

ART的本地机器码是在安装的时候翻译的,所以那个时候的APK安装时间是比较慢。
印象中好像还有一点,编译成本地机器码会占据更多的物理内存空间,算是以空间换时间。这次复习很早的资料里没有说这一点,所以这个就算了,那个时候的手机空间可不像现在这样随便用。

Android N的运作方式
ART 使用预先 (AOT) 编译,并且从 Android N混合使用AOT编译,解释和JIT。

1、最初安装应用时不进行任何 AOT 编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT,经过 JIT 编译的方法将会记录到Profile配置文件中。

2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行 AOT 编译。待下次运行时直接使用。

以上差不多是目前android虚拟机的发展历史。

接下来我们来学习一下classLoader
Android类加载器
双亲委托机制
loadClass与findClass

在这里插入图片描述
Log.e(TAG, “Activity.class 由:” + Activity.class.getClassLoader() +" 加载");
Log.e(TAG, “MainActivity.class 由:” + getClassLoader() +" 加载");

//输出: Activity.class 由:java.lang.BootClassLoader@d3052a9 加载

MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file
“/data/app/com.enjoy.enjoyfix-1/base.apk”],nativeLibraryDirectories= [/data/app/com.enjoy.enjoyfix-1/lib/x86, /system/lib, /vendor/lib]]] 加载

抽象类classLoader有两个直接子类,BootClassLoader主要是加载系统层的类,我们主要学习BaseClassLoader的两个子类:Path和Dex
在这里插入图片描述
PathClassLoader 与 DexClassLoader 的共同父类是 BaseDexClassLoader 。

在这里插入图片描述上面的代码执行顺序:首先从内存中找,如果没有的话,使用parent去找,这个parent是bootClasslLoader,这个是专门用来加载系统类的,所以你如果想写个类来替换系统类是不行的,它先加载系统类的。如果这个找不到,才真正从它PathClassLoader 的父类BaseDexClassLoader 去找,找不到再自己去找。

这就是双亲委托机制
1、避免重复加载,父类先找,找不到最后再自己去找,避免父类加载器已经加载了。
2、安全性,防止核心库被篡改。

parent是实例化的时候传进来的,你可能会想:
new PathClassLoader("/sdcard/xx.dex", null) ,能否加载Activity.class?

private Class findBootstrapClassOrNull(String name) {
 return null;
  }

其实不用去深究,谷歌不允许你这么搞。

其实任何ClassLoader子类,都可以重写 loadClass 与 findClass 。一般如果你不想使用双亲委托,则重写loadClass 修改其实现。
而重写 findClass 则表示在双亲委托下,父ClassLoader都找不到Class的情况下,定义自己如何去查找一个Class。
而我们的 PathClassLoader 会自己负责加载 MainActivity 这样的程序中自己编写的类,利用双亲委托父ClassLoader加载Framework中的 Activity 。
说明 PathClassLoader 并没有重写
loadClass ,因此我们可以来看看PathClassLoader中的 findClass 是如何实现的。
在这里插入图片描述
实现非常简单,从 pathList 中查找class。继续查看 DexPathList,把地址传入然后开始实例化Dex,我们看下它干了什么。
在这里插入图片描述
splitDexPath给传入的dex分组,以:号隔开。返回的dexElements是一个数组,一个dex就是一个Element对象,然后在findClass里面使用到:

在这里插入图片描述
这个就很明确了,for循环找到你这个class。

热修复

PathClassLoader 中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有X个dex,则Element数组就有X个元素。
在这里插入图片描述
在 PathClassLoader 中的Element数组为:[patch.dex , classes.dex , classes2.dex]。如果存在Key.class位于patch.dex与classes2.dex中都存在一份,当进行类查找时,循环获得 dexElements 中的DexFile,查找到了Key.class则立即返回,不会再管后续的element中的DexFile是否能加载到Key.class了。

因此实际上,一种热修复实现可以将出现Bug的class单独的制作一份fix.dex文件(补丁包),然后在程序启动时,从服务器下载fix.dex保存到某个路径,再通过fix.dex的文件路径,用其创建 Element 对象,然后将这个 Element 对象插入到我们程序的类加载器 PathClassLoader 的 pathList 中的 dexElements 数组头部。这样在加载出现Bug的class时会优先加载fix.dex中的修复类,从而解决Bug。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值