Android热修复原理与实战

news2024/11/15 8:49:27

作者:独孤狼

什么是热修复

在我们应用上线后出现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

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

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

相关文章

Introduction to modern Cryptography 现代密码学原理与协议第一章笔记

加密的语法由三个算法组成:密钥产生&#xff0c;加密&#xff0c;解密 (1) 密钥产生算法Gen是一个概率算法&#xff0c;能够根据方案定义的某种分布方案分布选择并输出一个密钥k (2) 加密算法Enc,输入为密钥k和明文m&#xff0c;输出为密文c。把使用密钥k加密明文m记为Enck(m) …

【Vue 基础】尚品汇项目-06-vuex模块式开发

vuex是官方提供一个插件&#xff0c;状态管理库&#xff0c;集中式管理项目中组件共用的数据。 目录 一、安装 二、vuex的基本使用 三、vuex模块式开发 一、安装 安装命令&#xff1a; npm install vuex3.6.2 --save 如果安装错版本&#xff0c;要先卸载再重新安装&#…

认识Filter(过滤器)

Filter介绍 在计算机编程中&#xff0c;Filter&#xff08;过滤器&#xff09;是一种用于对数据流进行处理的软件组件。Filter 的作用是从输入流中获取数据&#xff0c;对其进行处理后再将其写入输出流中。Filter 组件通常用于数据校验、数据转换、数据压缩等方面&#xff0c;…

微服务知识2

CAP和BASE是分布式必备理论基础 CAP理论 一致性(C)&#xff1a;写操作之后进行读操作无论在哪个节点都需要返回写操作的值 可用性(A)&#xff1a;非故障的节点在合理的时间内返回合理的响应 分区容错性(P)&#xff1a;当出现网络分区后&#xff0c;系统能够继续工作&#x…

家用平价洗地机哪款好?国产性价比高的品牌

在当今社会&#xff0c;人们使用清洁电器已经非常普及&#xff0c;成为了人们日常清洁中必不可少的得力助手了。洗地机在我看来&#xff0c;它在清洁力度上做的十分优秀&#xff0c;无论是干湿垃圾还是顽固污渍&#xff0c;皆可以清洗到位&#xff1b;同时&#xff0c;洗地机配…

《花雕学AI》AI 人工智能伙伴关系的指南:遵循原则,实现实践,展望未来

引言&#xff1a;人工智能&#xff08;AI&#xff09;是指由人造的机器或系统所展现出的智能&#xff0c;它可以模拟或扩展人类的认知功能&#xff0c;如学习、推理、感知、交流等。 人工智能的发展和应用已经深刻地影响了社会、经济、文化和政治等各个领域&#xff0c;同时也带…

( 数组和矩阵) 697. 数组的度 ——【Leetcode每日一题】

❓697. 数组的度 难度&#xff1a;简单 给定一个非空且只包含非负数的整数数组 nums&#xff0c;数组的 度 的定义是指数组里任一元素出现频数的最大值。 你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组&#xff0c;返回其长度。 示例 1&#xff1a; 输…

Java异常处理传递规范总结

java 异常分类 Thorwable类&#xff08;表示可抛出&#xff09;是所有异常和错误的超类&#xff0c;两个直接子类为Error和Exception&#xff0c;分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常&#xff0c; 这两种异常有很大的区别…

Linux 概述及常用命令(一)

1、Linux 简介 Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff08;主要用在服务器上&#xff09;&#xff0c;是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。 目前市面上较知名的发行版有&#xff1a;Ubuntu&#xff0c;RedHat&…

大学生创业者最关心的问题——校园外卖到底能不能盈利?

与前十年的寒窗苦读相比&#xff0c;大学自由散漫的生活略显空虚。除了学习&#xff0c;学生们还有很多时间自由安排。有些人选择颓废&#xff0c;有些人选择勤奋。而校园创业是许多有想法的大学生都会做的事情。其中&#xff0c;外卖跑腿配送是特别受欢迎的创业项目之一。 那…

