一小时极速掌握HybridCLR热更新

2023-10-10 22:30

本文主要是介绍一小时极速掌握HybridCLR热更新,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一小时极速掌握HybridCLR热更新

HybridCLR热更新是一个新的热更新方案,根据HybirdCLR的官方自述,它的性能和开发的便利性相较于传统的热更方式都有了大幅度的提升

本文会为大家讲解HybridCLR的详细情况以及如何基于HybridCLR进行游戏热更新

版权声明

  • 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
  • 更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
  • 点赞、关注、分享可免费获得配套学习资源
  • 点击观看完整视频

HybridCLR有哪些不同之处

请添加图片描述

  • 语言/虚拟机
    • ToLua和Xlua都是基于Lua的热更新方案,端游时代里经常会使用Lua作为脚本热更新的方案,在之后的手游时代里出现了ILRuntime热更新方案,现在又出现了HybridCLR热更新方案
    • 传统的热更新方案,不管是ToLua还是Xlua方案,都是基于另外一门编程语言,比如你在学习Unity时会用到的编程语言是C#,如果你要想用Lua进行热更新,那么你还要再学一门Lua编程语言
    • 为了降低学习成本,就出现了ILRuntime热更方案,ILRuntime基于C#编程语言,它的好处就是你不再需要从头学习Lua,可以直接基于C#进行编程
    • 但ILRuntime与Lua有一个共同的缺点,就是都有自己的虚拟机环境,它们会用虚拟机解释Lua或C#的指令,正是由于这个虚拟机的存在,造成了不管是Lua还是C#,它们的语言翻译成机器指令后都需要由专门的虚拟机进行解释,然后再与Unity的原生执行环境进行双向交互,这样就造成了交互开销,所以在ILRuntime之后又提出了一个新的热更新方案,也就是HybridCLR
    • HybridCLR没有虚拟机,这是它与传统热更新方案最本质的区别,也正是由于它没有虚拟机,所以它没有虚拟机之间进行双向数据交互的CPU和内存开销
  • 学习曲线
    • 由于Xlua和ToLua都是基于Lua语言的,而Lua语言是一种弱类型的语言,所以没办法得到精确的代码提示,网上虽然有一些能够提供代码提示的工具,但这些工具都是基于原生语言的解析,本身的代码提示也不是特别精确,并且你除了要学习Lua语言外,还需要学习基于Lua的面向对象、Lua的编程框架
    • 这些知识其实都是为了使用Lua而增加的额外负担,所以更精简的方案就是使用ILRuntime,再精简一点就是使用HybridCLR
    • ILRuntime还有一个问题就是虽然语言上统一了,但由于ILRuntime本身也有自己的虚拟机,所以ILRuntime的数据类型并不统一,需要对一些特殊情况专门进行适配,比如一些泛型、代理、继承等等
    • HybridCLR不需要做任何适配,也不需要额外学习编程语言,它的底层机制是修改了Unity的一部分源码,所以是无感透明的
  • 性能方面
    • ToLua和XLua的性能是比较低的,ILRuntime的性能相对要好一些,HybridCLR由于没有虚拟机,所以性能也会高一点
  • 成熟度
    • 端游时代里用ToLua和Xlua的比较多,但如果你到现在还在学习ToLua或者Xlua热更新,那么你的项目可能是一些比较传统的项目,就比如国内的一些MMORPG游戏,这些游戏大多都是从端游时代移植过来的,所以它们的主流热更新方案就是Lua
    • ILRuntime是有Unity官方支持的
    • 虽然HybridCLR是一个新的热更方案,但据官方描述,它并没有太大的问题,现在也正在接入一些商业项目

什么是游戏热更新

请添加图片描述

  • 游戏热更新的更新流程是什么样的?
    • 从软件商店下载游戏App并安装到手机上,启动手机上的游戏时会连接游戏的服务器,检查服务器上面有没有更新文件列表,如果有,就会把更新内容下载下来,没有就不更新,这是游戏玩家的热更新流程
    • 游戏开发者的热更流程是
      • 1,制作游戏更新内容,可能是新的资料包、新玩法、新道具、数值调整
      • 2,更新内容完成以后,开发者会把它们打包成Unity里的AB包并上传到资源更新服务器上,上传更新内容的同时还要上传更新目录索引,更新目录索引中储存的是文件、模型、贴图资源的索引和资源内容
      • 3,这时玩家打开游戏就会检查更新,并下载更新资源

