Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 513|回复: 0

[默认分类] 动态热修复技术的实现

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-7-9 20:24:20 | 显示全部楼层 |阅读模式

    项目地址:https://github.com/dodola/HotFix
    HotFix
    安卓App热补丁动态修复框架
    介绍
    该项目是基于QQ空间终端开发团队的技术文章实现的,完成了文章中提到的基本功能。
    文章地址:安卓App热补丁动态修复技术介绍
    项目部分代码从 dalvik_patch 项目中修改而来,这个项目本来是用来实现multidex的,发现可以用来实现方法替换的效果。
    项目包括核心类库,补丁制作库,例子。可以直接运行代码看效果。
    详细说明
    补丁制作
    该技术的原理很简单,其实就是用ClassLoader加载机制,覆盖掉有问题的方法。所以我们的补丁其实就是有问题的类打成的一个包。
    例子中的出现问题的类是
    1. dodola.hotfix.BugClass
    复制代码
    原始代码如下:
    1. [code]public class BugClass {
    2.     public String bug() {
    3.         return "bug class";
    4.     }
    5. }
    复制代码
    [/code]
    我们假设
    1. BugClass
    复制代码
    类里的
    1. bug()
    复制代码
    方法出现错误,需要修复,修复代码如下:
    1. [code]public class BugClass {
    2.     public String bug() {
    3.         return "fixed class";
    4.     }
    5. }
    复制代码
    [/code]
    那么我们只需要将修复过的类编译后打包成dex即可
    步骤如下:

      将补丁类提取出来到一个文件夹里

       
       
       

       
         patch1.png
       
       
      将class文件打入一个jar包中
    1. jar cvf path.jar *
    复制代码
      将jar包转换成dex的jar包
    1. dx --dex --output=path_dex.jar path.jar
    复制代码

    这样就生成了补丁包
    1. path_dex.jar
    复制代码

      
      

      
       patch2.png
      

    实现javassist动态代码注入
    实现这一部分功能的原因主要是因为出现如下异常
    1. java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
    复制代码
    问题原因在文档中已经描述的比较清楚。
      就是如果以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED
    很明显,解决的方法就是在类中引用一个其他dex中的类,但是源码方式的引用会将引用的类打入同一个dex中,所以我们需要找到一种既能编译通过并且将两个互相引用的类分离到不同的dex中,于是就有了这个动态的代码植入方式。
    首先我们需要制作引用类的dex包,代码在
    1. hackdex
    复制代码
    中,我直接使用了文档中的类名
    1. AntilazyLoad
    复制代码
    这样可以和文章中对应起来,方便一些。
    我们将这个库打包成dex的jar包,方法跟制作补丁一样。
    下面是重点,我们要用
    1. javassist
    复制代码
    将这个类在编译打包的过程中插入到目标类中。
    为了方便,我将这个过程做成了一个Gradle的Task,代码在
    1. buildSrc
    复制代码
    中。
    这个项目是使用Groovy开发的,需要配置Groovy SDK才可以编译成功。
    核心代码如下:
    1. [code] /**
    2. * 植入代码
    3. * @param buildDir 是项目的build class目录,就是我们需要注入的class所在地
    4. * @param lib 这个是hackdex的目录,就是AntilazyLoad类的class文件所在地
    5. */
    6.     public static void process(String buildDir, String lib) {
    7.         println(lib)
    8.         ClassPool classes = ClassPool.getDefault()
    9.         classes.appendClassPath(buildDir)
    10.         classes.appendClassPath(lib)
    11.         //下面的操作比较容易理解,在将需要关联的类的构造方法中插入引用代码
    12.         CtClass c = classes.getCtClass("dodola.hotfix.BugClass")
    13.         println("====添加构造方法====")
    14.         def constructor = c.getConstructors()[0];
    15.         constructor.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);")
    16.         c.writeFile(buildDir)
    17.         CtClass c1 = classes.getCtClass("dodola.hotfix.LoadBugClass")
    18.         println("====添加构造方法====")
    19.         def constructor1 = c1.getConstructors()[0];
    20.         constructor1.insertBefore("System.out.println(dodola.hackdex.AntilazyLoad.class);")
    21.         c1.writeFile(buildDir)
    22.         growl("ClassDumper", "${c.frozen}")
    23.     }
    复制代码
    [/code]
    下面在代码编译完成,打包之前,执行植入代码的task就可以了。
    在 app 项目的 build.gradle 中插入如下代码
    1. [code]task("processWithJavassist") << {
    2.     String classPath = file("build/intermediates/classes/debug")//项目编译class所在目录
    3.     dodola.patch.PatchClass.process(classPath, project(":hackdex").buildDir
    4.             .absolutePath + "/intermediates/classes/debug")//第二个参数是hackdex的class所在目录
    5. }
    6. android{
    7.    .......
    8.     applicationVariants.all { variant ->
    9.         variant.dex.dependsOn << processWithJavassist //在执行dx命令之前将代码打入到class中
    10.     }
    11. }
    复制代码
    [/code]
    反编译编译后的apk可以发现,代码已经植入进去,而且包里并不存在
    1. dodola.hackdex.AntilazyLoad
    复制代码
    这个类

      
      

      
       patch3.png
      

    ISSUE
    开发测试过程中遇到一些问题,这种方法无法在已经加载好的类中实现动态替换,只能在类加载之前替换掉。就是说,补丁下载下来后,只能等待用户重启应用才能完成补丁效果。





      文/dodomix(简书作者)
      
    原文链接:http://www.jianshu.com/p/56facb3732a7
      
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
      
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-6-2 23:55 , Processed in 0.335386 second(s), 38 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表