Andfix热修复_极光 热修复-程序员宅基地

技术标签: 技术  Android 热修复之Andfix  

一、什么是热修复

热修复说白了就是”打补丁”,比如你们公司上线一个app,用户反应有重大bug,需要紧急修复。如果按照通
常做法,那就是程序猿加班搞定bug,然后测试,重新打包并发布。这样带来的问题就是成本高,效率低。于是,热
修复就应运而生.一般通过事先设定的接口从网上下载无Bug的代码来替换有Bug的代码。这样就省事多了,用
户体验也好。

二、热修复的原理

1.Android的类加载机制

android的类加载器分为两种,PathClassLoader和DexClassLoader,两者都继承自BaseDexClassLoader

PathClassLoader代码位于libcore\dalvik\src\main\Java\dalvik\system\PathClassLoader.java
DexClassLoader代码位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
BaseDexClassLoader代码位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java

  • PathClassLoader
  • 用来加载系统类和应用类

  • DexClassLoader

    用来加载jar、apk、dex文件.加载jar、apk也是最终抽取里面的Dex文件进行加载.

    这里写图片描述

2.热修复机制

看下PathClassLoader代码

public class PathClassLoader extends BaseDexClassLoader {
     

    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
} 

DexClassLoader代码

public class DexClassLoader extends BaseDexClassLoader {
     