Unity的热更新以及Unity如何编译和执行游戏脚本的详细内容可以参考我们的《Unity热更新那些事》,这里只是为HybridCLR做铺垫

游戏热更新的种类

请添加图片描述

  • 游戏能做的热更新有两种,一种是游戏资源热更新,另一种是游戏代码热更新,有同学可能会说游戏热更新一般都是指资源热更新,代码怎么能热更新呢?
    • 代码在电脑里是以文件的形式存储的,所以代码也是一种资源,既然是资源的话,那就可以热更,但Unity本身是不支持代码热更的,因为万一程序代码热更新更新出的代码是被病毒感染的,那么就会对用户的手机造成很多不良影响
    • 所以Unity不能直接支持热更,包括IOS这样的游戏市场也不允许你随意更新游戏代码
  • 我们平时在网上看到的各种各样的热更新内容,其实无外乎就是为了绕过苹果或安卓渠道的审核,以一种安全的方式把游戏的更新以增量的形式发布到客户手机上,而不需要重新从游戏市场上全量下载整个游戏更新包,这就是代码热更新的意义所在

HybridCLR的打包模式

在这里插入图片描述

  • 如果想用使用HybridCLR热更,那么在选择打包模式时,脚本后台就不能用默认的Mono方式来进行打包,必须切换到IL2CPP方式,这是HybridCLR热更的特点
  • Mono的脚本执行是C#代码,而IL2CPP是把它转成C++代码,C++代码本身的性能就比C#代码要高
    在这里插入图片描述
  • 使用IL2CPP方式进行打包后,如果打的是安卓包,那么你就会看到一个libil2cpp.so文件,这个文件里存储的是最终编译出的代码

IL2CPP的编译原理

请添加图片描述

  • 程序员写的C#代码会被C#编译器编译成Assembly-CSharp.dll,这个dll是以微软中间汇编的形式存储的,如果你使用的是Mono虚拟机,那么脚本编译流程到这里就结束了,也就是执行完下图被红框框选的内容后便结束了
    在这里插入图片描述
  • 但如果你用的是IL2CPP方式就不一样了,之前Mono脚本编译的内容可以分为用户代码和其他库代码,使用IL2CPP方式会把这些代码转换成C++代码的源码,然后利用Native C++ Compiler把C++源码编译成机器指令,这个机器指令会被IL2CPP的虚拟机加载执行,具体流程如下图
    在这里插入图片描述
  • 如果能看懂上面IL2CPP的编译流程,那么我再稍微提示一下,大家或许就能明白HybridCLR是什么东西了
    • 由于C++代码没有C#那样的完整类型信息,所以为了获取完整类型信息,HybridCLR需要对IL2CPP进行改造
    • HybridCLR会下载一个名为il2cpp_plus的项目,这个项目会把泛型方法、泛型数据类型、以及其他的一些支持添加到IL2CPP里面
  • HybridCLR的热更新部分是怎样去做热更新的呢?
    • 程序员写的这些程序最终会形成IL2CPP这样的程序集,这个程序集里只包括一些没有热更新的代码,HybridCLR干的事情就是在IL2CPP的编译流程中(也就是下图标注的位置)引入了HybridCLR
      在这里插入图片描述
    • HybridCLR在这个位置就相当于虚拟机,但不等于虚拟机,它的作用是编译热更代码,这就是HybridCLR要做的事情
    • 所以HybridCLR实际上有两个库,一个是HybridCLR库,另外一个库就是刚才讲的il2cpp_plus库,HybridCLR需要这两个库才能完成完整的HybridCLR热更新功能

关于Unity热更新,大家可以参考我们的《Unity热更新那些事》

下载HybridCLR示例项目

  • 想要快速上手掌握HybridCLR,最好的方式就是从官方的快速上手文档开始(文档的网址可以在文末扫描爱丽丝老师的二维码领取)使用官方准备好的示例项目
  • 接下来我们先通过示例项目掌握HybridCLR热更新的总体原理和流程,当你真正的理解它以后,要用到自己的项目里面时就可以把示例项目里的一些多余的东西去掉,再从头搭建
    请添加图片描述
  • 在官方文档中点击快速开始栏中的hybridclr_trial示例项目链接**,**前往GitHub复制示例项目源码
    在这里插入图片描述
  • 想要在GitHub里复制源码,必须要安装两个软件:Git和TortoiseGit
    • Git是一个命令行工具,TortoiseGit就相当于Git的外壳,把这些东西装了以后你才能够从GitHub网站上下载并克隆源码

认识HybridCLR

