不会还有人不会热修复吧?

news2024/9/17 8:58:56

Class流派原理

基本原理:加载类的时候是找element,每个element对于一个dex。我要把我修复的那个类单独放到dex插入dexlist前面,在你做类加载从前往后找优先从你的dex加载加载的就是你修复后的class.这就是

实现代码

  1. 通过context拿到pathClassLoader,根据你下发的dex生成一个dexclassloader。

  2. 拿到两个的pathlist,在拿到两个pathlist的element,然后把生成的dexclassloader的element放到pathclassloader的element前面。然后把合并后的element赋值给pathclassloader的element

Davlik虚拟机上遇到的问题

unexpectDex崩溃

davlik虚拟机上会抛出unexpectDex崩溃()

业务情况:A引用了待修复的B类(下发的类)

抛出unexpectDex崩溃同时满足的三个条件

抛出这个崩溃需要**「同时」**满足三个条件:

  1. 补丁类不是通过静态类或者instance of的方式被引用
  2. 引用下发补丁的类在dexopt阶段verify成功,引用类被打上了CLASS_ISPERVRIFYIED的标志
  3. 这两个类不在一个dex上

在你app加载被引用类的时候(A引用B,也就是加载B类的时候)会做这样一个校验,如果你同时满足这三个条件就会崩溃

「由于补丁类是单独的放在一个dex中所以第三个条件没法变。只能从1和2入手」

「应用安装的时候需要一个dexopt阶段,会对你的dex进行优化成odex后续运行加载的odex才能运行」

dexopt阶段的过程

「检查静态方法,私有方法,构造函数,虚方法所调用的类是否根当前类在同一个dex中(A在调用上面方法时调用的BCDE类是否和A类在同一个dex上)」

在同一个dex上,虚拟机就会对A类做一些优化并打上CLASS_ISPREVERIFIED标志

比如A引用B。并且A和B在一个dex里的时候A类会打上CLASS_ISPERVRIFYIED标志

何时抛出异常

在之后加载A类(dexopt阶段标记的类)的时候虚拟机会检查Verfiy标记的结果进行反向做verfiy的校验

当校验的时候同时满足上面三个条件的话就不通过抛出unexceptDex异常,只有校验通过才会吧类加载上来

QZone插桩组织preverify方案

这个方案肯定不满足第三个条件,所以只能从第一个或第二个条件下手

QZone从第二个条件入手通过插妆阻止preverify

解决思路:「当上面那些特殊方法(构造函数,静态函数…)调用的是同一个dex上的类会被标志,那么我跨dex访问就不会打上标志。最简单的就是在构造函数里面进行访问跨dex即可,这样不在同一个dex就不会打标志」

实现:

「创建一个空的类放到一个独立的dex上」

「在所有类的构造函数里面都去访问那个独立dex里面空的类,所有的类都存在一个跨dex的访问,所以整个app里面的所有类都不会被打伤标志」

但是独立的dex需要先被加载进来,因为APP的PathClassLoader找不到这个类。利用双亲委派模型机制(加载类的时候先从缓冲中找)先把这个空类加载进来后续就可以访问到这个类了。


「缺点:」

「影响了odex的校验和优化过程存在一个性能的问题」

「降低APP启动性能,运行内存增加」

Qfix提前constclass引用方案

从抛出的第一个条件入手

「针对静态类调用和instanceof这两种方式以外的方式会抛异常」

如果我以静态类来调用补丁类的话即使存在跨dex调用被打伤标志也不会抛出异常,同时classloader加载类的时候只要加载过会优先从缓存里面读利用这个机制。

davlik虚拟机加载类的过程:

先会从dex的缓存里面找如果有就直接返回不会有后续的校验和加载过程,后面加载和校验完成后也会放到dex的缓存里面

实现思路

