什么是Android热修复技术

简单来说就是不重新安装apk的情况下,通过补丁,修复bug

正常开发流程

热修复开发流程

目前主流的热修复技术框架

阿里系的: Andfix、Hotfix、Sophix

腾讯系的:QQ空间超级补丁技术、Qfix、Tinker(微信)

美团系的:Robust

饿了么的:Amigo

关于热修复的技术积淀

最开始 ,是手淘基于Xposed进行了改进,产生了针对Android Dalvik虚拟机运行时的Java method Hook技术——Dexposed。但是这个方案由于对底层Dalvik结构过于依赖,最终无法兼容Android5.0以后

后来支付宝提出了新的热修复方案Andfix。Andfix同样是一种底层结构替换的方案,也达到了运行时生效及时修复的效果,阿里后来对Andfix改进,对相关业务解耦后,推出了阿里百川Hotfix方案,此时的修复已经非常的不错,对代码修复需求都可以解决,而且全版本兼容,但是问题在于Anfix本身有局限,它只提供代码层面的修复,对于资源和so库的修复都还未能实现

最终在2017年Sophix的横空出世,打破了各家热修复技术纷争的局面。在代码修复,资源修复,so修复的方面,以及方案的安全性,易用性放慢,sophix都做到了业界领先

本文重点介绍如何在项目中实现代码修复

通过类加载机制实现

优点:适用性强、修复范围广、限制少

缺点:属于热修复中的冷修复、需要重启App

通过底层替换方法实现

优点:时效好、不需重启,即使生效

缺点:受限制较多(需要修改虚拟机字段,如果手机厂商修改了虚拟机…….)

ClassLoader 简介

对于 Java 程序来说,编写程序就是编写类,运行程序也就是运行类(编译得到的 class 文件),其中起到关键作用的就是类加载器 ClassLoader。说起类加载器我就想到ClassLoader的双亲委托加载机制,接下来就介绍一下类加载的双亲机制

双亲机制

当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程,具体的加载过程如下:

1 源 ClassLoader 先判断该 Class 是否已加载,如果已加载,则直接返回 Class,如果没有则委托给父类加载器。

2 父类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则委托给祖父类加载器。

3 依此类推,直到始祖类加载器(引用类加载器)。

4 始祖类加载器判断是否加载过该 Class,如果已加载,则直接返回 Class,如果没有则尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的子类加载器。

5 始祖类加载器的子类加载器尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,则委托给始祖类加载器的孙类加载器。

6 依此类推,直到源 ClassLoader。

7 源 ClassLoader 尝试从其对应的类路径下寻找 class 字节码文件并载入。如果载入成功,则直接返回 Class,如果载入失败,源 ClassLoader 不会再委托其子类加载器,而是抛出异常。

Android 中的ClassLoader

Android 的 Dalvik/ART 虚拟机如同标准 Java 的 JVM 虚拟机一样,也是同样需要加载 class 文件到内存中来使用,但是在 ClassLoader 的加载细节上会有略微的差别。

Android的dex文件

Android 应用打包成 apk 文件时,class 文件会被打包成一个或者多个 dex 文件,Android 中的 Dalvik/ART 无法像 JVM 那样 直接 加载 class 文件和 jar 文件中的 class,需要通过 dx 工具来优化转换成 Dalvik byte code 才行,只能通过 dex 或者 包含 dex 的jar、apk 文件来加载(注意 odex 文件后缀可能是 .dex 或 .odex,也属于 dex 文件),因此 Android 中的 ClassLoader 工作就交给了 BaseDexClassLoader 来处理。

如何通过类加载机制实现

PathClassLoader:系统运作,app运行时用于加载app所有需要的类。PathClassLoader 里面除了这 2 个构造方法以外就没有其他的代码了,具体的实现都是在 BaseDexClassLoader 里面,其 dexPath 比较受限制,一般是已经安装应用的 apk 文件路径

DexClassLoader:程序员运作,可以通过它加载我们想加载的资源,一般包括这么几种:jar、dex、apk等。

BaseDexClassLoader:热修复中的大Boss,PathClassLoader和DexClassLoader均继承自BaseDexClassLoader,PathClassLoader和DexClassLoader的重要方法均在其父类BaseDexClassLoader中。(因此就需要从BaseDexClassLoader入手)。

对比 PathClassLoader 只能加载已经安装应用的 dex 或 apk 文件,DexClassLoader 则没有此限制,可以从 SD 卡上加载包含 class.dex 的 .jar 和 .apk 文件,这也是插件化和热修复的基础,在不需要安装应用的情况下,完成需要使用的 dex 的加载。

BaseDexClassLoader查找类的源码:

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

List suppressedExceptions = new ArrayList();

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;

}