init_local_il2cpp_data.bat

  • 在下载的HybridCLR项目源码中点击HybridCLRData文件夹
    在这里插入图片描述
  • 右键HybridCLRData文件夹中的init_local_il2cpp_data.bat文件,选择编辑
    在这里插入图片描述
  • init_local_il2cpp_data.bat是一个批处理文件,什么是批处理文件呢?
    • 它其实就是一段代码,这段代码就像我们写的程序一样,可以一行一行执行,也有if else,也有for循环
  • 在打开的文本编辑器中设置Unity版本为2021.3.1(这个示例项目能够支持的Unity版本只有两个:2020.3.33和2021.3.1,本文使用的Unity版本为2021.3.1)
    在这里插入图片描述
  • 指定IL2CPP的官方原始代码所在目录
    在这里插入图片描述
    • 因为HybridCLR要基于IL2CPP的原始源码做一些修改,而且定位到这个目录以后它也能找到跟这个目录相关的一些目录,下图就是我电脑上的IL2CPP的官方原始代码所在目录
      在这里插入图片描述
    • HybridCLR修改的源码都放在这个目录中的libil2cpp文件夹中,这个文件夹里存放的全部都是一些以.cpp后缀结尾的文件,熟悉C++的同学就知道这就是C++的源码,IL2CPP是开源的,源码都能看到,所以HybridCLR的作者可以基于源码去做修改
  • 定位到IL2CPP的官方原始代码所在目录后还可以顺着找到MonoBleedingEdge文件夹
    在这里插入图片描述
  • 这个文件里存放的是一些命令行小程序,这些小程序并不能直接执行,必须载入到Mono里执行,大家在学习HybridCLR时可以把这个批处理程序好好看一看,本文只是对几个比较重要的点进行讲解,其中的一些细节还需要读者自己学习
  • 配置完init_local_il2cpp_data.bat文件后双击执行,init_local_il2cpp_data.bat文件在执行时会用Git命令下载HybridCLR的源码仓库和IL2CPP的修改版本
  • init_local_il2cpp_data.bat里还有一个LocalIl2cpp的目录,它的作用是什么呢?
    在这里插入图片描述
  • 替换Unity里的IL2CPP源码最原始的方法就是将下载的ilbil2cpp文件夹复制到Unity的IL2CPP目录中覆盖掉原始的libil2cpp文件夹
  • 但是作者利用了Unity里的环境变量特性,使Unity在启动以后会把刚才下载的修改过的IL2CPP目录设置成IL2CPP源码所在的目录,这样Unity就会从这个目录里读取IL2CPP的源码来进行打包
    • Unity在打包时就是通过读IL2CPP库里的源码,利用这些源码来进行C#到C++的转换,转换完了以后形成机器指令,放到Unity里进行执行

init_local_il2cpp_data.bat文件的详细介绍可以参考文末完整视频

组织结构介绍

  • 如果你在前面没有修改并执行bat文件,那么你使用Untiy打开示例项目时控制台上就会有一些报错,如果你把前置工作做好了,那么你的项目就是完整无错的
  • 下面我们来解释一下HybridCLR示例项目工程的组织结构是什么样的?
    在这里插入图片描述
  • HotFix和HotFix2文件夹是示例项目的热更工程,它们的作用我们会在后面介绍
  • Editor文件夹的作用是用来为打包做一些配置
  • 如果你想要体验一下HybridCLR的热更新,就需要打开Scenes文件夹
  • StreamingAssets文件夹则是用于存放打包以后的资源

快速掌握HybridCLR热更新

  • 点击打开并运行Senens文件夹中的Main场景
    请添加图片描述
  • 直接运行Main场景会出现报错(如上图),第一段话的意思是场景已经被加载了,第二段的意思则是要在程序启动时加载common包
    • common包在程序结构中相当于是热更程序集,类似于刚才介绍的HotFix跟HotFix2
  • 第三段话的意思是下载出错了,这个问题跟网络没有什么关系,只是没有找到common包
    • common包里存放的是HotFix和HotFix2这两个程序集,前面也讲过程序也是一种资源,并不是只有模型贴图这些才算是资源
  • 所以这个报错是因为没有资源包所引发的,要解决这个问题就需要进行打包
  • 点击HybridCLR菜单栏中的Build,选择Win64进行打包(第一次打出的资源包就是我们的首包)
    在这里插入图片描述
    在这里插入图片描述
  • 打完包以后,点击资源包里HybridCLRTrial.exe运行就可以看到如下画面
    在这里插入图片描述
  • 来看一看它的输出内容
    • 第一段语句是提示场景被加载,第二段语句则是提示加载了common包
    • 下图中被框选的三段语句是HybridCLR的一些底层实现,它的作用是补充一些元数据,也可以理解为补充一些数据类型的信息

