一步步手动实现热修复(三)-Class文件的替换

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

本节课程主要分为3块:

  • 1.一步步手动实现热修复(一)-dex文件的生成与加载
  • 2.一步步手动实现热修复(二)-类的加载机制简要介绍
  • 3.一步步手动实现热修复(三)-Class文件的替换

本节示例所用到的任何资源都已开源,项目中包含工程中所用到代码、示例图片、说明文档。项目地址为:
https://code.csdn.net/u011064099/sahadevhotfix/tree/master


在上一节了解了基本的类加载原理之后,我们这一节开始对工程内部的类实行替换。

Tips: 本章主要依赖文章http://blog.csdn.net/vurtne_ye/article/details/39666381中的未实现代码实现,实现思路也源自该文章,在阅读本文之前可以先行了解。

这一节我们主要实现的流程有:

  • 在工程内创建相同的ClassStudent类,但在调用getName()方法返回字符串时会稍有区别,用于结果验证
  • 使用DexClassLoader加载外部的user.dex
  • 将DexClassLoader中的dexElements放在PathClassLoader的dexElements之前
  • 验证替换结果

创建工程内的ClassStudent

我们在第一节中演示了如何加载外部的Class,为了起到热修复效果,那么我们需要在工程内有一个被替换的类,被替换的ClassStudent类内容如下:

package com.sahadev.bean;/*** Created by shangbin on 2016/11/24.* Email: sahadev@foxmail.com*/public class ClassStudent {private String name;public ClassStudent() {}public void setName(String name) {this.name = name;}public String getName(){return this.name + ".Miss";}}

外部的ClassStudent类的内容如下:

package com.sahadev.bean;/*** Created by shangbin on 2016/11/24.* Email: sahadev@foxmail.com*/public class ClassStudent {private String name;public ClassStudent() {}public void setName(String name) {this.name = name;}public String getName(){return this.name + ".Mr";   }
}

这两个类除了在getName()方法返回之处有差别之外,其它地方一模一样,不过这足可以让我们说明情况。

我们这里要实现的目的: 我们默认调用getName()方法返回的是“xxxx.Miss”,如果热修复成功,那么再使用该方法的话,返回的则会是“xxxx.Mr”

对含有包名的类再次编译

因为第一节中专门声明了不可以对类声明包名,但是这样在Android工程中无法引用到该类,所以把不能声明包名的问题解决了一下。

不能声明包名的主要原因是在编译Java文件时,没有正确的使用命令。对含有包名的Java文件应当使用以下命令:

javac -d ./ ClassStudent.java

经过上面命令编译后的.class文件便可以顺利通过dx工具的转换。

我们还是按照第一节的步骤将转换后的user.dex文件放入工程中并写入本地磁盘,以便稍后使用。

Dex转换注意: 很多同学走到这一步时遇到了问题,这是因为第一章介绍的时候并没有带包名,所以在这里我们所使用的命令的最后一个参数应当携带包的相对路径,例如:

dx –dex –output=user.dex .\com\sahadev\bean\ClassStudent.class

替换工程内的类文件

在开始之前还是再回顾一下实现思路:类在使用之前必须要经过加载器的加载才能够使用,在加载类时会调用自身的findClass()方法进行查找。然而在Android中类的查找使用的是BaseDexClassLoader,BaseDexClassLoader对findClass()方法进行了重写:

    @Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {Class clazz = pathList.findClass(name);if (clazz == null) {throw new ClassNotFoundException(name);}return clazz;}

pathList是类DexPathList的实例,这里pathList.findClass的实现如下:

    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;}

由此我们可以得知类的查找是通过遍历dexElements来进行查找的。所以为了实现替换效果,我们需要将DexClassLoader中的Element对象放到dexElements数组的第0个位置,这样才能在BaseDexClassLoader查找类时先找到DexClassLoader所用的user.dex中的类。

Tips: 如果对上面这段内容看不懂的,没关系,可以移步到本系列课程的第二节了解一下类加载的具体流程。

