android热修复方案

热补丁方案有很多,其中比较出名的有腾讯Tinker、阿里的AndFix、美团的Robust以及QZone的超级补丁方案。他们的优劣如下:

一、Tinker 热修复

Tinker通过 Dexdiff 算法将原apk和修复后的apk中的dex文件进行对比,生成差分包,运行时将差分包中的dex和原包中的dex进行合并,从而加载差分包中修复好的类。因为是运行时加载的dex文件,所以修复完成后不能即时生效,需要重启app。

二、Qzone热修复

QQ空间的热修复原理和tinker有异曲同工之处,它基于dex分包方案,把bug类修复完成之后,单独生成一个dex文件,运行期间加载dex补丁,运行的是修复后的类。在Android中所有我们运行期间需要的类都是由ClassLoader(类加载器)进行加载,因此让ClassLoader加载全新的类替换掉出现Bug的类即可完成热修复。所以也需要重启才能生效。

三、AndFix热修复

在native动态替换java层的方法,通过native层hook java层的代码。执行方法时,会直接将修复后的方法再native层进行替换,达到修复的效果,这种方式修复后直接会生效,不需要重启。

四、Robust美团热修复方案

方法运行时会在方法内插入一段代码,如果有修复内容,会将执行的代码重定向到其他方法中。

参考了Instans Run的原理。这种方案也是不需要重启的

五、我们基于QQ空间的热修复方案进行研究

1. ART与Dalvik

什么是Dalvik

​ Dalvik是Google公司自己设计用于Android平台的Java虚拟机。支持已转换为.dex(Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。

什么是ART:

Android Runtime, Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认模式。在应用安装的时候Ahead-Of-Time(AOT)预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)预编译。应用程序安装会变慢,但是执行将更有效率,启动更快。

  • 在Dalvik下,应用运行需要解释执行,常用热点代码通过即时编译器(JIT)将字节码转换为机器码,运行效率低。而在ART 环境中,应用在安装时,字节码预编译(AOT)成机器码,安装慢了,但运行效率会提高。
  • ART占用空间比Dalvik大(字节码变为机器码), “空间换时间"。
  • 预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。

Dexopt与DexAot

这两个操作是Art架构安装时的操作, ART会执行AOT,但针对Dalvik 开发的应用也能在 ART 环境中运作。

  • dexopt:对dex文件进行验证和优化,优化后的格式为odex(Optimized dex) 文件
  • dexAot:在安装时对 dex 文件执行dexopt优化之后,再将odex进行 AOT 提前编译操作,编译为OAT可执行文件(机器码)

2. ClassLoader

Java 类加载器

  • BootClassLoader , 用于加载Android Framework层class文件。

  • PathClassLoader ,用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex

  • DexClassLoader,加载指定的,以及jar、zip、apk 中的classes.dex。

我们可以在activity中打印来进行验证:

 @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);/** 测试classLoader的一些使用情况*/// 我们外部的类都是用的PathClassLoaderClassLoader classLoader1 = this.getClassLoader();LogUtils.i("loader1 === " + classLoader1);// 父加载器就是BootClassLoader,所以这个类先从framework中去查找,找不到就从我们本地中查找LogUtils.i("loader1 parent === " + classLoader1.getParent());// framework层的类加载都是用的BootClassLoaderClassLoader classLoader2 = Activity.class.getClassLoader();LogUtils.i("loader  === " + classLoader2);}

打印结果:

3. 源码跟踪

在虚拟机中,加载一个类时,使用的时ClassLoader中的loadClass方法进行加载的,看一下源码:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{// First, check if the class has already been loaded// 一个类加载后会加入到缓存中,以后加载时从缓存中读取就可以了// 如果找不到就从父亲classLoader中查找Class<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.c = findClass(name);}}return c;}

代码中可以看到,虚拟机加载一个类时,会从父ClassLoader中查找这个类,父ClassLoade找不到会递归到父亲的父亲,如果祖辈都找不到时,才会使用当前的ClassLoader进行查找。这就是传说中的双亲委托机制。为什么这样做呢?

  • 1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

  • 2、安全性考虑,防止核心API库被随意篡改。

显而易见,这样不管是父加载器还是自己,都会走到findClass()方法:

 @Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();Class c = pathList.findClass(name, suppressedExceptions);if (c == null) {ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);for (Throwable t : suppressedExceptions) {cnfe.addSuppressed(t);}throw cnfe;}return c;}