    public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

两个ClassLoader就两三行代码,只是调用了父类的构造函数.

public class BaseDexClassLoader extends ClassLoader {
     
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected 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;
    }

在BaseDexClassLoader 构造函数中创建一个DexPathList类的实例,这个DexPathList的构造函数会创建一个dexElements 数组

public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {
        ... 
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        //创建一个数组
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
        ... 
    }

然后BaseDexClassLoader 重写了findClass方法,调用了pathList.findClass,跳到DexPathList类中.

/* package */final class DexPathList {
    ...
    public Class findClass(String name, List<Throwable> suppressed) {
            //遍历该数组
        for (Element element : dexElements) {
            //初始化DexFile
            DexFile dex = element.dexFile;

            if (dex != null) {
                //调用DexFile类的loadClassBinaryName方法返回Class实例
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }       
        return null;
    }
    ...
} 

会遍历这个数组,然后初始化DexFile,如果DexFile不为空那么调用DexFile类的loadClassBinaryName方法返回Class实例.
归纳上面的话就是:ClassLoader会遍历这个数组,然后加载这个数组中的dex文件.
而ClassLoader在加载到正确的类之后,就不会再去加载有Bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可.

这里有个问题,可参考QQ空间团队的 安卓App热补丁动态修复技术介绍
概括来讲:如果引用者和被引用者的类(直接引用关系)在同一个Dex时,那么在虚拟机启动时,被引用类就会被打上CLASS_ISPREVERIFIED标志,这样被引用的类就不能进行热修复操作了.
那么我们就要阻止被引用类打上CLASS_ISPREVERIFIED标志.QQ空间的方法是在所有引用到该类的构造函数中插入一段代码,代码引用到别的类.

三、热修复的例子

我用的是阿里开源的热修复框架AndFix热修复框架地址

其实它的原理也是动态加载class文件,然后调用反射完成修复.可参考我上一篇写的
Java的ClassLoader加载机制

AndFix是 “Android Hot-Fix”的缩写。它支持Android 2.3到6.0版本,并且支持arm与X86系统架构的设备。完美支持Dalvik与ART的Runtime。AndFix 的补丁文件是以 .apatch 结尾的文件。

我这是用eclipse写的Demo.

1.把AndFix抽取成library依赖的形式

这里写图片描述

2.新建一个AndFixDemo项目,依赖AndFix这个library

2.1

新建一个MyApplication继承Application

public class MyApplication extends Application {
     

    private static final String TAG = "MyApplication";

    /**
     * apatch文件
     */
    private static final String APATCH_PATH = "/Dennis.apatch";

    private PatchManager mPatchManager;

    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化
        mPatchManager = new PatchManager(this);
        mPatchManager.init("1.0"); // 版本号

        // 加载 apatch
        mPatchManager.loadPatch();

        //apatch文件的目录
        String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH;
        File apatchPath = new File(patchFileString);

        if (apatchPath.exists()) {
            Log.i(TAG, "补丁文件存在");
            try {
                //添加apatch文件
                mPatchManager.addPatch(patchFileString);
            } catch (IOException e) {
                Log.i(TAG, "打补丁出错了");
                e.printStackTrace();
            }
        } else {
            Log.i(TAG, "补丁文件不存在");
        }

    }

}

实际当中肯定是通过网络接口下载apatch文件,我这里为了方便演示就放在了SD卡根目录

2.2

在MainActivity用一个按钮弹出吐司,上面是有Bug的代码,下面是修正后的代码

这里写图片描述

这里写图片描述

分别打包成Bug.apk和NoBug.apk

这里写图片描述

2.3

然后要用到一个生成补丁的工具apkpatch

解压

这里写图片描述

_MACOSX是给OSX系统用的
.bat是给window系统用的

我用得是.bat

把之前生成的Bug.apkNoBug.apk,还有打包所使用的keystore文件放到apkpatch-1.0.3目录下
打开cmd,进入到apkpatch-1.0.3目录下,输入如下指令

apkpatch.bat -f NoBug.apk -t Bug.apk -o Dennis -k keystore -p 111111 -a 111111 -e 111111

每个参数含义如下

-f 新版本的apk
-t 旧版本的apk
-o 输出apatch文件的文件夹,可以随意命名
-k 打包的keystore文件名
-p keystore的密码
-a keystore 用户别名
-e keystore 用户别名的密码

这里写图片描述

如果出现add modified …….就表示成功了,去apkpatch-1.0.3目录看下,新增了Dennis目录

这里写图片描述

这里写图片描述

我把这个文件改为Dennis.apatch

2.4

手机装上Bug.apk运行起来

这里写图片描述

然后把Dennis.apatch 放到SD卡根目录,退出app,再进入,按下按钮

这里写图片描述

最后附上Demo还有apk和apatch 文件 打开链接

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cs15229216584/article/details/76177400

智能推荐

Java面试题之:日志_logback面试问题-程序员宅基地

文章浏览阅读614次。Java面试题之:日志一、Slf4j二、Log4j三、LogBackLogback 优点四、ELK一、Slf4j  slf4j 的全称是 Simple Loging Facade For Java,即它仅仅是一个为 Java 程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就比如 JDBC 一样,只是一种规则而已。所以单独的 slf4j 是不能工作的,必须搭配其他具体的日志实现方案,比如 apache 的 org.apache.log4j.Logger,jdk 自带的 java.util.lo_logback面试问题

树莓派安装mysql并开启远程访问(开启3306端口)_树莓派 mysql client sdk-程序员宅基地

文章浏览阅读2.5w次,点赞2次,收藏15次。使用dpkg -l|grep mysql:查看是否安装mysql注意:在2016-02-26-raspbian-jessie这个版本中:系统安装了mysql和java 在2015-05-05-raspbian-wheezy这个版本中:系统没有安装mysql、java1、卸载mysql一开始安装的操作系统是raspbian-jessie,故需_树莓派 mysql client sdk

python3.6 学习笔记之安装PIL_python3.6 image.pil-程序员宅基地

文章浏览阅读4.6k次。安装环境:Python 3.6.3PIL(Python Image Library)是python 的一个强大的图像处理库,不过只支持到python2.7pillow是PIL的一个派生分支,如今已发展成比PIL更具活力的图像处理库本次安装使用pip命令查看python的版本安装pillow步骤如下:1、以管理员运行命令提示符,首先_python3.6 image.pil

TP5隐藏public和index.php_tp5.1 nginx 去除public-程序员宅基地

文章浏览阅读3.1k次。个人理解:将public下的index.php文件移动到主目录下和更改index的入口文件可以在URL去掉public将public下的.htaccess文件复制到主目录下并更改配置是:当url地址访问不存在的文件或路径时,调用正则表达式进行替换自动补齐/index.php/。也就是说即使你加上index.php访问也不会出错。一、Apache1、public下的index.php入口文件..._tp5.1 nginx 去除public

bash命令的使用方法_bash怎么用-程序员宅基地

文章浏览阅读1.7w次,点赞11次,收藏58次。小编给大家分享一下bash命令的使用方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Bash(Bash)是Bourne Again Shell的缩写,用于执行描述命令(如Linux中的命令)的shell。在Linux上采用bash作为标准,基本上它描述了对带有“.sh”扩展名的vi编辑器等文本的处理并执行。与编程一样,它有许多函数,如变量,函数和算术处理,所以如果你是一个小程序,你可以用bash编写..._bash怎么用

创意多彩CSS3垂直时间轴特效_基于css实现垂直时间轴特效-程序员宅基地

文章浏览阅读1.5k次。简要教程这是一款创意多彩CSS3垂直时间轴特效。该CSS3垂直时间轴通过CSS伪元素和css transform属性,制作椭圆形和箭头,并配以多彩的颜色,构建出漂亮的垂直时间轴效果。 使用方法在页面中引入bootstrap.min.css文件。&lt;link rel="stylesheet" href="bootstrap.min.css" type="text/css"&gt; HTML结构该..._基于css实现垂直时间轴特效

随便推点

嵌入式Linux系统BSP简介_auto linux bsp-程序员宅基地

文章浏览阅读1k次。 作者:李智敏,华清远见嵌入式学院讲师。嵌入式系统由硬件环境、嵌入式操作系统和应用程序组成,硬件环境是操作系统和应用程序运行的硬件平台,它随应用的不同而有不同的要求。硬件平台的多样性是嵌入式系统的主要特点,如何使嵌入式操作系统在不同的硬件平台上有效地运行,是嵌入式系统开发中需要解决的关键问题。解决的方法是在硬件平台和操作系统之间提供硬件相关层来屏蔽这些硬件的差异,给操作系统提供统一的运行环_auto linux bsp

源码仓库搭建---linux搭建svn服务器_linux服务器做源码库-程序员宅基地

文章浏览阅读151次。1、检查系统是否已经安装如果安装就卸载检查:svnserve --version卸载:yum remove subversion2、安装yum install subversion3、建立SVN库(文件位置可自由)创建仓库文件夹:mkdir -p/opt/svn/repository用svn管理员身份创建一个仓库:svnadmincreate/opt/svn/repository执行上面的命令后,自动建立repositories库,查看/opt/svn..._linux服务器做源码库

python程序的循环结构_循环14到19的程序用python的-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏14次。遍历循环 for无限循环 while循环控制保留字循环的高级用法遍历循环遍历某个结构形成的运行方式for &amp;amp;amp;lt;循环变量&amp;amp;amp;gt; in &amp;amp;amp;lt;遍历结构&amp;amp;amp;gt; : &amp;amp;amp;lt;语句块&amp;amp;amp;gt;从遍历结构中逐一提取元素,放在循环变量由保留字fo_循环14到19的程序用python的

统计大写辅音字母 (15分)_7-11 统计大写辅音字母 (15分)英文辅音字母是除a、e、i、o、u以外的字母。本题要求编写程序-程序员宅基地

文章浏览阅读1k次。英文辅音字母是除A、E、I、O、U以外的字母。本题要求编写程序,统计给定字符串中大写辅音字母的个数。输入格式:输入在一行中给出一个不超过80个字符、并以回车结束的字符串。输出格式:输出在一行中给出字符串中大写辅音字母的个数。输入样例:HELLO World!输出样例:4注意点就是记录的字母一定是要大写的;参考代码#include<stdio.h>#include..._7-11 统计大写辅音字母 (15分)英文辅音字母是除a、e、i、o、u以外的字母。本题要求编写程序,统计给定字符串中大写辅音字母的个数。

【教程】phpstudy设置伪静态-程序员宅基地

文章浏览阅读1k次。开启phpstudy扩展,勾选“rewrite_module”开启phpstudy伪静态支持,打开httpd-conf,修改所有“AllowOverrideNone”为“AllowOverrideAll”修改并保存后,需重启Apache服务器在网站根目录下添加.htaccess文件,创建时需要使用文本编辑器进行另存为创建,比如Editplus.htaccess文件代码..._php_study 伪静态

从sockaddr结构获取IP和端口_socketaddress获取ip和端口号-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏10次。前言 从sockaddr结构中提取IP, 先将结构sockaddr转为sockaddr_in结构,然后用在利用相关API将其中的IP地址从网络格式转化我们熟悉点分十进制的字符串。一、sockaddr和sockaddr_in结构程序员不应操作sockaddr结构,sockaddr是给操作系统用的程序员应使用sockaddr_in来表示地址,sockaddr_in区分..._socketaddress获取ip和端口号