类的加载是从上而下加载的,所以就算是DexClassLoader加载了外部的类,但是在系统使用类的时候还是会先在ClassLoader中查找,如果找不到则会在BaseDexClassLoader中查找,如果再找不到,就会进入PathClassLoader中查找,最后才会使用DexClassLoader进行查找,所以按照这个流程外部类是无法正常发挥作用的。所以我们的目的就是在查找工程内的类之前,先让加载器去外部的dex中查找。

好了,再次梳理了思路之后,我们接下来对思路进行实践。

下面的方法是我们主要的注入方法:

    public String inject(String apkPath) {boolean hasBaseDexClassLoader = true;File file = new File(apkPath);try {Class.forName("dalvik.system.BaseDexClassLoader");} catch (ClassNotFoundException e) {hasBaseDexClassLoader = false;}if (hasBaseDexClassLoader) {PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();DexClassLoader dexClassLoader = new DexClassLoader(apkPath, file.getParent() + "/optimizedDirectory/", "", pathClassLoader);try {Object dexElements = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(dexClassLoader)));Object pathList = getPathList(pathClassLoader);setField(pathList, pathList.getClass(), "dexElements", dexElements);return "SUCCESS";} catch (Throwable e) {e.printStackTrace();return android.util.Log.getStackTraceString(e);}}return "SUCCESS";}

Tips: 这段代码原封不动采用于http://blog.csdn.net/vurtne_ye/article/details/39666381文章中最后的实现代码,但是该文章并没有给出具体的注入细节。我们接下里的过程就是对没有给全的细节进行补充与讲解。

这段代码的核心在于将DexClassLoader中的dexElements与PathClassLoader中的dexElements进行合并,然后将合并后的dexElements替换原先的dexElements。最后我们在使用ClassStudent类的时候便可以直接使用外部的ClassStudent,而不会再加载默认的ClassStudent类。

首先我们通过classLoader获取各自的pathList对象:

public Object getPathList(BaseDexClassLoader classLoader) {Class<? extends BaseDexClassLoader> aClass = classLoader.getClass();Class<?> superclass = aClass.getSuperclass();try {Field pathListField = superclass.getDeclaredField("pathList");pathListField.setAccessible(true);Object object = pathListField.get(classLoader);return object;} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return null;
}

在使用以上反射的时候要注意,pathList属性属于基类BaseDexClassLoader。所以如果直接获取DexClassLoader或者PathClassLoader的pathList属性的话,会得到null。