APP启动的时候把补丁类放进来以后,「提前以静态方式引用补丁类,这个引用不会抛异常(静态类引用方式)同时会让这个补丁类提前加载到虚拟机的缓存中,后面的访问即使是非静态的即使有标志冲突的也不需要进行校验了。可以直接返回后续从缓冲中读到这个类」

实现代码:

  1. 在application最开始的地方用静态类方式加载补丁类,但是我们并不知道要修复哪个类,所以不可能在application里面把所有的类都加载一遍(哈哈哈,不科学)
  2. QFix**「通过nativehook直接调用虚拟机加载类的native方法」**,APP打包的时候保存了各个类的dexId和classId。在运行的时候找到补丁类所在的dexid和classid在jni侧主动调用虚拟机解析class的方法(设置formUnverifedConstant参数为true代表这次的调用是以constantof或者是以instanceof的方式调用进来,这个为true就不会做preverify的校验),这一次调用你的补丁就在缓存里面了,后续使用就直接从缓存里面找就可以,也不需要进行校验了

因为调用的是虚拟机的native方法加载类,所以在不同虚拟机上有较多的适配,同时会有稳定性的问题。分享文档里面说出来在在X86上有问题

Art虚拟机上遇到的问题

不仅仅是下面级联优化的问题,还有其他问题在dex流派上在标注

Art虚拟机上由于方法内联会带来更大的问题,不管是哪个虚拟机在安装阶都有个dex优化的过程

不同安卓版本有不同的odex编译器,早期编译器用的是QuickCompile后面用的比较多的是OptimizingCompare

不同的编译器进行方法内联时有不同的方法条件,并且**「Optiminzing有级联优化操作」**(method1调用method2里面调用method3里面调用method4)如果这些调用的方法都满足虚拟机的内联条件。

最终编译后的method1里面直接包含了method2method3method4的代码(方法

2包含3和4的代码,3包含4的代码),内联的意思是把代码直接写进来而不是通过方法id进行调用

问题

假如ClassA正好要引用你的补丁类,而补丁类之前在虚拟机优化的时候满足内联条件,那么老的方法已经被写到引用类里面了。这时候在下发新的class修复的时候可以正常加载class,但是方法的调用并没有调用到你的新类class上来,因为你的实现已经被写到引用类里面了。就会存在问题

「由于内联,执行流程并未跳转到新的方法里面,引用类里面的方法是用的老的方法」。对于引用类来说用的还是老方法中局部变量表存放的内容 所以查找成员字符串都是用的旧方法的索引。但是新的补丁类索引是可能发生变化的引用类访问的时候就会出现crash出错的问题。

解决方法

「由于级联优化的存在因此把你要修复的类,你的子类,调用你的类都必须整个放到patch里面,下发整个patch,所以整个patch会很大」

Dex流派热修复原理

class是干扰的系统api较为底层所以存在适配和兼容性问题。

后来tinker走上了dex存量热修复的路径

「原理:进行全量dex的替换,但是不可能吧整个dex下发,所以下发的是dex的diff。」

新老dex的diff在服务端生成,通过diff算法:

Sigma用的是比较常见的BsDiff

tinker做的比较深入依据dex结构发明了一个dexdiff算法,让你diff差异包更小,合成效率更高

步骤

  1. 服务端生成了新老dex的diff之后就会生成差异包。差异包会被你的patch进程请求到和本地依据安装的dex进行merge成新的dex也就是通过patch还原成新的dex
  2. 通过新dex创建出一个新的dexclassloader,「把这个新dexclassloader设置成App的pathclassloader的parent。根据双亲委派模型你加载的就是新的dexclassloader,也就是修复后的类」

修复为什么要在独立进程做?

  1. 即使业务进程无线崩溃,patch进程也能修复你的问题
  2. 业务进程可能在做迭代,做合并可能会出现crash
  3. 独立进程中做的话不依赖于主进程启动,其他业务进程的启动也可以吧patch进程拉起来进行统一的修复

注意点