通过源码可以看到,BaseDexClassLoader通过pathList.findClass查找类的,这里出现一个 大Boss “PathList”

public Class findClass(String name, List 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;

}

前方高能:

可以看到,PathList从dexElements中查找类,如果clazz != null直接return class,这就是我们可以利用的地方,从源码看,dexElements应该是个数组或者集合,设想:我们是不是可以把我们修复bug后的xx类,打包成dex,插入到dexElements的最前面,这样,系统通过PathClassLoader,查找bug类的时候,就会下找到我们的修复bug的xx类,然后直接返回,不去管后面有bug的那个xx类,达到热修复的功能

理一下我们热修复的方案

修复有bug的类,生成dex补丁包;

通过反射机制得到PathClassLoader的成员变量PathList字段(DexPathList的属性)(通过上面分析知道,PathList是PathClassLoader父类BaseDexClasLoader中的)

然后再反射PathList获取它的dexElements字段(是一个存放dex的Element数组)

将我们生成的dex补丁包,插入到dexElements的数组的最前端

项目中的实现

实现步骤

编写改变前的app

编写热修复需要重写生成的类

生成dex补丁包,并放到服务器

编写补丁检测和下载代码

编写修复补丁代码(即用反射拿到dexElements数组,把dex放到有问题的类之前)

如何生成dex补丁包

用class文件生成”001dex”补丁

android在sdk/build-tools/文件件下提供了”dx”命令工具,帮助我们将class文件生成dex文件

生成方式如下:

dx –dex –output=

例如:

dx –dex –output=001.dex …MainAtvity …Actvity2.class …People.class

核心代码

/**

* 加载并安装补丁

* @type {[type]}

*/

private void loadPatch(File file){

Log.d(TAG, file.getAbsolutePath()) ;

if(file.exists()){

Log.d(TAG,"文件存在...") ;

}else{

Log.d(TAG, "文件不存在...") ;

}

//获取系统PathClassLoader

PathClassLoader pLoader = (PathClassLoader) context.getClassLoader();

//获取PathClassLoader中的PathList

Object pPathList = getPathList(pLoader) ;

if(pPathList == null){

Log.d(TAG, "get PathClassLoader pathlist failed...") ;

return ;

}

//加载补丁

DexClassLoader dLoader = new DexClassLoader(file.getAbsolutePath(),optPath, null, pLoader) ;

//获取DexClassLoader的pathLit,即BaseDexClassLoader中的pathList

Object dPathList = getPathList(dLoader) ;

if(dPathList == null){

Log.d(TAG, "get DexClassLoader pathList failed...") ;

return ;

}

//获取PathList和DexClassLoader的DexElements

Object pElements = getElements(pPathList) ;

Object dElements = getElements(dPathList) ;

//将补丁dElements[]插入系统pElements[]的最前面

Object newElements = insertElements(pElements, dElements) ;

if(newElements == null){

Log.d(TAG, "patch insert failed...") ;

return ;

}

//用插入补丁后的新Elements[]替换系统Elements[]

try {

Field fElements = pPathList.getClass().getDeclaredField("dexElements") ;

fElements.setAccessible(true);

fElements.set(pPathList, newElements);

} catch (Exception e) {

e.printStackTrace();

Log.d(TAG, "fixed failed....") ;

return ;

}

}

/**

* 将补丁插入系统DexElements[]最前端,生成一个新的DexElements[]

* @param pElements

* @param dElements

* @return

*/

private Object insertElements(Object pElements, Object dElements){

//判断是否为数组

if(pElements.getClass().isArray() && dElements.getClass().isArray()){

//获取数组长度

int pLen = Array.getLength(pElements) ;

int dLen = Array.getLength(dElements) ;

//创建新数组

Object newElements = Array.newInstance(pElements.getClass().getComponentType(), pLen+dLen) ;

//循环插入

for(int i=0; i

if(i

Array.set(newElements, i, Array.get(dElements, i));

}else{

Array.set(newElements, i, Array.get(pElements, i-dLen)) ;

}

}

return newElements ;

}

return null ;

}

/**

* 获取DexElements

* @param object

* @return

*/

private Object getElements(Object object){

try {

Class> c = object.getClass() ;

Field fElements = c.getDeclaredField("dexElements") ;

fElements.setAccessible(true);

Object obj = fElements.get(object) ;

return obj ;

} catch (Exception e) {

e.printStackTrace();

}

return null ;

}

/**

* 通过反射机制获取PathList

* @param loader

* @return

*/

private Object getPathList(BaseDexClassLoader loader){

try {

Class> c = Class.forName("dalvik.system.BaseDexClassLoader") ;

//获取成员变量pathList

Field fPathList = c.getDeclaredField("pathList") ;

//抑制jvm检测访问权限

fPathList.setAccessible(true);

//获取成员变量pathList的值

Object obj = fPathList.get(loader) ;

return obj ;

} catch (Exception e) {

e.printStackTrace();

}

return null ;

}

