Android每周一轮子:Nvwa(热修复)

news2024/9/25 9:30:59

前言

(废话) 最近发现了一个问题,一些平时博客写的很多的程序员,反倒在日常的工作中,却是业务写的很一般,只会摆理论的人,甚至还跑出来教别人如何找工作,如何做架构,其实自己都没搞明白。但是受众的分层导致了输出者的分层,教出清北学生的老师并不一定来自比这更好的学校,因此,对于博客的输出,一个是作为对自己学习的一个记录,非常仔细的梳理可以非常方便的让我们在需要的时候拿起来,再就是即使这个知识现在不用,无法深入下去,可能会遗忘的比较快,其细节可能会忘记,但是核心思想我们还是有印象的,再就是站在读者的角度上来看,由于读者的差异性,我们的博客在保证无误的前提下,一定是可以帮助到很多同学的,本着这些原则,一周一篇的输出,希望可以坚持下去。

(正题) 最近在做热修复的相关调研,接着博客的专题,可以好好的发一波文章了,今天要分析的是ClassLoader方案最简单的一个Nvwa,本篇文章将会从class查找过程到Nvwa的实现,以及在实现的时候解决了什么问题,这几个方面展开,逐步讲解。

基础使用

初始化

Nuwa.init(this); 

装载补丁包

Nuwa.loadPatch(this,patchFile) 

源码分析

类的查找

对于类的加载,在通过DexClassLoader进行加载的时候,通过DexPathList进行加载,其中维护了一个Element的数组,在查找的类的时候,会遍历数组查找类,如果找到则返回。对于数组遍历查找的代码如下所示。

public Class findClass(String name) {for (Element element : dexElements) {DexFile dex = element.dexFile;if (dex != null) {Class clazz = dex.loadClassBinaryName(name, definingContext);if (clazz != null) {return clazz;}}}return null;
} 
  • Nvwa的初始化
public static void init(Context context) {File dexDir = new File(context.getFilesDir(), DEX_DIR);dexDir.mkdir();String dexPath = null;try {//拷贝Asset目录下的Hack.apk到指定路径dexPath = AssetUtils.copyAsset(context, HACK_DEX, dexDir);} catch (IOException e) {e.printStackTrace();}//从拷贝后的指定路径加载apkloadPatch(context, dexPath);
} 

在nvwaw的init方法中进行的操作是将asset中的一个hack.apk拷贝出来,然后将其作为补丁进行装载。

  • 补丁的加载
public static void loadPatch(Context context, String dexPath) {if (context == null) {return;}if (!new File(dexPath).exists()) {return;}File dexOptDir = new File(context.getFilesDir(), DEX_OPT_DIR);dexOptDir.mkdir();try {DexUtils.injectDexAtFirst(dexPath, dexOptDir.getAbsolutePath());} catch (Exception e) {e.printStackTrace();}
} 

由上面代码,可以看出核心的实现在对DexUtilsinjectDexAtFirst调用上。

public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));Object newDexElements = getDexElements(getPathList(dexClassLoader));Object allDexElements = combineArray(newDexElements, baseDexElements);Object pathList = getPathList(getPathClassLoader());ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);
} 

将两个Dex进行合并,将补丁dex塞在数组的前面,然后通过反射的方式设置进去,通过这种方式,根据上面的类加载逻辑,可以知道,对于类的加载是从数组的最开始的位置进行查找加载的,当前面的dex查找到相应的类之后,就会停止后面的查找,这样,我们通过补丁的替换的类就会生效。

private static Object combineArray(Object firstArray, Object secondArray) {Class<?> localClass = firstArray.getClass().getComponentType();int firstArrayLength = Array.getLength(firstArray);int allLength = firstArrayLength + Array.getLength(secondArray);Object result = Array.newInstance(localClass, allLength);for (int k = 0; k < allLength; ++k) {if (k < firstArrayLength) {Array.set(result, k, Array.get(firstArray, k));} else {Array.set(result, k, Array.get(secondArray, k - firstArrayLength));}}return result;
} 

存在问题

通过上述的方式,我们将差异补丁单独打一个包,然后进行下发,从而使得我们的类得到修复。但是在这样实现的时候,存在一个问题,就是 在Dalvik 虚拟机对于dex的一个优化。Dalvik 虚拟机在启动的时候,会有许多的启动参数,其中有一项就是verify,当verify被打开的时候,doVerify变量为true,则进行类的校验(dvmVerifyClass方法调用)。若校验成功,则这个类会被打上标记:CLASS_ISPREVERIFIED。

