Android中类的加载以及应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yoonerloop/article/details/87897458

将重点介绍一下三个方面:

  1. 类的加载机制
  2. 热修复原理
  3. 增量更新原理

一、类加载器的分类

1、Java ClassLoader

(1)启动类加载器:Bootstrap ClassLoader

用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来加载核心类库。该加载器无法直接获取。加载存放在JAVA_HOME\lib目录中的类。

(2)扩展类加载器:Extension ClassLoader

负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库

(3)应用程序类加载器:App/System ClassLoader

负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器,可以通过ClassLoader的静态方法getSystemLoader()来获取系统类的加载器。如果没有特别指定,则用户自定义的类的加载器都以此类加载器作为父类。

2、Android ClassLoader

(1)BootClassLoader,它是 Android 中最顶层的 ClassLoader,继承自ClassLoader,C代码编写,不能被继承和修改。

(2)PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。

(3)DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。

他们都继承自BaseDexClassLoader,他又继承自ClassLoader。PathClassLoader 和 DexClassLoader 最大的区别就是 DexClassLoader 可以加载 外置dex文件,这是因为 PathClassLoader 构造方法中像上传递时第二个参数传了null,这个参数代表的是 dex 优化后的路径,DexPathList 在生成 Element数组 时会判断这个参数是否为 null,如果为 null 就使用系统默认路径 /data/dalvik-cache,这也是导致如果要加载外置dex文件只能使用 DexClassLoader 的原因。

Android 中主要使用的 ClassLoader 有 PathClassLoader 和 DexClassLoader,它们都继承自 BaseDexClassLoader,BaseDexClassLoader 中维护了一个 DexPathList, DexPathList 中又维护这一个 Element 数组,这个数组中 Element元素 其实就是 Dex 文件,所以子类可以通过父类findClass方法获得这个dex集合。

二、类的加载机制

1、 双亲委派模型

在Java中为了保证一个类的唯一性使用了双亲委派模型,也就是说如果要加载一个类首先会委托给自己的父加载器去完成,父加载器会再向上委托,直到最顶层的类加载器,如果父加载器没有找个要加载的类,父类的子类才会一步步尝试自己去加载,如果再加载失败测抛出异常,这样就保证了加载的类都是一个类。

2、如何保证类的唯一性

JVM 在判定两个 class 是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM 才认为这两个 class 是相同的。就算两个 class 是同一份 class 字节码,如果被两个不同的 ClassLoader 实例所加载,JVM 也会认为它们是两个不同 class。

三、热修复技术

就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候,系统会选择哪个类进行加载呢?一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找到类则返回,如果找不到从下一个dex文件继续查找。理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类。热补丁的方案,把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面,在遍历时候获取到最前面的dex然后返回,后面的就不管了。目前较火的热修复方案大致分为两派,分别是:

(1)阿里系:DeXposed、andfix:从底层二进制入手c语言替换方法。

(2)腾讯系:tinker:从java加载机制入手,替换class文件。

1、QQ空间超级补丁技术

基于DEX分包方案,使用了多DEX加载的原理,大致的过程就是:把bug方法修复以后,放到一个单独的dex里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。当patch.dex中包含Test.class时就会优先加载,在后续的DEX中遇到Test.class的话就会直接返回而不去加载,这样就达到了修复的目的。详细步骤如下:

(1)通过获取到当前应用的Classloader,即为BaseDexClassloader;

(2)通过反射获取到他的DexPathList属性对象pathList;

(3)通过反射调用pathList的dexElements方法把patch.dex转化为Element[];

(4)两个Element[]进行合并,把patch.dex放到最前面去;

(5)根据类的加载机制,加载Element[],达到修复目的。

特点是产很灵活,对开发者透明,但是不支持即时生效,必须通过重启才能生效。另外当插入的dex比较多的时候影响启动性能比较大。

2、微信Tinker修复方案

微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,区别在于不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX文件,以达到修复的目的。

特点是稳定性比较高,与超级补丁技术一样,不支持即时生效,必须通过重启应用的方式才能生效。对于多DEX的应用来说,如果修改了多个DEX文件,就需要下发多个patch.dex与对应的classes.dex进行合并操作时这种情况会更严重,因此合并过程的失败率也会更高。

3、阿里系AndFix修复方案

AndFix不同于QQ空间超级补丁技术和微信Tinker通过增加或替换整个dex的方案,提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。AndFix对ART设备同样支持,具体的过程与Dalvik相似。替换流程如下:

Dalvik设备——>Native层找到被替换的类——>将类的状态设置为初始化完毕——>得到新旧方法的指针——>操作指针,指针指向新的替换方法——>完成新的方法替换。

特点是及时性强,不需要重启手机,生成的差量包小,性能损耗小,缺点是是不支持新增字段,也不支持对资源的替换,和ROM关系很大,一旦厂商修改ROM下发就失败了。

4、三个热修复技术比较

关于热修复微信Tinker的接入请参考:

https://blog.csdn.net/yoonerloop/article/details/80246265

四、增量更新

自从 Android 4.1 开始, Google Play 引入了应用程序的增量更新功能,App使用该升级方式,可节省约2/3的流量。在APP应用商城上我们可以看到很多APP显示省流量更新,这就是使用了增量更新技术。如下:

1、原理

old.apk  new.apk,服务端比较两个安装包的差异生成增量包patch,客户端下载增量包patch,下载完成后和olde.apk合成新的new.apk再安装。这样可以减少很多下载中的流量。比较有名的开源库为bsdiff,使用bsdiff可以生成增量包和合并增量包。这个增量更新的过程需要客户端和服务端同时支持。

2、注意事项

(1)增量包patch可以在dos命令生成也可以服务端生成。

(2)差分包的版本问题,一般在服务器会保存几个拆分包,如果用户当前为1.0版本,很久没更新,一下子需要升级到6.0这时候就不合适用拆分包,可能1.0到6.0的拆分包比直接下载还大,这时候需要全量更新了,这种情况需要在代码中进行判断,版本差异比较下,变化比较下使用差分包,版本差异很大或者架构调整很多适合用全量包。所有一个APP集成增量更新的同时也需要集成全量更新功能。

(3)集成增量更新需要用到NDK技术,集成步骤请查看相关文档。

 

参考文献:

https://blog.csdn.net/qq_31530015/article/details/51785228?locationNum=11

 

 

 

猜你喜欢

转载自blog.csdn.net/yoonerloop/article/details/87897458