「parch进程」中PathCore合并核心代码中的一些操作「是和Application一起由PathClassLoader加载的」,如果你的pathcore调用了你的业务逻辑没有做解耦的话, 那么这个时候path会加载你的旧业务的类(由pathclassloader加载),由于双亲委派模型后续这些旧业务的类是从pathClassLoader缓存拿的而不是从你patch进程做完合并后的dexclassloader拿的就会出现问题导致调用类和加载类不一致,所以需要进行和业务解耦。

就是如果在生成新的dex替换pathclassloader的parent之前访问了之前的类,那么是由pathClassLoader加载的,就会导致加载的类是旧的dex。而因为有缓存,一直是拿的pathClassLoader加载的类而不是合并后修复完成的dexClassLoader的类

基本的共性问题

dex的热修复有一些基本典型的问题需要解决:

  1. patch的入口和patch的核心业务**「需要和业务进行隔离」**
  2. patch合并需要**「放到独立进程做」**
  3. 「每次打包的mapping会变化」:如果不对混淆进行干预,每次打包的混淆规则是会变化的,所以会导致哪怕是很小的改动也会导致两个包的dex差异非常大所以需要对混淆的mapping进行保存,在打新包的时候apply这个mapping就会保持混淆一致不会导致差异
  4. 「每次打包的分包结果会变化」:如果APP大的话,会存在跨dex访问(针对这种多dex情况哪怕你没有修改也会导致他的分包结果不一样)。所以在打基准包的时候也要把他的分包结果保存下来(打新包时按照这个结果进行分包)
  5. 「patch进程做完patch合并之后,主进程利用patch的时候会立马黑屏或者anr」。虚拟机是不会直接访问dex的有个dexopt阶段(应用安装时候做的,动态加载dex时也会做这个阶段)。「dexopt是由系统触发的。所以会黑屏就是因为你的主进程直接用得动态加载的dex触发了dexopt导致黑屏。所以在patch进程合并完新的dex之后应该立刻去触发dexopt.」

如何触发dexopt

直接手动new一个dexclassloader,然后虚拟机就会做全量的dexopt在独立进程中(虽然dexopt过程放到了独立的patch进程做,但是还是会存在部分anr,后面问题在列出)

Art dex2oat对热修复影响

dex2oat是对dex进行编译的一个进程。在art虚拟机上你的dex是需要编译成机器码以后才能被虚拟机加载和运行的

dex2oat编译模式

编译过程有十几种模式,比较关心的只有三种:

  1. 「interpret only」:该模式在first boot或者install的时候(第一次启动或者安装)进行。只会做verify,代码还是解释执行,不做机器码的编译操作。「性能是和davlik虚拟机保持一致」
  2. 「speed」:该模式在new DexClassLoader的时候触发。会做**「全量的机器码编译」**
  3. 「speed profile」:该模式在系统做oat升级的时候或者混合编译(有一个background的dexopt在系统idle的时候会唤醒做dexopt)的时候,他只编译你app对应的profile里保存的热代码,只编译这部分热代码。

全量编译机器码:art虚拟机为了提高性能,会对代码做全量机器码编译。这个过程会在ClassLoader加载类的时候发现传入进来的opt路径上不存在odex文件的时候就会自动触发。因为是第一次newclassloader之前没有做过编译也就没有odex文件所以就会做全量编译

解决方案演进

  • 所以如果主进程启动直接做全量编译直接挂
  • 如果在patch进行全量编译,由于dex2oat过程非常长在部分机型达到几分钟好的机型上也得等二三十秒而且非常占资源就有可能你的整个apt过程没做完来不及。比如用户总是点开新闻看几秒就杀掉导致你一直做不完优化,修复就一直用不上可能会拖慢主进程导致ARN
  • tinker的方案:所以**「patch进程先进行轻量编译,如果做完了就用,做不完的话应用老的先让用户能用,并且避免全量编译(你都用的是老的了,就没必要做过多的全量编译了可能会导致占用资源过多业务进程也卡)」**。如果patch做轻量量编译可以用就用,不能用避免全量编译先让用户跑起来(如何避免全量编译稍后介绍)