ClassLoader中findCliss方法只抛出了一个异常?那肯定是它的子类重写并实现它了~, 在android中,ClassLoader都是继承了BaseDexClassLoader(可以看PathClassLoader和DexClassLoader, 往上有些人说PathClassLoader可以加载内部类,DexClassLoader才可以加载外部存储卡的文件,其实这两者都可以加载, 没有任何区别),以上代码就是在BaseDexClassLoader中实现了类的查找。里面是通过pathList来进行查找的。继续看pathList(DexPathList.java)中的实现:

public Class findClass(String name, List<Throwable> suppressed) {for (Element element : dexElements) {DexFile dex = element.dexFile;if (dex != null) {Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);if (clazz != null) {return clazz;}}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;}

可以看到,dexElements是一个数组, 这里遍历了dexElements,DexFile对象可以看作是dex文件, 如果找到了类直接返回了,这里验证了我们上面所说的。QQ空间热修复就是将修复包patch.dex加入到dexElements开始的位置,当虚拟机加载类时,会先从patch.dex中查找,找到了直接返回,找不到还使用原来的,这样就达到了热修复的效果。

dexElements是一个Element类型的数组,源码中这个Element是私有的,如何创建新的Element并加入到dexElements中呢? 先来看看源码中的dexElements是怎么创建的:

// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);

在pathList的构造方法中可以看到,通过makePathElements可以创建一个element数组,所以我们通过反射来调用makePathElements方法创建一个新的数组,再获取到原数组,将两个数组合并到dexElements就可以了。

拿SDK23举例:

  • 首先通过classloader找到pathList对象,

  • 再执行pathList中的makePathElements方法创建补丁包的Element数组

  • 反射拿到原来的dexElements数组

  • 将两个数组进行合并,放到一个新的数组中

  • 再反射修改dexElements,将新数组覆盖调原来的数组,完成热修复。

    代码如下:

