热更新_热更新原理 - CSDN
精华内容
参与话题
  • 如何实现热更新

    千次阅读 2018-07-12 18:01:45
    热更新的优点热更新是一个绝对很酷的功能.简单来说,它的好处有两点:一个是提高开发效率,一个是在线上修复问题.可能有些同学不太理解,毕竟大家的技术背景不太一样,所以这里还是展开来讨论一下.先说开发效率.我以前...

    热更新的优点

    热更新是一个绝对很酷的功能.简单来说,它的好处有两点:一个是提高开发效率,一个是在线上修复问题.可能有些同学不太理解,毕竟大家的技术背景不太一样,所以这里还是展开来讨论一下.

    先说开发效率.我以前曾经做过一段游戏服务器的开发,与web服务器不太一样的是,游戏服务器通常需要在启动的时候加载很多数据进来.如果你使用的是编译型语言做游戏服务器的开发,那么假如进行了一些修改,除去编码-编译的过程不说,每次重启服务器验证时加载数据的时间都够你上个厕所的.所以,基本上现在做游戏服务器的开发,如果完全使用编译型语言,那么会很苦逼,一般都采用编译型语言结合脚本语言的方式.

    有了脚本语言的热更新功能,还是刚才的工作场景,不仅没有了编译的过程,而且还不需要重启服务器了.在使用了Lua来编写游戏服务器的逻辑之后,我经常一整天都不需要重启服务器,就可以完成整个工作,开发效率提高了很多.

    也正是因为热更新能使得不需要重启服务器就能更新代码,所以也可以用在线上修复问题.

    热更新的缺点

    前面说的只是热更新的优点,也需要提一提热更新的缺点.

    脚本语言虽然不需要编译,但是这也存在一个缺陷,就是代码要到运行时才能跑到,也只有到那个时候才能知道代码有没有问题.换言之,需要非常多的测试覆盖来保证脚本的正确性.

    另外,前面提到热更新能在线上修复问题,但是不能因为这样就过度依赖这个特性,更不能把修复变成了新增功能,热更新修复线上问题是实在万不得已才为之的.

    使用qnode如何实现热更新

    要支持项目的Lua脚本热更新,在qnode中需要做一些特殊的事情.以examples/qcached中的memcached服务器为例来说明.

    该项目中在配置文件中指定加载的主文件是main.lua,来看看它的内容:

    require("util")
    
    require_ex("server")
    require_ex("child")
    

    该项目除了main.lua之外,还有另外两个Lua文件:server.lua和child.lua,所以这里main.lua在启动时调用util提供的函数require_ex来加载这两个文件.

    所有需要支持热更新的文件,在加载的时候都需要使用require_ex函数来加载.

    当修改了代码需要热更新验证时,可以向qnode服务器发送USR1信号来让服务器重新加载,这在根目录中reload.sh脚本中已经封装了该操作.

    简而言之,需要支持热更新的项目文件组织应该是这样的:由配置文件指定的服务器启动脚本文件中,使用require_ex函数来加载所有该项目的脚本文件,在需要热更新的时候发送USR1信号来通知服务器,它将按照顺序重新加载所有的脚本文件.

    展开全文
  • 热更新

    2019-06-11 23:39:54
    组件化 就是将app分成多个模板,每个模块都是一个组件(Model),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。...

    组件化

    就是将app分成多个模板,每个模块都是一个组件(Model),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。

    插件化

    将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。开发中,往往会堆积很多的需求进项目,超过 65535 后(会报错,详细解决方案百度去),插件化就是一个解决方案。

    增量更新

    与热更新区别最大的一个,其实这个大家应该很好理解,安卓上的有些很大的应用,特别是游戏,大则好几个G的多如牛毛,但是每次更新的时候却不是要去下载最新版,而只是下载一个几十兆的增量包就可以完成更新了,而这所使用的技术就是增量更新了。实现的过程大概是这个样子的:我们手机上安装着某个大应用,下载增量包之后,手机上的apk和增量包合并形成新的包,然后会再次安装,这个安装过程可能是可见的,或者应用本身有足够的权限直接在后台安装完成。

    热更新

    更新的类或者插件粒度较小的时候,我们会称之为热修复,一般用于修复bug!!比如更新一个bug方法或者紧急修改lib包,甚至一个类等。2016 Google 的 Android Studio 推出了Instant Run 功能 同时提出了3个名词;“热部署” – 方法内的简单修改,无需重启app和Activity。 “暖部署” – app无需重启,但是activity需要重启,比如资源的修改。 “冷部署” – app需要重启,比如继承关系的改变或方法的签名变化等。
    所以站在app开发者角度的“热”是指在不发版的情况来实现更新,而Google提出的“热”是指值无需重新启动。 同时在开发插件化的时候也有两种情景,一种是插件与宿主apk没有交互,只是在用户使用到的时候进行一次吊起,还有一种是与宿主有很多的交互。

    热修复框架分类

    在这里插入图片描述分类

    在这里插入图片描述

    Tinker原理

    在这里插入图片描述
    BaseApk就是我们的基准包,也就是渠道上线的包。
    NewApk就是我们的hotfix包,包括修复的代码资源以及so文件。
    Tinker做了对应的DexDiff、ResDiff、BsDiff来产出一个patch.apk,里面具体内容也是由lib、res和dex文件组成,assets中还有对应的dex、res和so信息

    然后Tinker通过找到基准包data/app/packagename/base.apk通过DexPatch合成新的dex,并且合成一个tinker_classN.apk(其实就是包含了所有合成dex的zip包)接着在运行时通过反射把这个合成dex文件插入到PathClassLoader中的dexElements数组的前面,保证类加载时优先加载补丁dex中的class。

    热修复原理

    在for循环遍历dexElements中,首先遍历出来的是dex文件,然后再是从dex文件中获取class,所以,我们只要让修复好的class打包成一个dex文件,放于Element数组的第一个元素,这样就能保证获取到的class是最新修复好的class了(当然,有bug的class也是存在的,不过是放在了Element数组的最后一个元素中,所以没有机会被拿到而已)。

    展开全文
  • 热更新介绍

    千次阅读 2018-10-02 09:10:32
    什么是热更新:游戏上线后玩家下载的第一个版本(70M~200M),在运营过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新打包,然后让玩家重新下载(浪费流量和时间,体验不好...
    • 什么是热更新:游戏上线后玩家下载的第一个版本(70M~200M),在运营过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新打包,然后让玩家重新下载(浪费流量和时间,体验不好)。 热跟新可以在不重新下载客户端的情况下更新游戏的内容。 热更新一般用在手机的网游上。
    • 为什么C#脚本不能直接进行更新:C#是一门编程语言,它运行之前需要在编译环境下进行编译,而这个编译过程在移动平台无法完成,所以当我们游戏逻辑更改、C#代码发生改变的时候,我们就需要重新在开发环境下编译(形成dll文件),然后重新打包让玩家下载更新最新的版本。 体验差、浪费流量。
    • 热更新有哪些实现方式:
      1. 使用Lua脚本编写游戏的UI或者其他的逻辑:Lua是一个精悍小巧的脚本语言,可以跨平台进行解析,而不需要编译的过程。
      2. 使用C#light
      3. 使用C#反射技术
    • 关于AssetBundle:Unity提供了一个资源更新技术,就是通过AssetBundle,我们可以通过AssetBundle更新游戏UI,也可以把脚本或者其他代码当成资源打包成AssetBundle然后更新到客户端。在所有的热更新技术中都需要AssetBundle
    • 如何利用Lua进行热更新:在移动端可以编写Lua的解析器,通过这个解析器,可以运行最新的Lua脚本,然后我们把控制游戏逻辑的代码都写成Lua脚本。
    • Lua的解析技术有哪些
      1. uLua 骏擎【CP】 ulua.org
      2. Nlua unity支持Riley G nlua.org
      3. UniLua 阿楠同学
      4. sLua
    展开全文
  • Android热更新研究与实现

    万次阅读 2019-07-11 17:35:59
    第一部分重点是将当下热门的热更新方案实现之后再研究,第二部分则是自己动手实现一个自己的热更新框架。 Android热更新技术的研究与实现之研究篇 ———概念讲解——– 热更新 相关概念 这个词出现的时间已经很...

    第一部分重点是将当下热门的热更新方案实现之后再研究,第二部分则是自己动手实现一个自己的热更新框架。

    Android热更新技术的研究与实现之研究篇

    ———概念讲解——–

    热更新 相关概念

    这个词出现的时间已经很久了,感觉现在要找工作才来看是晚了不少,但是好东西什么时候学习都不晚的。 

    今天看到一句话,和大家分享下,人一生有三样东西是别人抢不走的:

    吃进胃里的食物

    藏在心中的梦想

    读进大脑里的书

    所以趁着我们的时光正好,多学点东西肯定是赚翻的!!(当然多吃点也没错,不配点图感觉好突兀) 

    言归正传,首先我们要了解与热更新相关的一些概念吧!

    组件化—-就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。我之前的开发方式基本上都是这一种。具体可以参考Android组件化方案

    插件化–将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。开发中,往往会堆积很多的需求进项目,超过 65535 后,插件化就是一个解决方案。

    放张图帮大家理解:

    热更新 – 更新的类或者插件粒度较小的时候,我们会称之为热修复,一般用于修复bug!!比如更新一个bug方法或者紧急修改lib包,甚至一个类等。2016 Google 的 Android Studio 推出了Instant Run 功能 同时提出了3个名词;

    热部署” – 方法内的简单修改,无需重启app和Activity。 “暖部署” – app无需重启,但是activity需要重启,比如资源的修改。 “冷部署” – app需要重启,比如继承关系的改变或方法的签名变化等。 

    所以站在app开发者角度的“热”是指在不发版的情况来实现更新,而Google提出的“热”是指值无需重新启动。 同时在开发插件化的时候也有两种情景,一种是插件与宿主apk没有交互,只是在用户使用到的时候进行一次吊起,还有一种是与宿主有很多的交互。

    增量更新,与热更新区别最大的一个,其实这个大家应该很好理解,安卓上的有些很大的应用,特别是游戏,大则好几个G的多如牛毛,但是每次更新的时候却不是要去下载最新版,而只是下载一个几十兆的增量包就可以完成更新了,而这所使用的技术就是增量更新了。实现的过程大概是这个样子的:我们手机上安装着某个大应用,下载增量包之后,手机上的apk和增量包合并形成新的包,然后会再次安装,这个安装过程可能是可见的,或者应用本身有足够的权限直接在后台安装完成。

    今天碰到Android Studio的更新,这应该就是增量更新啦!补丁包只有51M,如果下载新版本有1G多。

    而热更新究竟是什么呢?

    有一些这样的情况, 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。老是发布版本用户会疯掉的!!!(好吧 猿猿们也会疯掉。。)

     

    这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?

    这种需要替换运行时新的类和资源文件的加载,就可以认为是热操作了。而在热更新出现之前,通过反射注解、反射调用和反射注入等方式已经可以实现类的动态加载了。而热更新框架的出现就是为了解决这样一个问题的。

    从某种意义上来说,热更新就是要做一件事,替换。当替换的东西属于大块内容的时候,就是模块化了,当你去替换方法的时候,叫热更新,当你替换类的时候,加热插件,而且重某种意义上讲,所有的热更新方案,都是一种热插件,因为热更新方案就是在app之外去干这个事。就这么简单的理解。无论是替换一个类,还是一个方法,都是在干替换这件事请。。这里的替换,也算是几种hook操作,无论在什么代码等级上,都是一种侵入性的操作。

    所以总结一句话简单理解热更新 HotFix 就是改变app运行行为的技术!(或者说就是对已发布app进行bug修复的技术) 此时的猿猿们顿时眼前一亮,用户也笑了。。

    好的,现在我们已经知道热更新为何物了,那么我们就先看看热更新都有哪些成熟的方案在使用了。

    在我们写好的安卓项目中,有很多逻辑代码,在预编译和编译阶段互相连在一起,各种业务逻辑的链接和lib的链接,各种变量和运算符的的编译优化;

    热更新方案介绍

    热更新方案发展至今,有很多团队开发过不同的解决方案,包括Dexposed、AndFix,(HotFix)Sophix,Qzone超级补丁的类Nuwa方式,微信的Tinker, 大众点评的nuwa、百度金融的rocooFix, 饿了么的amigo以及美团的robust、腾讯的Bugly热更新。 

    苹果公司现在已经禁止了热更新,不过估计也阻止不了开发者们的热情吧!

    我先讲几种方案具体如何使用,说下原理,最后再讲如何实现一个自己的热更新方案!

    –Dexposed & AndFix & (HotFix)SopHix –阿里热更新方案

    Dexposed

    “Dexposed”是大厂阿里以前的一个开源热更新项目,基于Xposed“Xposed”的AOP框架,方法级粒度,可以进行AOP编程、插桩、热补丁、SDK hook等功能。

    Xposeed大家如果不熟悉的话可以在网上搜一下” Xposed源码剖析——概述”,我以前用Xposed做过一些小东西(其实就是获取root权限后hook修改一些手机数据,比如支付宝步数,qq微信步数等,当然了,余额啥的是改不了的),在这里就不献丑了,毕竟重点也不是这个。我们可以看出Xposed有一个缺陷就是需要root,而Dexposed就是一个不需要root权限的hook框架。目前阿里系主流app例如手机淘宝,支付宝,天猫都使用了Dexposed支持在线热更新。

    Dexposed中的AOP原理来自于Xposed。在Dalvik虚拟机下,主要是通过改变一个方法对象方法在Dalvik虚拟机中的定 义来实现,具体做法就是将该方法的类型改变为native并且将这个方法的实现链接到一个通用的Native Dispatch方法上。这个 Dispatch方法通过JNI回调到Java端的一个统一处理方法,最后在统一处理方法中调用before, after函数来实现AOP。在Art虚拟机上目前也是是通过改变一个 ArtMethod的入口函数来实现。

    android4.4之后的版本都用Art取代了Dalvik,所以要hook Android4.4以后的版本就必须去适配Art虚拟机的机制。目前官方表示,为了适配Art的dexposed_l只是beta版,所以最好不要在正式的线上产品中使用它。

    Dexposed已经被抛弃,原因很明显,4.4以后不支持了,我们就不细细分析这个方案了,感兴趣的朋友可以通过“这里”了解。简单讲下它的实现方式:

    引入一个名为patchloader的jar包,这个函数库实现了一个热更新框架,宿主apk(可能含有bug的上线版本)在发布时会将这个jar包一起打包进apk中

    补丁apk(已修复线上版本bug的版本)只是在编译时需要这个jar包,但打包成apk时不包含这个jar包,以免补丁apk集成到宿主apk中时发生冲突

    补丁apk将会以provided的形式依赖dexposedbridge.jar和patchloader.jar

    通过在线下载的方式从服务器下载补丁apk,补丁apk集成到宿主apk中,使用补丁apk中的函数替换原来的函数,从而实现在线修复bug的功能。

    AndFix

    AndFix是一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。AndFix就是 “Android Hot-Fix”的缩写。支持Android 2.3到6.0版本,并且支持arm与X86系统架构的设备。完美支持Dalvik与ART的Runtime。AndFix 的补丁文件是以 .apatch 结尾的文件。它从你的服务器分发到你的客户端来修复你App的bug 。

    AndFix的实现方式(画的丑勿怪⊙﹏⊙):

    首先添加依赖

    compile ‘com.alipay.euler:andfix:0.3.1@aar’

    然后在Application.onCreate() 中添加以下代码

    patchManager = new PatchManager(context);

    patchManager.init(appversion);//current version

    patchManager.loadPatch();

    可以用这句话获取appversion,每次appversion变更都会导致所有补丁被删除,如果appversion没有改变,则会加载已经保存的所有补丁。

    String appversion= getPackageManager().getPackageInfo(getPackageName(), 0).versionName;

    然后在需要的地方调用PatchManager的addPatch方法加载新补丁,比如可以在下载补丁文件之后调用。

    之后就是打补丁的过程了,首先生成一个apk文件,然后更改代码,在修复bug后生成另一个apk。通过官方提供的工具apkpatch生成一个.apatch格式的补丁文件,需要提供原apk,修复后的apk,以及一个签名文件。

    通过网络传输或者adb push的方式将apatch文件传到手机上,然后运行到addPatch的时候就会加载补丁。

    AndFix更新的原理:

    首先通过虚拟机的JarFile加载补丁文件,然后读取PATCH.MF文件得到补丁类的名称

    使用DexFile读取patch文件中的dex文件,得到后根据注解来获取补丁方法,然后根据注解中得到类名和方法名,使用classLoader获取到Class,然后根据反射得到bug方法。

    jni层使用C++的指针替换bug方法对象的属性来修复bug。

    具体的代码主要都是我们在Application中初始化的PatchManager中。

    public PatchManager(Context context) {

        mContext = context;

        mAndFixManager = new AndFixManager(mContext);//初始化AndFixManager

        mPatchDir = new File(mContext.getFilesDir(), DIR);//初始化存放patch补丁文件的文件夹

        mPatchs = new ConcurrentSkipListSet();//初始化存在Patch类的集合,此类适合大并发

        mLoaders = new ConcurrentHashMap();//初始化存放类对应的类加载器集合

    }

    其中mAndFixManager = new AndFixManager(mContext);//初始化AndFixManager:

    public AndFixManager(Context context) {

        mContext = context;

        mSupport = Compat.isSupport();//判断Android机型是否适支持AndFix

        if (mSupport) {

            mSecurityChecker = new SecurityChecker(mContext);//初始化签名判断类

            mOptDir = new File(mContext.getFilesDir(), DIR);//初始化patch文件存放的文件夹

            if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail

                mSupport = false;

                Log.e(TAG, "opt dir create error.");

            } else if (!mOptDir.isDirectory()) {// not directory

                mOptDir.delete();//如果不是文件目录就删除

                mSupport = false;

            }

        }

    }

    。。。。。。。。。。。。

    然后是对版本的初始化mPatchManager.init(appversion)init(String appVersion)代码如下:

    public void init(String appVersion) {

        if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail

            Log.e(TAG, "patch dir create error.");

            return;

        } else if (!mPatchDir.isDirectory()) {// not directory

            mPatchDir.delete();

            return;

        }

        SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,

                Context.MODE_PRIVATE);//存储关于patch文件的信息

        //根据你传入的版本号和之前的对比,做不同的处理

        String ver = sp.getString(SP_VERSION, null);

        if (ver == null || !ver.equalsIgnoreCase(appVersion)) {

            cleanPatch();//删除本地patch文件

            sp.edit().putString(SP_VERSION, appVersion).commit();//并把传入的版本号保存

        } else {

            initPatchs();//初始化patch列表,把本地的patch文件加载到内存

        }

    }

    /*************省略初始化、删除、加载具体方法实现*****************/

    init初始化主要是对patch补丁文件信息进行保存或者删除以及加载。

    那么patch补丁文件是如何加载的呢?其实patch补丁文件本质上是一个jar包,使用JarFile来读取即可:

    public Patch(File file) throws IOException {

        mFile = file;

        init();

    }

    @SuppressWarnings("deprecation")

    private void init() throws IOException {

        JarFile jarFile = null;

        InputStream inputStream = null;

        try {

            jarFile = new JarFile(mFile);//使用JarFile读取Patch文件

            JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);//获取META-INF/PATCH.MF文件

            inputStream = jarFile.getInputStream(entry);

            Manifest manifest = new Manifest(inputStream);

            Attributes main = manifest.getMainAttributes();

            mName = main.getValue(PATCH_NAME);//获取PATCH.MF属性Patch-Name

            mTime = new Date(main.getValue(CREATED_TIME));//获取PATCH.MF属性Created-Time

            mClassesMap = new HashMap>();

            Attributes.Name attrName;

            String name;

            List strings;

            for (Iterator it = main.keySet().iterator(); it.hasNext();) {

                attrName = (Attributes.Name) it.next();

                name = attrName.toString();

                //判断name的后缀是否是-Classes,并把name对应的值加入到集合中,对应的值就是class类名的列表

                if (name.endsWith(CLASSES)) {

                    strings = Arrays.asList(main.getValue(attrName).split(","));

                    if (name.equalsIgnoreCase(PATCH_CLASSES)) {

                        mClassesMap.put(mName, strings);

                    } else {

                        mClassesMap.put(

                                name.trim().substring(0, name.length() - 8),// remove

                                                                            // "-Classes"

                                strings);

                    }

                }

            }

        } finally {

            if (jarFile != null) {

                jarFile.close();

            }

            if (inputStream != null) {

                inputStream.close();

            }

        }

    }

    然后就是最重要的patchManager.loadPatch()

    public void loadPatch() {

        mLoaders.put("*", mContext.getClassLoader());// wildcard

        Set patchNames;

        List classes;

        for (Patch patch : mPatchs) {

            patchNames = patch.getPatchNames();

            for (String patchName : patchNames) {

                classes = patch.getClasses(patchName);//获取patch对应的class类的集合List

                mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),

                        classes);//修复bug方法

            }

        }

    }

    循环获取补丁对应的class类来修复bug方法,mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),classes)

    public synchronized void fix(File file, ClassLoader classLoader,

            List classes) {

        if (!mSupport) {

            return;

        }

        //判断patch文件的签名

        if (!mSecurityChecker.verifyApk(file)) {// security check fail

            return;

        }

            /******省略部分代码********/

            //加载patch文件中的dex

            final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),

                    optfile.getAbsolutePath(), Context.MODE_PRIVATE);

            if (saveFingerprint) {

                mSecurityChecker.saveOptSig(optfile);

            }

            ClassLoader patchClassLoader = new ClassLoader(classLoader) {

                @Override

                protected Class findClass(String className)

                        throws ClassNotFoundException {//重写ClasLoader的findClass方法

                    Class clazz = dexFile.loadClass(className, this);

                    if (clazz == null

                            && className.startsWith("com.alipay.euler.andfix")) {

                        return Class.forName(className);// annotation’s class

                                                        // not found

                    }

                    if (clazz == null) {

                        throw new ClassNotFoundException(className);

                    }

                    return clazz;

                }

            };

            Enumeration entrys = dexFile.entries();

            Class clazz = null;

            while (entrys.hasMoreElements()) {

                String entry = entrys.nextElement();

                if (classes != null && !classes.contains(entry)) {

                    continue;// skip, not need fix

                }

                clazz = dexFile.loadClass(entry, patchClassLoader);//获取有bug的类文件

                if (clazz != null) {

                    fixClass(clazz, classLoader);// next code

                }

            }

        } catch (IOException e) {

            Log.e(TAG, "pacth", e);

      }

    }

    private void fixClass(Class clazz, ClassLoader classLoader) {

        Method[] methods = clazz.getDeclaredMethods();

        MethodReplace methodReplace;

        String clz;

        String meth;

        for (Method method : methods) {

            //获取此方法的注解,因为有bug的方法在生成的patch的类中的方法都是有注解的

            methodReplace = method.getAnnotation(MethodReplace.class);

            if (methodReplace == null)

                continue;

            clz = methodReplace.clazz();//获取注解中clazz的值

            meth = methodReplace.method();//获取注解中method的值

            if (!isEmpty(clz) && !isEmpty(meth)) {

                replaceMethod(classLoader, clz, meth, method);//next code

            }

        }

    }

    private void replaceMethod(ClassLoader classLoader, String clz,

            String meth, Method method) {

        try {

            String key = clz + "@" + classLoader.toString();

            Class clazz = mFixedClass.get(key);//判断此类是否被fix

            if (clazz == null) {// class not load

                Class clzz = classLoader.loadClass(clz);

                // initialize target class

                clazz = AndFix.initTargetClass(clzz);//初始化class

            }

            if (clazz != null) {// initialize class OK

                mFixedClass.put(key, clazz);

                Method src = clazz.getDeclaredMethod(meth,

                        method.getParameterTypes());//根据反射获取到有bug的类的方法(有bug的apk)

                AndFix.addReplaceMethod(src, method);//src是有bug的方法,method是补丁方法

            }

        } catch (Exception e) {

            Log.e(TAG, "replaceMethod", e);

      }

    }

    public static void addReplaceMethod(Method src, Method dest) {

        try {

            replaceMethod(src, dest);//调用了native方法

            initFields(dest.getDeclaringClass());

        } catch (Throwable e) {

            Log.e(TAG, "addReplaceMethod", e);

        }

    }

    private static native void replaceMethod(Method dest, Method src);

    从上面的bug修复源码可以看出,就是在找补丁包中有@MethodReplace注解的方法,然后反射获取原apk中方法的位置,最后进行替换。

    而最后调用的replaceMethod(Method dest,Method src)则是native方法,源码中有两个replaceMethod:

    extern void dalvik_replaceMethod(JNIEnv* env, jobject src, jobject dest);//Dalvik

    extern void art_replaceMethod(JNIEnv* env, jobject src, jobject dest);//Art

    从源码的注释也能看出来,因为安卓4.4版本之后使用的不再是Dalvik虚拟机,而是Art虚拟机,所以需要对不同的手机系统做不同的处理。

    首先看Dalvik替换方法的实现:

    extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(

        JNIEnv* env, jobject src, jobject dest) {

        jobject clazz = env->CallObjectMethod(dest, jClassMethod);

        ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(

            dvmThreadSelf_fnPtr(), clazz);

        clz->status = CLASS_INITIALIZED;

        Method* meth = (Method*) env->FromReflectedMethod(src);

        Method* target = (Method*) env->FromReflectedMethod(dest);

        LOGD("dalvikMethod: %s", meth->name);

        meth->jniArgInfo = 0x80000000;

        meth->accessFlags |= ACC_NATIVE;//把Method的属性设置成Native方法

        int argsSize = dvmComputeMethodArgsSize_fnPtr(meth);

        if (!dvmIsStaticMethod(meth))

        argsSize++;

        meth->registersSize = meth->insSize = argsSize;

        meth->insns = (void*) target;

        meth->nativeFunc = dalvik_dispatcher;//把方法的实现替换成native方法

    }

    Art替换方法的实现:

    //不同的art系统版本不同处理也不同

    extern void __attribute__ ((visibility ("hidden"))) art_replaceMethod(

            JNIEnv* env, jobject src, jobject dest) {

        if (apilevel > 22) {

            replace_6_0(env, src, dest);

        } else if (apilevel > 21) {

            replace_5_1(env, src, dest);

        } else {

            replace_5_0(env, src, dest);

        }

    }

    //以5.0为例:

    void replace_5_0(JNIEnv* env, jobject src, jobject dest) {

        art::mirror::ArtMethod* smeth =

                (art::mirror::ArtMethod*) env->FromReflectedMethod(src);

        art::mirror::ArtMethod* dmeth =

                (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);

        dmeth->declaring_class_->class_loader_ =

                smeth->declaring_class_->class_loader_; //for plugin classloader

        dmeth->declaring_class_->clinit_thread_id_ =

                smeth->declaring_class_->clinit_thread_id_;

        dmeth->declaring_class_->status_ = (void *)((int)smeth->declaring_class_->status_-1);

        //把一些参数的指针给补丁方法

        smeth->declaring_class_ = dmeth->declaring_class_;

        smeth->access_flags_ = dmeth->access_flags_;

        smeth->frame_size_in_bytes_ = dmeth->frame_size_in_bytes_;

        smeth->dex_cache_initialized_static_storage_ =

                dmeth->dex_cache_initialized_static_storage_;

        smeth->dex_cache_resolved_types_ = dmeth->dex_cache_resolved_types_;

        smeth->dex_cache_resolved_methods_ = dmeth->dex_cache_resolved_methods_;

        smeth->vmap_table_ = dmeth->vmap_table_;

        smeth->core_spill_mask_ = dmeth->core_spill_mask_;

        smeth->fp_spill_mask_ = dmeth->fp_spill_mask_;

        smeth->mapping_table_ = dmeth->mapping_table_;

        smeth->code_item_offset_ = dmeth->code_item_offset_;

        smeth->entry_point_from_compiled_code_ =

                dmeth->entry_point_from_compiled_code_;

        smeth->entry_point_from_interpreter_ = dmeth->entry_point_from_interpreter_;

        smeth->native_method_ = dmeth->native_method_;//把补丁方法替换掉

        smeth->method_index_ = dmeth->method_index_;

        smeth->method_dex_index_ = dmeth->method_dex_index_;

        LOGD("replace_5_0: %d , %d", smeth->entry_point_from_compiled_code_,

                dmeth->entry_point_from_compiled_code_);

    }

    其实这个替换过程可以看做三步完成

    打开链接库得到操作句柄,获取native层的内部函数,得到ClassObject对象

    修改访问权限的属性为public

    得到新旧方法的指针,新方法指向目标方法,实现方法的替换。

    如果我们想知道补丁包中到底替换了哪些方法,可以直接方便易patch文件,然后看到的所有含有@ReplaceMethod注解的方法基本上就都是需要替换的方法了。

    最近我在学习C++,顿时感觉到还是这种可以控制底层的语言是多么强大,不过Java可以调用C++,也就没什么可吐槽的了!

    好的,现在AndFix我们分析了一遍它的实现过程和原理,其优点是不需要重启即可应用补丁,遗憾的是它还是有不少缺陷的,这直接导致阿里再次抛弃了它,缺陷如下:

    并不能支持所有的方法修复

    不支持YunOS

    无法添加新类和新的字段

    需要使用加固前的apk制作补丁,但是补丁文件很容易被反编译,也就是修改过的类源码容易泄露。

    使用加固平台可能会使热补丁功能失效(看到有人在360加固提了这个问题,自己还未验证)。

    链接:https://www.jianshu.com/p/4ecd611383e6
     

    展开全文
  • 科大讯飞和悦跑圈均表示,下架与“热更新”相关。然而,这并不是苹果应用商店第一次因为“热更新”而作出如此大规模的动作。不过,此次多款知名应用遭遇突然下架,也体现出苹果对其封闭生态系统的强力维护。数据显示...
  • 热更新总结--冷启动热更新

    千次阅读 2018-12-19 17:11:44
    本文章主要根据阿里出的《深入探索Android修复技术原理》后的个人总结   一、为什么直接补丁类直接导入到补丁包中,运行类加载时会产生异常并退出? 首先,因为dex加载到本地内存时,如果不存在odex文件,那么...
  • 热更新流程

    千次阅读 2019-05-22 16:52:29
    热更新流程 热更新一般需要包含以下东西: 1.URL1:游戏版本配置文件地址 2.URL2:所有资源的MD5配置文件地址 graph TB  启动游戏-->|准备|热更新;  热更新-->|拉取游戏版本...
  • Android热更新技术的研究与实现(一)

    万次阅读 多人点赞 2017-10-18 09:28:49
    第一部分重点是将当下热门的热更新方案实现之后再研究,第二部分则是自己动手实现一个自己的热更新框架。Android热更新技术的研究与实现之研究篇———概念讲解——–热更新 相关概念这个词出现的时间已经很久了,...
  • idea热更新设置

    万次阅读 2018-06-11 09:31:16
    1,设置中勾选此项2,ctrl+shift+alt+/ 选择registy 勾选第一项3,maven中依赖spring-boot-devtools转载自 https://blog.csdn.net/com3294/article/details/79104513
  • Lua热更新原理及示例

    万次阅读 2016-09-08 17:30:34
    网上有不少Lua热更新的文章,都只说了理论,被没有给出实际可操作的代码,下面是我写的几个例子。热更新原理Lua的 require(modelname) 把一个lua文件加载存放到package.loaded[modelname]。 当我们加载一个模块的...
  • 热更新”这个词,在unity3D的应用下,是有些语义错误的,但是作为大家都熟知的一项技术,我们姑且这么叫它,相信很长时间内,大家依然还会这么叫,甚至有人叫它“暖更新”。 一、什么是热更新热更新,是对hot ...
  • idea使用spring boot 热更新、热加载

    万次阅读 2017-11-09 10:35:14
    修改代码后无需重新make、build、run项目,直接看到结果 两种方式 第一种:适合Idea自动装载的Run或Debug 1、Settings->Build project automatically 2、Ctrl+Shift+A ->搜索registry,找到Registry...,注意是后面...
  • vue-cli3 热更新配置

    万次阅读 2019-02-26 17:49:16
    问题: 在使用vue-cli3搭建项目...只需要在vue.config.js文件中配置一下就可以实现热更新了,如下: chainWebpack: config => { // 修复HMR config.resolve.symlinks(true); }, 是的就是这么简单。...
  • vuecli3.0热更新失效问题解决

    万次阅读 2018-07-05 17:01:51
    webpack的热更新可以说极大地提高了前端的开发效率,以下就是本人遇到的针对vuecli热更新失效的解决方法:1、检查控制台,编译的时候是否有警告,警告很可能导致热更新的失效2、vueCli3.0及以上的版本,注意不要用...
  • Unity官方公布热更新方案性能对比

    万次阅读 2016-03-11 09:48:57
    孙广东 2016.3.11 Unity应用的iOS热更新作者:丁治宇Unity TechnologiesChina Agenda• 什么是热更新• 为何要热更新• 如何在iOS 上对Unity 应用进行热更新• 支持Unity iOS 热更新的各种Lua 插件的对比什么...
  • IDEA 热更新

    千次阅读 2017-12-28 15:15:57
    在进行开发时,热更新功能是很方便的,能大大的提高开发效率,而IDEA本身就带了这个功能,而无需安装插件来支持。 开启步骤: 1、在run configuration中进行如下设置: 2、在完成文件修改后点击run或者debug会...
  • Flutter 热更新无效问题

    万次阅读 2018-08-06 14:38:43
    Error connecting to the service protocol: Exception: Attempted to connect to Dart observatory 5 times, and all attempts failed. Giving up. The URL was ws://127.0.0.1:8111/ws 如果运行后输出 Log 是这样...
  • 热更新最大的难点是状态同步,所以无状态的热更新是最容易实现的,本篇幅的热更新方案的约束条件就是不涉及状态同步的模块。 挑选出业务内善变的模块做热更新方案,从而提高开发效率,本篇幅是总结一下我工作当中的...
  • springboot的部署网上有太多的教案,在此呢,也只是真实记录一下自己在实际操作过程中的方法步骤。 1、pom.xml配置:这是一定要配置的,忘记吃饭都不能忘记这个 2、File->setting:该√的√上 3、...
  • vue-cli3+webpack热更新失效问题

    千次阅读 2019-06-10 18:49:16
    A项目中遇到问题,热更新失效,百思不得其解,查询搜索vuecli3热更新失效、vue histroy 模式热更新失效,网上看到不少方法,npm重新安装,不要用淘宝镜像cnpm安装;npm安装yarn,再用yarn重新install,yarn serve启动...
1 2 3 4 5 ... 20
收藏数 134,646
精华内容 53,858
关键字:

热更新