其次是获取pathList对应的dexElements,这里要注意dexElements是个数组对象:

    public Object getDexElements(Object object) {if (object == null)return null;Class<?> aClass = object.getClass();try {Field dexElements = aClass.getDeclaredField("dexElements");dexElements.setAccessible(true);return dexElements.get(object);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return null;}

接下来我们将两个数组对象合并成为一个:

    public Object combineArray(Object object, Object object2) {Class<?> aClass = Array.get(object, 0).getClass();Object obj = Array.newInstance(aClass, 2);Array.set(obj, 0, Array.get(object2, 0));Array.set(obj, 1, Array.get(object, 0));return obj;}

上面这段代码我们根据数组对象的类型创建了一个新的大小为2的新数组,并将两个数组的第一个元素取出,将代表外部dex的dexElement放在了第0个位置。这样便可以确保在查找类时优先从外部的dex中查找。

最后将原先的dexElements覆盖:

    public void setField(Object pathList, Class aClass, String fieldName, Object fieldValue) {try {Field declaredField = aClass.getDeclaredField(fieldName);declaredField.setAccessible(true);declaredField.set(pathList, fieldValue);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}

验证替换结果

好,我们做完以上的工作之后,写一段代码来进行验证:

    /*** 验证替换类后的效果*/private void demonstrationRawMode() {ClassStudent classStudent = new ClassStudent();classStudent.setName("Lavon");mLog.i(TAG, classStudent.getName());}

如果我们没有替换成功的话,那么这里默认使用的是内部的ClassStudent,getName()返回的会是Lavon.Miss
如果我们替换成功的话,那么这里默认使用的是外部的ClassStudent,getName()返回的则会是Lavon.Mr

我们实际运行看下效果:
这里写图片描述

这说明我们已经完成了基本的热修复。有任何疑问欢迎留言。


我建了一个QQ群,欢迎对学习有兴趣的同学加入。我们可以一起探讨、深究、掌握那些我们会用到的技术,让自己不至于太落伍。
这里写图片描述

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

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

相关文章

Vision Transformer 论文解读

原文链接&#xff1a;https://blog.csdn.net/qq_16236875/article/details/108964948 扩展阅读&#xff1a;吸取CNN优点&#xff01;LeViT&#xff1a;用于快速推理的视觉Transformer https://zhuanlan.zhihu.com/p/363647380 Abstract: Transformer 架构早已在自然语言处理…

长这么大,才知道数据集不用下载可以直接在线使用

每天清晨打开电脑&#xff0c;搜索所需的数据集&#xff0c;点击“Download”&#xff0c;愉快地眯上眼睛&#xff0c;期待n个小时后醒来乖巧下载好的数据&#xff0c;开始放进模型像小仓鼠进滚轮一样快乐奔跑。梦醒时分&#xff0c;一个“Error”蹦进眼睛里&#xff0c;美好码…

会议交流 - CCKS2020 | 2020年全国知识图谱与语义计算大会

OpenKG开放知识图谱&#xff08;简称 OpenKG&#xff09;旨在促进中文知识图谱数据的开放与互联&#xff0c;促进知识图谱和语义技术的普及和广泛应用。

LeetCode 739. 每日温度(单调栈)

1. 题目 根据每日 气温 列表&#xff0c;请重新生成一个列表&#xff0c;对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高&#xff0c;请在该位置用 0 来代替。 例如&#xff0c;给定一个列表 temperatures [73, 74, 75, 71, 69, 72, 76, …

令人振奋的好消息!2016年12月8日Google Developers中文网站发布!

令人振奋的好消息&#xff01; 2016年12月8日Google Developers中文网站发布&#xff01; 以往我们需要访问Android的开发网站、Google的开发网站都需要翻墙&#xff0c;苦不堪言。现在Google发布了中文网站&#xff0c;怎能不让人开心&#xff1f;&#xff01; Android中文…

美团外卖Android Lint代码检查实践

概述 Lint是Google提供的Android静态代码检查工具&#xff0c;可以扫描并发现代码中潜在的问题&#xff0c;提醒开发人员及早修正&#xff0c;提高代码质量。除了Android原生提供的几百个Lint规则&#xff0c;还可以开发自定义Lint规则以满足实际需要。 为什么要使用Lint 在美团…

零基础入门NLP - 新闻文本分类,正式赛第一名方案分享

零基础入门NLP - 新闻文本分类&#xff0c;正式赛第一名方案分享&#xff1a;https://mp.weixin.qq.com/s/7WpZUqdlItBToLYuRLm44g

这篇论文提出了一个文本-知识图谱的格式转换器...

文 | 花小花PosyHello, 大家好&#xff0c;我是小花。今天给大家介绍一篇有野心的paper。为何如此说呢&#xff1f;因为该工作提出了一个知识的格式转换器&#xff0c;用于转换 无结构化的纯文本(Text)和结构化的知识图谱(KG) 。换句话说&#xff0c;给模型一打句子&#xff0c…

论文小综 | Using External Knowledge on VQA

本文转载自公众号&#xff1a;浙大KG。本文作者&#xff1a;陈卓&#xff0c;浙江大学在读博士&#xff0c;主要研究方向为图神经网络和知识图谱表示学习我们生活在一个多模态的世界中。视觉的捕捉与理解&#xff0c;知识的学习与感知&#xff0c;语言的交流与表达&#xff0c;…

如何修改TextView链接点击实现(包含链接生成与点击原理分析)

*这篇文章的主要目的是想要大家学习如何了解实现&#xff0c;修改实现&#xff0c;以达到举一反三&#xff0c;自行解决问题的目的。 某天遇到这么一个需求&#xff1a;在TextView中的文本链接要支持跳转&#xff0c;嗯&#xff0c;这个好办&#xff0c;TextView本身是支持的&…

LeetCode 752. 打开转盘锁(图的BFS最短路径)

1. 题目 你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字&#xff1a; ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转&#xff1a;例如把 ‘9’ 变为 ‘0’&#xff0c;‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨…

使用TensorFlow训练WDL模型性能问题定位与调优

简介 TensorFlow是Google研发的第二代人工智能学习系统&#xff0c;能够处理多种深度学习算法模型&#xff0c;以功能强大和高可扩展性而著称。TensorFlow完全开源&#xff0c;所以很多公司都在使用&#xff0c;但是美团点评在使用分布式TensorFlow训练WDL模型时&#xff0c;发…

docker镜像打包save,载入load,启动run

docker镜像打包save&#xff0c;载入load&#xff0c;启动run docker打包&#xff0c;针对的是镜像&#xff0c;而不是运行中的容器。 查看当前系统的镜像文件&#xff1a; docker images 首先&#xff0c;将当前运行中的&#xff0c;已经自定义修改之后的容器保存为新的镜像…

会议交流 - CNCC 技术论坛 | NLP中知识和数据怎么选?当然是全都要!——第四届中文信息技术发展战略研讨会...

本文转载自公众号&#xff1a;中国计算机学会 。本论坛将于CNCC2020期间&#xff0c;10月23日下午16:00-18:00&#xff0c;在北京新世纪日航饭店3层南京厅举行。本论坛将邀请多位来自国内著名高校和人工智能企业的知名自然语言处理专家介绍他们在知识图谱、知识获取、预训练模型…

从我开发过的Tensorflow、飞桨、无量框架看深度学习这几年

文 | Peter潘欣知乎和深度学习框架打交道已有多年时间。从Google的TensorFlow, 到百度的PaddlePaddle&#xff0c;再到现在腾讯的无量。很庆幸在AI技术爆发的这些年横跨中美几家公司&#xff0c;站在一个比较好的视角看着世界发生巨大的变化。在这些经历中&#xff0c;视角在不…

探索Glide对Gif图片资源的获取、解析过程

先预祝大家汤圆节快乐&#xff01;很久没写博客了。今天我们来探索一下Glide是如何支持Gif图片加载的。 本篇博客的目的 了解代码分析的基本思路与方法了解Glide是如何对Gif图片进行支持的 探索背景 为什么会有这么一个想法呢&#xff0c;一来一直对Glide是知其名而不知其所…

美团“猜你喜欢”深度学习排序模型实践

引言 推荐作为解决信息过载和挖掘用户潜在需求的技术手段&#xff0c;在美团点评这样业务丰富的生活服务电子商务平台&#xff0c;发挥着重要的作用。在美团App里&#xff0c;首页的“猜你喜欢”、运营区、酒店旅游推荐等重要的业务场景&#xff0c;都是推荐的用武之地。 目前&…

如何关闭docker容器里的进程

如何关闭docker容器里的进程 1、使用docker exec 容器名 ps -ef命令查看进程信息 示例&#xff1a; 创建名为"redis"的容器&#xff0c;并在容器内部和宿主机中查看容器中的进程信息&#xff1a; 2、然后进入该容器中&#xff0c;执行如下命令即可停掉进程&…

论文浅尝 | 一日看尽长安花--NLP可解释研究梳理

本文是对TACL2019《Analysis Methods in Neural Language Processing: A Survey》的翻译和整理。本文转载自知乎&#xff0c;文章链接&#xff1a;https://zhuanlan.zhihu.com/p/265815975导读NLP领域发展迅猛&#xff0c;其模型分析方法也正逐渐火热。为什么要研究NLP的可解释…

简明扼要的反射入门教程

反射 反射作为RTTI语言&#xff08;比如Java&#xff09;的基础之一被很多人所熟知&#xff0c;但是有些同学对反射本身还是懵懵懂懂的&#xff0c;不是很清楚它到底有什么用。今天这节课我们就对反射本身来一个通体的认知。 定义 反射所在的包为&#xff1a;java.lang.refl…