Android工具修复属性,Android 热修复介绍之代码修复相关推荐

  1. android:ellipsize = marquee 跑马灯,Android基于TextView属性android:ellipsize实现跑马灯效果的方法...

    本文实例讲述了Android基于TextView属性android:ellipsize实现跑马灯效果的方法.分享给大家供大家参考,具体如下: Android系统中TextView实现跑马灯效果,必须具 ...

  2. android中textcolor属性,android – EditText和TextView textColorPrimary不遵循API lt;21的主题颜色...

    在设计工具栏视图以使其适用于API 21及以下版本时存在一些问题,但我认为我有这个styles.xml @color/colorPrimary @color/colorPrimaryDark @col ...

  3. android自定义组件属性,android自定义控件并添加属性的方法以及示例

    安卓系统为我们提供了丰富的控件,但是在实际项目中我们仍然需要重新通过布局来实现一些效果,比如我们需要一个上面图标,下面文字的button,类似于下面这样的: 最直接的解决办法是通过将imageview ...

  4. android自定义组件属性,Android组合控件详解 自定义属性

    组合控件详解 & 自定义属性 组合控件是自定义控件的一种,只不过它是由其他几个原生控件组合而成,故名组合控件. 在实际项目中,GUI 会遇到一些可以提取出来做成自定义控件情况. 一个自定义控件 ...

  5. android中edittext属性,Android中EditText的inputType属性的详解

    xml的inputtype的值. Android:inputType="none" android:inputType="text" android:input ...

  6. Android工具里没有Android,android – AppCompat工具栏没有显示

    在主题中声明.NoActionBar之后,以及将工具栏放在布局中,我的工具栏不会显示.我最终得到的正是你在宣布没有动作栏时所期望的 – 没有动作栏.这是布局: activity_home.xml: l ...

  7. android Mediaplayer各种属性和方法简单介绍

    主要涉及类:MediaPlayer (1) 当一个MediaPlayer对象被创建或者调用reset()方法之后,它处于空闲状态,调用release()方法后处于结束状态 1,一个MediaPlaye ...

  8. android menu xml 属性,Android中Menu类型及常见属性说明

    Android系统里面有3种类型的菜单:options menu,context menu,sub menu. 一.options menu 按Menu键就会显示,用于当前的Activity. 它包括 ...

  9. android布局的属性,android

    第一类:属性值为true或false android:layout_centerHrizontal  水平居中 android:layout_centerVertical   垂直居中 android ...

最新文章

  1. [原创]关于在VS2008和VS2010中禁用及卸载Visual Assist X的方法研究
  2. 【视频】SQL Server 2008 R2 StreamInsight - 多源复杂事件处理
  3. 【Paper】2018_多机器人领航-跟随型编队控制
  4. 波卡链Substrate (3)SRML框架
  5. linux 服务器 iptables 防止arp病毒,Linux下防御ARP病毒攻击
  6. 关于Activity的getReferrer():如何在Activity中获取调用者?
  7. 编写优雅代码,从挖掉恶心的if/else 开始
  8. 使用Rancher搭建K8S测试环境
  9. 为什么搜索与推荐场景用AUC评价模型好坏?
  10. 在Rayeager px2上搭建web服务器anmpp
  11. 如何看待夸克,酷狗概念版等简洁型软件?
  12. vector.resize 与 vector.reserve的区别(转载)
  13. ENVI入门系列教程---二、图像分析---10.基于专家知识决策树分类
  14. php 抓取天气显示天气图片,Geektool 使用 python+beautifulsoup 抓取天气显示
  15. 三大跳槽传闻,信了你就输了!
  16. 安全私人云储存时代 H3C Magic M2脱颖而出
  17. 老广人为粤语---广州话写篇文章
  18. 洛谷-P1618 三连击(升级版)
  19. 【Cucumber系列】Cucumber Hooks
  20. 最简单的TAR,TPR,FAR,FPR的说明

热门文章

  1. win10通过计算机名称共享打印机,WIN10怎么连接共享打印机到电脑上
  2. 如何免费下载的全球的矢量边界(WGS84)
  3. 24个基本指标精粹讲解(5)--SR
  4. 晶体三极管的放大原理
  5. 五子棋_AI权值算法(2)
  6. 感觉心上有一阵秋风吹过
  7. 课程笔记之《论文写作》
  8. 对伪装docx文件病毒的逆向分析
  9. windbg查阅资料(持续更新)
  10. php程序员述职材料_php程序员述职报告(多篇范文)