这么做,是防止外部DEX注入的一个安全方案,即保证运行期的Class与其直接引用类之间所在的DEX关系要与安装时候一致,也是为了防止类被篡改校验类的合法性。Dalvik 虚拟机在安装期间,为Class 打上 CLASS_ISPREVERIFIED 是为了提高性能,下次使用时,则会省去校验操作,提高访问效率。dvm在运行期载入Class时候,会对其内存中对应的直接引用类进行校验,如果该类存在与直接引用类所在的dex不是同一个,则直接报“pre-verification” 错误,该类无法加载。

由于这一个限制,导致我们的补丁包无法在被调用到的时候,就会抛出异常,因此我们需要让我们的补丁包,如何通过这次校验, 不被打上CLASS_ISPREVERIFIED,这样,我们的补丁包在被加载的时候,就不会抛出异常了。

nvwa采取的方式就是插桩的方式,在每一个类里去引用到另一个独立dex中的类,也会是在初始化的时候加载的hack.apk中的Hack.class,通过这种方式,可以让我们的类不会被打上这个标签。这样就可以继续装载其它Dex中的类。

插桩存在一个什么问题呢?由于没有打上验证标签,导致每个类的装载的时候都进行验证。

微信在对插装和不插桩做的测试中。在连续加载700个50行的类,还有统计应用启动耗时得到的数据,700个类:不插桩:84ms,插桩:685ms。启动耗时:4934ms,7240ms。

结语

每周一更,由于最近业务需求较多,更新速度明显慢了很多了,因此本篇分析的也是一很简单的框架。接下来,将会逐步深入,分析一些更为复杂的热修复方案框架。

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

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

相关文章

2022年终总结:少年不惧岁月长,彼方尚有荣光在。

2022年终总结&#xff1a;少年不惧岁月长&#xff0c;彼方尚有荣光在。 &#x1f3ac; 博客主页&#xff1a;王同学要努力 &#x1f3ac;个人简介&#xff1a;大三小白&#xff0c;喜欢前端 &#xff0c;热爱分享 &#x1f3a5; 本文由 王同学要努力 原创&#xff0c;首发于…

Eureka 注册中心

Eureka 注册中心目录概述需求&#xff1a;设计思路实现思路分析1.快速上手2.增加 Maven 依赖3.Client端配置注册中心Server端配置注册中心参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip ha…

R 语言 管道操作符

背景 关于代码的简洁性,就是你使用了比较简化的高级操作符,但是有时候会增加代码的可读性。这种可读性在于你是否真正的去了解R的高级语法。你了解高级语法,他就不难,你不了解,他就难,可读性差。 这里我们来讲解一下,关于管道操作符,使R语言编程简化一些。 管道操作…

【登录流程执行逻辑】

1)整体流程图 2.main.js是入口&#xff0c;里面require("permission")时会触发方法体 在进行require("权限时")&#xff0c;会进行一系列初始化。 重定向。 接着走login方法。 vuex其实就是store。 触发vuex里面的方法: await store.dispatch(user/getInf…

CDN消耗速度太快,解决办法

前段时间发现我网站的CDN消耗速度太快了&#xff0c;20多块钱100G的流量&#xff0c;半个月甚至十几天左右都消耗完了。 于是我看CDN的访问ip并不多&#xff0c;发现大部分消耗的都是静态资源&#xff0c;js等文件。 后来找到了解决办法&#xff0c;目前100G还有这么多 方法 …

基于MVC的网上汽车城网站平台开发

摘要 随着现代都市生活节奏的不断加快、网络覆盖面的日益扩大&#xff0c;越来越多的人们加入了网上购物的行列。如今&#xff0c;网购已经成为人们生活的一部分。本系统主要是使用 B/S架构开发出的一个基于 ASP.NET的网上汽车城网站平台开发。前台页面使用CSSDIV&#xff0c;后…

Torch包学习

创建 torch.from_numpy(ndarray) → Tensor&#xff1a;将numpy.ndarray 转换为pytorch的 Tensor。两者共享内存。返回的张量不能改变大小。orch.linspace(start, end, steps100, outNone) → Tensor&#xff1a;生成一个 从start 到 end 的tensor。tensor的长度为steps。包括…

JVM【字节码与类的加载篇】

Class文件结构 字节码文件的跨平台性(了解) 1.Java语言&#xff1a;跨平台的语言 当Java源代码成功编译为字节码后&#xff0c;如果想在不同的平台上运行&#xff0c;则无须再次编译这个优势不再那么吸引人了。Python PHP perl ruby lisp等有强大的解释器跨平台似乎已经快成…

二叉树的遍历大全(前序,中序,后序)+(递归,迭代,Morris方法)