轻量编译也有一定耗时,导致首次启动慢。而且你轻量编译之后你的独立进程也是无限制的在做全量编译可能抢资源导致主进程拖满然后ANR(概率较小tinker准备忽略,因为APP性能足够好)

  • App在前台运行也有可能会导致patch进程抢占资源导致anr。所以在这个基础上面进一步优化:patch进程拉倒patch后**「先进行轻量编译」「主进程优先用轻量编译后的patch。找合适的时机做全量编译(合适时机:我的APP退倒后台,其他APP在前台的时候/锁屏)系统做background dexopt也是系统不用的时候去做」**
避免全量编译

有三种方案:

  1. Atlas方案:在Native侧修改Art虚拟机的执行模式,直接用DexFile底层接口加载Dex文件(影响同进程下的dex加载并且DexFile在O版本以上被废弃)存在可用性和兼容问题
  2. Tbs方案:发现如果在new DexClassLoader的时候optDir传入为null的时候会置空oat_location就不会对你做全量编译(8.0上系统会忽略你传入的这个路径)
  3. Tinker方案:dexopt就是执行虚拟机的一个命令行,所以在你系统触发全量编译之前手动去调用dex2oat命令执行编译方式intercept-only只做清凉的编译。「先用你轻量编译达到的结果首次启动或者首次安装完以后的运行效果和虚拟机一样的效果让他先跑起来也是出现问题之后的优化方案」

Android N混合编译对热修复影响

混合编译:AOT,解释,JIT三种模式并存。

用户真正使用到的类可能只有很少部分,我们为什么要为了百分之二三十的代码去做全量编译呢?没有必要

N之前的Art虚拟机上安装是做的全量编译,所以安装的时候会等很久,做Jit及时编译又会很慢

在N上解决了这个问题通过混合编译缩短安装时间,系统OAT升级更快:「安装和首次启动用intervept-only的方式没有编译(和davlik虚拟机一样的效果)」,对哪些代码做编译,什么时候做编译呢:来看N上的增量编译过程:

Android N虚拟机增量编译过程

虚拟机会在APP代码运行过程中收集运行到的代码放到profile文件上,系统会通过jobSchedule启动BackgroundDexOptService。这个Service会在灭屏/充电的状态下启动。晚上睡觉或者其他手机空闲的情况时就会启「动任务把收集到的代码给编译好(这些热代码是经常跑的所以会快)。后面启动的时候就会很块,通过这种方式给APP做增量的编译。编译完之后会生成base.odex和base。art(称之为App的image)

虚拟机认为这是热代码所以在你APP启动的时候就提前帮你吧这部分代码加载起来。在ClassLoader创建ClassLinker的时候一次性加载到dexcache上

所以就是你刚启动Application里面什么都还没做就已经加载了一些类(以前编译好的热代码)

Art混合编译对热修复的三种情况影响分析

  • 要修复的类不在appimage中:「Dex流派采用的是双亲委派预期的是通过parent去加载如果你要修复的类正好不在appimage里面也就是没有被提前加载那么这个机制就没错补丁可以生效」
  • 要修复的类有一部分在appimage中:「如果你有一部分在appimage里面。就导致一部分用的新的,一部分用的老的。这样访问就会出现地址错乱出现crash」
  • 要修复的类已经在appimage中:「如果你全部都在appimage里面,你修复的这些正好之前都被收集了,那么你这个patch是不会生效的」

解决方案

「在N以上的设备抛弃设置parent的模式,做全量的直接替换吊我们的pathclassloader而不是设置他的parent」

实现步骤

  1. 创建补丁dex的DexClassLoader
  2. 通过contextimpl拿到loadkedApk在拿到持有的PathClassLoader对象。就是系统帮我们创建的pathClassloader
  3. 通过反射替换这个属性为补丁的classloader