在这里插入图片描述

  • 下图中被黄色标注的语句是比较重要的:hello, HybridCLR.prefab
    • 它的内部实现的机制是创建了一个预制体,然后通过预制体加载一个脚本

在这里插入图片描述

  • 下面我们就要对这个预制体加载的脚本做热更新

LoadDLL源码解析

  • 在做热更新之前我先带大家看一看HybridCLR的源码是什么样的
    • 主场景在运行时会执行LoadDll对象的LoadDll脚本
      在这里插入图片描述
  • LoadDll脚本在运行时会启动DownLoadDlls协程下载DLL,下载完了以后会执行onDownLoadDll回调函数,也就是执行StartGame
    在这里插入图片描述
    在这里插入图片描述
  • StartGame会调用LoadGameDll方法加载DLL程序集
    在这里插入图片描述
  • LoadGameDll方法在执行时会获取common资源包,然后从资源包里加载两个程序集:HotFix.dll和HotFix2.dll,这两个dll就是之前介绍的HotFix、HotFix2文件夹
  • 如果你在项目中定义了HotFix、HotFix2这两个脚本目录,并在这两个脚本目录中创建了ASMDEF文件,那么这两个文件夹就会生成两个程序集,经过HybridCLR打包,这两个程序集就会被打包到common包里
  • 这两个程序集里有哪些内容呢?
    • 其实就是这两个文件夹里的脚本,示例项目中HotFix、HotFix2只有两个脚本:PrintHello脚本和App脚本
    • App脚本因为跟本文内容没什么关系,所以就不在这里专门介绍了,PrintHello的作用是在Main场景中打印hello,HybridCLR.prefab
      在这里插入图片描述
  • common包里除了这两个程序集还有一个HotUpdatePrefab游戏对象,它存放在示例项目的Prefabs文件夹中,这个预制体也是通过脚本打包的,在项目运行时HotUpdatePrefab会被LoadGameDll加载并进行实例化,形成testPrefab
    在这里插入图片描述

热更测试

在这里插入图片描述

  • HotUpdatePrefab上挂载了PrintHello脚本,示例项目在运行时会实例化HotUpdatePrefab,HotUpdatePrefab上的PrintHello脚本就会在Main场景中打印hello,HybridCLR.prefab(如下图)
    在这里插入图片描述
  • 既然要测试热更,那么我就在PrintHello脚本里加点东西(如下图),因为我们的机构名叫优梦创客,所以在hello, HybridCLR字符串中添加了优梦创客作为标识
    在这里插入图片描述
  • 保存PrintHello脚本后回到Unity里面,打一个Win64位的资源包
    在这里插入图片描述
  • 打开StreamingAssets目录找到刚才打包的资源并复制build_init、common文件
    在这里插入图片描述
  • 将这些资源复制到刚才之前第一次打的common包中,替换原本的build_init、common文件
    在这里插入图片描述
  • 替换完成后点击首包里的HybridCLRTrial.exe运行,就可以看到画面下方的hello,Hybrid后面多了优梦创客几个字,这代表热更成功了
    在这里插入图片描述
  • HybridCLR快速上手就讲到这里,下面为大家整理一下思路和细节
  • HybridCLR打包流程
    • HybridCLR所有的打包功能都是集中在HybridCLR菜单下
      在这里插入图片描述
    • 正常的打包你可能还需要理解它的打包流程,但HybridCLR提供了傻瓜打包的方式,只要点击Build里的Win64就可以直接把首包打好,中间的过程是:打包形成裁剪信息,然后利用裁剪信息打包热更dll,这样就可以获得热更所需的所有信息
  • 首包打完以后,再启动游戏时,主场景会加载热更代码。加载完成后就会进入到热更代码的执行流程里,所以在之前的LoadDll脚本里会有这么几个步骤
    • 第一步,启动下载
    • 第二步,下载完了以后启动游戏,启动游戏分为两个步骤:加载dll、运行主程序
      • LoadDll脚本会通过LoadGameDll方法加载程序集
      • 加载完成后LoadDll脚本就会执行主程序,主程需会通过反射的方式加载App数据类型,然后调用App类的Main方法(如下图)
        在这里插入图片描述
      • App的Main方法在调用时会测试泛型的一些功能是否正常,然后打印一段文字,创建一个HotFix2对象,并为它添加一个CreateByCode组件
        在这里插入图片描述
      • CreateByCode组件也是可以进行热更的,它的内容如下,它和前面的PrintHello方法一样,只是在运行画面中打印:“这个脚本是通过代码AddComponent直接创建的”
        在这里插入图片描述
      • 你可以采用之前热更PrintHello脚本的方法来对CreateByCode组件进行热更,首先为CreateByCode组件要打印的字段后添加优梦创客标志
        在这里插入图片描述
      • 然后进行打包,复制打包后资源里的build_init、common文件,替换第一次打包之前打好的common包中的build_init、common文件,然后执行首包里的HybridCLRTrial.exe,效果下图所示
        在这里插入图片描述

