您现在的位置是:首页 >技术交流 >Android热修复原理与实战网站首页技术交流

Android热修复原理与实战

Android小贾 2023-07-13 20:00:02
简介Android热修复原理与实战

作者:独孤狼

什么是热修复

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

怎么进行热修复

服务端:补丁包管理

用户端:执行热修复

开发端:生成补丁包

热修复要解决的问题

客户端

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

服务端

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

热修复解决方案

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

AndFix

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

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文件

ClassLoader

双亲委派机制

含义

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

作用

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

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

类查找流程

类加载实现热修复

流程

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"));

EnjoyFix.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 {
    private static final String TAG = "HotFix";

    private static File initPatch(Context context) {
        File patchFile = new File(context.getExternalFilesDir(""), "patch.dex");
        FileOutputStream fos = null;
        InputStream is = null;
        try {
            fos = new FileOutputStream(patchFile);
            is = context.getAssets().open("patch.dex");
            int len;
            byte[] buffer = new byte[2048];
            while ((len = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return hackFile;
    }

    public static void installPatch(Application application, File patch) {
        File patchDex = initHack(application);
        List<File> patchs = new ArrayList<>();
        patchs.add(patchDex);
        if (patch.exists()) {
            patchs.add(patch);
        }
        //1、获取程序的PathClassLoader对象
        ClassLoader classLoader = application.getClassLoader();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            try {
                ClassLoaderInjector.inject(application, classLoader, patchs);
            } catch (Throwable throwable) {
            }
            return;
        }
        //2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
        try {
            Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
            Object pathList = pathListField.get(classLoader);
            //3、反射获取pathList的dexElements对象 (oldElement)
            Field dexElementsField = ShareReflectUtil.findField(pathList, "dexElements");
            Object[] oldElements = (Object[]) dexElementsField.get(pathList);
            //4、把补丁包变成Element数组:patchElement(反射执行makePathElements) 
            Object[] patchElements = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Method makePathElements = ShareReflectUtil.findMethod(pathList, "makePathElements", List.class, File.class, List.class);
                ArrayList<IOException> ioExceptions = new ArrayList<>();
                patchElements = (Object[]) makePathElements.invoke(pathList, patchs, application.getCacheDir(), ioExceptions);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                Method makePathElements = ShareReflectUtil.findMethod(pathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
                ArrayList<IOException> ioExceptions = new ArrayList<>();
                patchElements = (Object[]) makePathElements.invoke(pathList, patchs, application.getCacheDir(), ioExceptions);
            } 
            //5、合并patchElement+oldElement = newElement (Array.newInstance)创建一个新数组,大小 oldElements+patchElements
            Object[] newElements = (Object[]) Array.newInstance(oldElements.getClass().getComponentType(), oldElements.length + patchElements.length);
            System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
            System.arraycopy(oldElements, 0, newElements, patchElements.length, oldElements.length);
            //6、反射把oldElement赋值成newElement 
            dexElementsField.set(pathList, newElements);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

热修复原理

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

为了帮助到大家快速熟悉APP架构,热修复这一块的内容,下面我将自己的心得体会整理在此了,大家可以参考这学习:https://qr21.cn/CaZQLo?BIZ=ECOMMERCE

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。