原理:「因为系统的appimage提前加载是加载到系统的pathClassloader缓存上的」。而我们后续运行的是用我们替换的classloader,所以这个新的classloader上没有了appimage的存在了

影响:由于没有了appimage的存在所以性能上会有牺牲但是是能达到修复的目的,统计下来影响是非常小的

结尾

谢谢大家看完,如有不恰当、不充分的地方,欢迎大家指正。针对这块知识点我在学习过程中,进行了详细的整理梳理了一些学习笔记,有需要参考学习的小伙伴可以 点击这里查看获取方式 传送门直达 !!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/68722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt跨平台截图工具

Qt跨平台截图工具 文章目录Qt跨平台截图工具1、概述2、实现效果3、软件构成4、关键代码5、源代码更多精彩内容👉个人内容分类汇总 👈👉Qt自定义模块、工具👈 1、概述 Qt版本:V5.12.5兼容系统: Windows&…

2022,记录与华为的这场会议

一、数据治理团体标准发布会 11月26日,中国计算机用户协会信息科技审计分会联合华为与擎创科技共同举办了“金融行业运维数据治理团体标准应用研讨暨2022年度调研报告线上发布会”。来自国家开发银行、中国建设银行、中国邮政储蓄银行、招商银行、兴业银行、中信银行…

【LeetCode_字符串_逻辑分析】9. 回文数

目录考察点第一次:2022年12月7日10:16:33解题思路代码展示题目描述给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左…

340页11万字智慧政务大数据资源平台大数据底座数据治理建设方案

目 录 第一章 项目概况 1.1 项目名称 1.2 项目单位 1.3 项目建设依据 1.4 项目建设内容和目标 1.4.1 建设内容 1.4.2 建设目标 1.5 项目投资估算及建设周期 1.5.1 项目投资估算 1.5.2 服务周期 第二章 现状 2.1 项目单位概况 2.1.1 单位职责、内设及下属机构、人员…

【配准图像】

MU-Net: A MULTISCALE UNSUPERVISED NETWORK FOR REMOTE SENSING IMAGE REGISTRATION (MU-Net:一种多尺度无监督遥感图像配准网络) 多传感器或多模态图像对的配准是许多遥感应用的基础性任务。为了实现高精度、低成本的遥感图像配准&#x…

彻底搞懂JS原型与原型链

说到JavaScript的原型和原型链,相关文章已有不少,但是大都晦涩难懂。本文将换一个角度出发,先理解原型和原型链是什么,有什么作用,再去分析那些令人头疼的关系。 一、引用类型皆为对象 原型和原型链都是来源于对象而…

浅谈Linux内核编程规范与代码风格

1 缩进 Tab的宽度是八个字符,因此缩进的宽度也是八个字符。有些异教徒想让缩进变成四个字符,甚至是两个字符的宽度,这些人和那些把 PI 定义为 3 的人是一个路子的。 注意:缩进的全部意义在于清晰地定义语句块的开始与结束&#…

《MongoDB》Mongo Shell中的基本操作-删除操作一览

前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言~博主看到后会去代替大家踩坑的~ 主页: oliver尹的主页 格言: 跌倒了爬起来就好~ 来个关注吧,点个赞…

分布式事务,单JVM进程与多数据库,分布式事务技术选型,0-1过程,代码全。

由于很多小白程序员在单一JVM进程配合多数据库的架构环境中,总是考虑一主多从的mysql集群环境。还不知道mysql集群数据库按照表纵向分割以后,也是可以走数据库使用事务的。那么这里使用到的就是分布式事务,XA协议。现在大部分主流的数据库都支持XA协议。这里不用太多废话,直…

【Web智能聊天客服】之JavaScript、jQuery、AJAX讲解及实例(超详细必看 附源码)