public static void install(ClassLoader classLoader,File patch) {List<File> patchs = new ArrayList<>();patchs.add(patch);// 查找pathList字段Field pathListField = ReflectUtils.findField(classLoader, "pathList");// 1. 获取pathList对象try {Object pathList = pathListField.get(classLoader);if (pathList == null) {throw new RuntimeException("pathList对象为空");}Method method = ReflectUtils.findMethod(pathList, "makePathElements", List.class, File.class, List.class);ArrayList<IOException> suppressedExceptions = new ArrayList<>();// 2. 补丁包的elements数组Object[] patchElements = (Object[]) method.invoke(null, patchs, null, suppressedExceptions);Field dexElementsField = ReflectUtils.findField(pathList, "dexElements");// 3. 原来的dex数组Object[] oldElements = (Object[]) dexElementsField.get(pathList);// 进行合并// 4. 首先利用反射创建一个盛放两个数组的新数组Object[] newElements = (Object[]) Array.newInstance(oldElements.getClass().getComponentType(),oldElements.length + patchElements.length);// 5. 将两个数组放到新数组中,补丁包的要放在前面System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);System.arraycopy(oldElements, 0, newElements, patchElements.length, oldElements.length);// 6. 将原来的dexElement数组用新数组替换掉dexElementsField.set(pathList, newElements);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();LogUtils.i("error == " + e.getTargetException().getMessage());}}

sdk23 , 19 , 14, 4 这些版本创建dexElement数组的方式不一样,或许是方法名不同,或许是参数不同,需要对这几个版本单独做适配,这里只列举了sdk23的反射方法,其他版本原理相同。同时,这一部分内容可参考Tinker热修复方案来进行适配:tinker方案

android-揭秘热修复黑科技相关推荐

  1. JAndFix: 基于Java实现的Android实时热修复方案

    简述 JAndFix是一种基于Java实现的Android实时热修复方案,它并不需要重新启动就能生效.JAndFix是在AndFix的基础上改进实现,AndFix主要是通过jni实现对method(A ...

  2. android Tinker 热修复 乐固加固后友盟打多渠道包之后的补丁失效

    继上一篇 android tinker 热修复使用及注意事项  生成了热修复的补丁; 现在的需求是这样的,我想把这个包用腾讯乐固加固,然后生成多渠道包,希望这个补丁能修复所有这些渠道的包,经过测试,直 ...

  3. Android最强保活黑科技的最强技术实现

    大家好,我是老玩童.今天来跟大家分享TIM最强保活思路的几种实现方法.这篇文章我将通过ioctl跟binder驱动交互,实现以最快的方式唤醒新的保活服务,最大程度防止保活失败.同时,我也将跟您分享,我 ...

  4. android 反编译_Box 黑科技——支持手机端反编译

    项目地址: https://github.com/lulululbj/Box 文末扫码获取最新安装包 . 前言 有将近一个月没有更新文章了,一方面在啃 AOSP ,消化起来确实比较慢.在阅读的过程中, ...

  5. 不挡脸,放肆看!揭秘B站黑科技蒙版弹幕

    来源:AI前线 本文约2019字,建议阅读5分钟. 本文为你揭秘B 站推出的一种"不挡脸"的黑科技弹幕以及背后的故事. [ 导读 ]不久前,B 站发布一条官方消息,为了更好的提升用 ...

  6. Android之热修复框架Nuwa

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/70284239 本文出自:[顾林海的博客] ##前言 当热修复框架还没出 ...

  7. android的热修复,Android热修复原理

    热修复框架技术主要有三类,代码修复,资源修复,动态链接库修复. 资源修复 很多资源修复的框架参考了Instant Run资源修复的原理,所以先了解一下Instant Run Instant Run I ...

  8. 向日葵android平板,向日葵远程控制 - 黑科技改变生活,让端游一秒变手游 - Android 应用 - iPad - 【最美应用】...

    玩主机的看不起玩平台的,玩平台的看不起玩端游的,玩端游的看不起玩页游的,玩页游的还看不起玩手游的. 现在不要分什么彼此了,通过以下方法,想用手机玩平台游戏.玩端游.玩页游,可一次实现! 手机自定义键盘 ...

  9. Android 微信热修复 Tinker 接入过程及使用方法

    一.前言 学习热修复 Tinker 的使用,主要有两个原因: 业务需要:项目会出现一些细小的bug,需要等到发布新版才能修复,因此一直有关注热修复这块的技术. 技术驱动:这是一件需要一直保持的事情,不 ...

最新文章

  1. PNAS-2018-根系分泌物香豆素调控微生物群落结构并促进植物健康
  2. 信息安全“拷问”智慧城市建设 如何解决
  3. python 计算机程序设计基础-零基础,没有编程和计算机基础,究竟该怎么自学python?...
  4. Oracle 数据库基础学习 (六) 子查询
  5. 服务器硬件监控转载:
  6. Vue基础学习(一)------内部指令
  7. 读书笔记-互联网思维必读10本书之一《免费》
  8. [From 1.1~1.2]CLR的执行模型
  9. 日语输入法电脑版_攻略!教你如何用手机打日语
  10. 修改centos7的MAC地址
  11. JavaScript的简单复习
  12. Linux命令学习总结(超详细)
  13. HTK中函数ProcessCrossWordLinks处理流程
  14. GNS3+winPcap+wireshark的安装步骤
  15. laravel 框架中的路由
  16. Unity如何开发微信小游戏
  17. 2022年的1024
  18. ftp文件服务器存储空间,查看ftp服务器存储空间
  19. Matlab中计算程序运行时间的几种方法
  20. VSFTPD + NGINX

热门文章

  1. 万字谏言,给那些想学Python的人,建议收藏后细看!
  2. cropper.js图片裁剪的使用
  3. 作弊:阳光青春下的影子
  4. 人工神经网络训练的目的,神经网络训练过程图解
  5. 切记切记,不要忘记呀
  6. 看一下MySQL索引类型
  7. PHP面试问题总结整理
  8. 将本地项目上传到gitlab
  9. 搞算法的凭啥比你工资高
  10. 孙振耀谈工作与生活---一篇说到我心坎上的文章