五月到了,再来看看ChatGPT给我们带来了什么吧!

ChatGPT&#xff0c;即聊天生成预训练转换器&#xff08;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;是美国OpenAI公司基于GPT-3.5架构&#xff08;目前已经更新到GPT-4&#xff0c;但仅限于Plus用户&#xff09;研发和强化训练的一款人工智能聊天机器人…

C++学习day--04 图形化开发环境搭建

1、为什么要搭建图形化开发环境 因为很多初学者学习C就是单纯的控制台打印输出&#xff0c;很枯燥&#xff0c;我们今后在做项目或者在学习的过程中&#xff0c;采用图形化方式相结合的方法学习&#xff0c;再学习中体会编程带来的快乐。先说明一下&#xff0c;奇牛课程是边学…

YOLOv6 4.0 使用记录:python推理 OpenCV DNN 推理

目录 1、下载源码 2、下载权重文件 3、配置环境 4、推理 6、ONNX格式导出 权重文件为yolov6list_s.pt 权重为yolov6.pt 7、opencv DNN推理 8、个人总结 1、下载源码 下载最新的4.0版本的 2、下载权重文件 我下的是YOLOv6Lite-S 3、配置环境 cd到项目目录&#xff0c;运…

关于IDEA编译运行时出现 《非法字符: ‘\ufeff‘ 需要class,interface或enum》的解决办法

问题如下 原因&#xff1a;编码问题解决办法&#xff1a;将这些报错的文件拷贝到一个目录中&#xff0c;然后用notpad打开&#xff0c;点击工具类上的 编码&#xff0c;将 以uft8格式编码 转为 以utf8无BOM格式编码&#xff0c;然后保存&#xff0c;再覆盖idea中对应的文件。或…

人脸识别--传统+深度方法

人脸识别算法--非深度方法 在前深度学习时代&#xff0c;非深度的方法探索了不同的人脸识别算法。 先考虑一下非深度学习时代&#xff0c;人脸识别难在哪&#xff1f;或者说目标识别的难点在哪&#xff1f; 图像是一个高度冗余的数据。 * 图像数据中包含大量与语义无关的内容…

Hadoop3.3.1 Windows环境配置

1.解压hadoop-3.3.1.tar.gz 到windows安装路径 D:\h3\hadoop-3.3.1 2.检查Windows jdk是否安装正常 3.下载winutils GitHub - steveloughran/winutils: Windows binaries for Hadoop versions (built from the git commit ID used for the ASF relase) 解压到D:\h3\ 并重命名…

Python每日一练(20230504) 课程表 Course Schedule I/II

目录 1. 课程表 Course Schedule I 2. 课程表 Course Schedule II &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 课程表 Course Schedule I 你这个学期必须选修 numCourses 门课程&a…

五一劳动节程序员应该知道的知识——计算机

前言 现在AI崛起&#xff0c;计算机已经成为我们不可或缺的一部分&#xff0c;几乎在所有行业和领域都有广泛应用 。 五一已经快要过去了&#xff0c;程序员们应该都放假了&#xff0c;那我们是不是应该去了解了解我们的伙伴——计算机&#xff0c;了去解计算机是怎样工作的&am…

在CSDN逮到一个字节10年老测试开发,聊过之后收益良多···

老话说的好&#xff0c;这人呐&#xff0c;一单在某个领域鲜有敌手了&#xff0c;就会闲得蛋疼。前几天我在上班摸鱼刷CSDN的时候认识了一位字节测试开发大佬&#xff0c;在字节工作了10年&#xff0c;因为本人天赋比较高&#xff0c;平时工作也兢兢业业&#xff0c;现在企业内…

REST API 最佳实践

文章目录 0.什么是 REST API&#xff1f;1.REST API 设计建议1.用名词表示资源2.用复数名词表示集合3.在端点上使用嵌套显示关系4.用 HTTP 方法操作资源5.用过滤、排序和分页请求数据6.用 JSON 作为发送和接收数据的格式7.将实际数据包装在 data 字段中8.非资源请求用动词9.考虑…