觉得有帮助请点赞关注收藏~~~ 一、JavaScript基础 Javascript是网页编程语言&#xff0c;决定网页元素的动作。HTML页面中通过<script></script>指定Javascript内容&#xff0c;通过//或者 /* */执行代码的备注功能&#xff0c;并且区分大小写。 1&#xff1a;变…

《视觉SLAM十四讲》示例程序编译报错处理(上)

高翔博士《视觉SLAM十四讲》这本书中的代码很不错&#xff0c;适合初学者。可惜有一些可能因为版本的问题会报错&#xff0c;本文总结一下我遇到的问题。 在slambook2/3rdparty文件夹git submodule update&#xff0c;这个版本是和书中的版本一致的。但我已经重新安装了新版Ei…

Webpack中的高级特性

自从webpack4以后&#xff0c;官方帮我们集成了很多特性&#xff0c;比如在生产模式下代码压缩自动开启等&#xff0c;这篇文章我们一起来探讨一下webpack给我们提供的高级特性助力开发。 探索webpack的高级特性 特性&#xff1a;treeShaking 顾名思义treeShaking&#xff0…

C++ Reference: Standard C++ Library reference: Containers: deque: deque: swap

C官网参考里链接&#xff1a;https://cplusplus.com/reference/deque/deque/swap-free/ 函数模板 <deque> std::swap (deque) template <class T, class Alloc> void swap (deque<T,Alloc>& x, deque<T,Alloc>& y); 交换两个双端队列容器的…

【ESP32调试-快速入门】

文章目录ESP32调试一. 环境搭建二. 运行OpenOCD1. 烧入blink2. 找到芯片型号对应的脚本文件&#xff0c;并运行脚本命令三. 运行GDBESP32调试 一. 环境搭建 ESP_IDF环境搭建 二. 运行OpenOCD 1. 烧入blink 如&#xff1a;安装环境中的examples中的blink 路劲&#xff1a;安装…

华为机试 - 探索地块建立

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 给一块n*m的地块&#xff0c;相当于n*m的二维数组&#xff0c;每个元素的值表示这个小地块的发电量&#xff1b; 求在这块地上建立正方形的边长为c的发电站&#xff0c;发电量满足目标电量k的地块数量…

汽车保养app开发,扩充汽车服务市场发展商机

从汽车市场规模来看&#xff0c;从2017年开始始终保持增长的发展趋势&#xff0c;在2021年市场规模达到140877.18亿元。互联网时代发展下&#xff0c;汽车后市场大力推广电子商务&#xff0c;将互联网技术与汽车保养服务相结合是汽车服务行业强大的必由之路。二者的结合可以让消…

centos7下搭建rabbitmq的开发环境

我们在项目开发的时候都不可避免的会有异步化的问题,比较好的解决方案就是使用消息队列,可供选择的队列产品也有很多,比如轻量级的redis, 当然还有重量级的专业产品rabbitmq,rabbitmq好就好在是用erlang(二郎神)开发的,它那天生的OTP并行计算框架,轻而易举的进程间通…

阿里云ssl免费证书申请

目录为什么申请SSL证书SSL证书申请支持的域名类型ssl免费证书申请过程为什么申请SSL证书 由于web服务部署需要使用https安全协议&#xff0c;因此需要申请相应域名的SSL证书用于部署。测试阶段&#xff0c;为节省成本&#xff0c;使用阿里云提供的免费SSL证书。 SSL证书申请支…

在Web服务器(IIS)上安装SSL证书并绑定网站

以windows server 2016为例 一、安装中间证书 1.下载中间证书文件 如果是RSA加密算法类的&#xff0c;下载此处。 如果是ECC加密算法类的&#xff0c;下载此处。 2.安装 双击下载好的文件进行安装&#xff0c;注意&#xff0c;安装过程中&#xff0c;存储位置要设”为本地…

搜题接口系统

搜题接口系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xff09; 题库…