性能评测

在这里插入图片描述

  • 上图中的柱子并不是代表柱子越长性能越好,而是柱子越短运行Unity花的时间越少
    • 蓝色的柱子代表Xlua、ToLua热更方案,可以看出Xlua和ToLua的性能最差的,在特定的情况下可能会好一点
    • 绿色的柱子代表IlRuntim热更方案,ILRuntime的性能测试看上去或许会比大家想象中的要好一些,这是因为大家过去对ILRuntime的性能测试都有一个误解,就是你根本没有打开ILRuntime性能最高的模式,这就像两个人比高矮一样,一个站在凳子上面,一个站在平地上,这是没法比的,所以ILRuntime的性能是比传统的Xlua、ToLua要好的
    • 黄色的柱子代表原生代码,因为原生代码在运行时没有任何额外负担,所以原生代码在比较里是最快的
    • 最后的柱子就是HybridCLR热更新方案,可以看出HybridCLR的性能已经跟传统方式不在一个级别上了,性能与原生编程方式相当,为什么会这样呢?
      • 因为HybridCLR根本没有虚拟机,而且它是把代码编译成C++,C++的性能本来就比C#要高,这就是它热更优势的来源
  • 内存占用
    • Xlua或ToLua每个值类型对象的大小都比ILRuntime要大,HybridCLR是最小的,几乎跟原生的C#或C++代码等同
    • Xlua和ToLua的引用类型也是最差的,ILRuntime局中,HybridCLR是最好的

进阶与总结

在这里插入图片描述

注意事项

在这里插入图片描述

  • 编译时的脚本后台必须选IL2CPP,具体原因可以看我们的《Unity热更新那些事》,只有这种方式才能够使用HybridCLR的热更功能
  • Api Compatibility Level 要选择 .NET 4.x
  • C++ Compiler Configuration 不要选择Debug,要选择Release版本,因为Release版本的性能要比Debug高不少

写在最后

  • 更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
  • 点赞、关注、分享可免费获得配套学习资源
  • 点击观看完整视频

这篇关于一小时极速掌握HybridCLR热更新的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/183519

相关文章

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/

GIS图形库更新2024.8.4-9.9

更多精彩内容请访问 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加微信:digital_twin123 Cesium 本期发布了1.121 版本。重大新闻,Cesium被Bentley收购。 ✨ 功能和改进 默认启用 MSAA,采样 4 次。若要关闭 MSAA,则可以设置scene.msaaSamples = 1。但是通过比较,发现并没有多大改善。

AI Toolkit + H100 GPU,一小时内微调最新热门文生图模型 FLUX

上个月,FLUX 席卷了互联网,这并非没有原因。他们声称优于 DALLE 3、Ideogram 和 Stable Diffusion 3 等模型,而这一点已被证明是有依据的。随着越来越多的流行图像生成工具(如 Stable Diffusion Web UI Forge 和 ComyUI)开始支持这些模型,FLUX 在 Stable Diffusion 领域的扩展将会持续下去。 自 FLU

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.

记录每次更新到仓库 —— Git 学习笔记 10

记录每次更新到仓库 文章目录 文件的状态三个区域检查当前文件状态跟踪新文件取消跟踪(un-tracking)文件重新跟踪(re-tracking)文件暂存已修改文件忽略某些文件查看已暂存和未暂存的修改提交更新跳过暂存区删除文件移动文件参考资料 咱们接着很多天以前的 取得Git仓库 这篇文章继续说。 文件的状态 不管是通过哪种方法,现在我们已经有了一个仓库,并从这个仓

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法   消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法 [转载]原地址:http://blog.csdn.net/x605940745/article/details/17911115 消除SDK更新时的“