文章目录二叉树的前序遍历递归迭代Morris遍历二叉树的中序遍历递归迭代Morris遍历二叉树的后序遍历递归迭代Morris遍历二叉树的前序遍历 力扣传送门&#xff1a; https://leetcode.cn/problems/binary-tree-preorder-traversal/description/ 递归 递归的解法非常简单&#x…

快过年了,Python实现12306查票以及自动购票....

嗨害大家好鸭&#xff01;我是小熊猫~ 明天就是2023年啦~ 还有谁像我小熊猫一样没有回家的&#xff1f; 这次康康能不能12306抢票回家&#xff01;&#xff01;&#xff01; Python实现12306查票以及自动购票 [代码来源]: 青灯教育-自游老师 [环境使用]: Python 3.8Pycha…

构造函数和原型

1、概述 在典型的 OOP 的语言中&#xff08;如 Java&#xff09;&#xff0c;都存在类的概念&#xff0c;类就是对象的模板&#xff0c;对象就是类的实例&#xff0c;但在 ES6之前&#xff0c; JS 中并没用引入类的概念。ES6&#xff0c; 全称 ECMAScript 6.0 &#xff0c;201…

计算机组成原理实验——三、存储器实验

一、实验目的 1.掌握存储器的工作原理和接口。 2.掌握存储器的实现方法和初始化方法。 3.掌握RISC-V中存储器的存取方式。 二、实验内容 1.利用vivado IP核创建6432的ROM&#xff0c;并在 系数文件中设置数据为123489ab&#xff1b; 2.利用vivado IP核创建6432的RAM&#xf…

猿如意---初学者的一盏明灯---程序员的宝藏app

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;我的CSDN活动之旅 &#x1f4ac;总结&#xff1a;希望你看完…

Rabbit客户端操作不同交换机[包含延迟类型]

文章目录一&#xff1a;direct-直投交换机0.0: 说明 --- 只有队列和交换机绑定&#xff0c;且routing key路由键一致才会收到消息1.1: 先后创建两个队列1.2: 队列绑定Direct交换机&#xff0c;和routing key1.3: 未指明路由键&#xff1a;1.4: 指明路由键1.5: 两个队列绑定一个…

Python中基本输入和输出

文章目录前言一、使用input()函数输入二、使用print()函数输出前言 从我们接触第一个Python程序开始&#xff0c;就一直在使用 print() 函数向屏幕上输出一些字符&#xff0c;如下图代码所示&#xff1a; print() 函数就是Python的基本输出函数。那既然有输出&#xff0c;肯定…

[牛客复盘] 牛客2022跨年场 20221212

[牛客复盘] 牛客2022跨年场 20221212 一、本周周赛总结二、 A 猜群名1. 题目描述2. 思路分析3. 代码实现三、B 分赃1. 题目描述2. 思路分析3. 代码实现四、C 翻卡牌1. 题目描述2. 思路分析3. 代码实现五、D ygg的分数运算1. 题目描述2. 思路分析3. 代码实现六、E 摇色子1. 题目…

多线程生产者消费者——分别使用条件变量、信号量实现

生产者消费者 概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯&#xff0c;而通过阻塞队列来进行通讯&#xff0c;所以生产者生产完数据之后不用等待消费者处理&#xff0c;直接扔给阻塞队列&#xff0c;消费者不…

超好用!win10安装Eiseg标注软件及使用(CPU版本)

写在前面的话 众所周知&#xff0c;标注分割掩膜的软件一般使用labelme&#xff0c;但是一个一个点太麻烦了&#xff0c;工作量太大&#xff0c;&#xff0c;之前&#xff0c;我的思路就是先标少量的数据然后训练个初始模型&#xff0c;再用初始模型对剩下的图像预测掩膜&…

【闲聊杂谈】深入理解Spring Security设计原理

1、什么是Spring Security 顾名思义&#xff0c;Security的意思是安全&#xff0c;本质上就是一个很纯粹的权限管理框架&#xff0c;提供认证和授权两大核心功能。在目前主流的Spring生态中的项目&#xff0c;说到安全框架&#xff0c;基本上SpringSecurity是首选。当然&#…

代码随想录算法训练营第四天|24. 两两交换链表中的节点 、19.删除链表的倒数第N个节点、160.链表相交、142.环形链表II

24. 两两交换链表中的节点 力扣题目链接(opens new window) 解析&#xff1a; 基础题&#xff0c;主要是要把握边界条件&#xff1a;由题可得&#xff0c;交换的节点两两一组&#xff0c;每交换完成一对&#xff0c;问题规模减2&#xff0c;也就是只剩一个或不剩节点时交换便结…