Android热修复原理与实战

1,434 阅读3分钟

Android热修复原理与实战

微信截图_20230502152947.png

热修复

什么是热修复

在我们应用上线后出现bug需要及时修复时,不用再发新的安装包,只需要发布补丁包,在客户无感知下修复掉bug

怎么进行热修复

服务端:补丁包管理

用户端:执行热修复

开发端:生成补丁包

微信截图_20230502131021.png

热修复要解决的问题

客户端

  • 补丁包是什么?
  • 如何生成补丁包?
  • 开启混淆后呢?
  • 对比改动自动生成补丁包(gradle)?

服务端

  • 什么时候执行热修复?
  • 怎么执行热修复(使用补丁包)?
  • Android版本兼容问题?

热修复解决方案

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

微信截图_20230502132239.png

AndFix

在native动态替换java层的方法,通过native层hook java层的代码

微信截图_20230502132727.png

微信截图_20230502132929.png

22.jpg

Robust

Android热更新方案Robust - 美团技术团队 (meituan.com)

public long getIndex() { 
    // 有BUG的代码片段
    return 100; 
}

public static ChangeQuickRedirect changeQuickRedirect; 
public long getIndex() { 
    // 经过插桩后实际执行的代码
    if(changeQuickRedirect != null) { 
        return 修复的实现;
    } 
    return 100L; 
}

Tinker

Tinker通过计算对比指定的Base Apk中的dex与修改后的Apk中的dex的区别,补丁包中的内容即为两者差分的描述,运行时将Base Apk中的dex与补丁包进行合成,重启后加载全新的合成后的dex文件

微信截图_20230502134103.png

ClassLoader

微信截图_20230502140244.png

双亲委派机制

含义

某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。

微信截图_20230502141630.png

作用

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

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

类查找流程

微信截图_20230502144920.png

类加载实现热修复

流程

1、获取程序的PathClassLoader对象

2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象

3、反射获取pathList的dexElements对象 (oldElement)

4、把补丁包变成Element数组:patchElement(反射执行makePathElements)

5、合并patchElement+oldElement = newElement (Array.newInstance)

6、反射把oldElement赋值成newElement

代码实现

HotFix.installPatch(this, new File("/sdcard/patch.jar"));

HotFix.java类


package com.example.simpledemo;

import android.app.Application;
import android.content.Context;
import android.os.Build;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class HotFix {
    
    public static void installDex(Context context, String dexPath) {
        try {
            // 获取pathClassLoader
            PathClassLoader classLoader = (PathClassLoader) context.getApplicationContext().getClassLoader();
            DexClassLoader dexClassLoader = new DexClassLoader(dexPath, null, null, classLoader);

            // 获取pathList对象
            Object newPathList = FieldUtils.getField(dexClassLoader.getClass(), "pathList", true).get(dexClassLoader);
            Object oldPathList = FieldUtils.getField(classLoader.getClass(), "pathList", true).get(classLoader);
            if (newPathList == null || oldPathList == null) return;

            // 获取dexElements数组
            Object newDexElements = FieldUtils.getDeclaredField(newPathList.getClass(), "dexElements", true).get(newPathList);
            Object oldDexElements = FieldUtils.getDeclaredField(oldPathList.getClass(), "dexElements", true).get(oldPathList);
            if (newDexElements == null || oldDexElements == null) return;

            // 合并dexElements数组
            Class<?> componentType = newDexElements.getClass().getComponentType();
            if (componentType == null) return;
            int newArrayLen = Array.getLength(newDexElements);
            int oldArrayLen = Array.getLength(oldDexElements);
            Object array = Array.newInstance(componentType, newArrayLen + oldArrayLen);
            if (array == null) return;
            System.arraycopy(newDexElements, 0, array, 0, newArrayLen);
            System.arraycopy(oldDexElements, 0, array, newArrayLen, oldArrayLen);

            // 将合并后的dexElements重新赋值给原dexElements
            // Note:一定要重新获取一下pathList对象,否则会报错
            Object pathList = FieldUtils.getField(classLoader.getClass(), "pathList", true).get(classLoader);
            if (pathList == null) return;
            Field field = FieldUtils.getDeclaredField(pathList.getClass(), "dexElements", true);
            FieldUtils.writeField(field, pathList, array, true);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

代码中用到的FieldUtils类是一个开源库中的类,依赖即可

implementation 'org.apache.commons:commons-lang3:3.12.0'

热修复原理

微信截图_20230502151707.png

依据双亲委托机制的原理,在执行热修复代码后,会先去加载patch.dex中的类,从而避免再加载有bug的类,达到热修复的目的