关于performance:设置ApplicationtargetFrameRate没有起作用的原因

1)设置Application.targetFrameRate没有起作用的起因2)Unity如何监听程序退出,包含被后盾kill3)升高Mesh LOD是否会升高片元函数的执行次数4)TMP SubMeshUI呈现的起因 这是第304篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) PerformanceQ:在代码里设置了Application.targetFrameRate并没有起作用,请问是什么起因? A1:题主能够检查一下引擎设置中VSync设置是否开启。如下图,能够调成Don't Sync再试试。 经测试,VSync敞开时,Application.targetFrameRate的锁帧成果失常失效;VSync开启时,理论帧率总是会达到设施反对的最大帧率,从而使Application.targetFrameRate的锁帧生效。 举例而言,在小米10上进行测试,开启VSync后,只管在代码中设置Application.targetFrameRate=60,但理论帧率会达到90,从而使得我的项目的GPU压力高于预期。 感激Faust@UWA问答社区提供了答复 A2:挪动平台会跟手机无关,不会超过最大的刷新率。看下官网文档:https://docs.unity.cn/cn/2021...感激zerolj@UWA问答社区提供了答复 AndroidQ:如果实现了MonoBehaviour.OnApplicationQuit这个办法,在PC或者编辑器下用能够监听到,然而我发现,公布Android时这个函数不会被监听到。 应用Application.Quit办法退出能够监听到,然而如果间接被后盾杀死,那么就无奈监听到。 想请问有没有别的办法,能在各种状况下(强制退出或后盾kill等)监听到程序退出? A:将我的项目导出成Android我的项目,此时导出的我的项目目录unityLibrary\src\main\java\com\unity3d\player下有一份UnityPlayerActivity.Java文件,外面的代码是在管制UnityPlayer的生命周期。 Android生命周期: 依据以上周期,游戏失常退出会调用Quit,零碎杀过程则不会触发Quit。 所以可能倡议思考一些别的方法。比方onSaveInstanceState()和onRestoreInstanceState()这两个事件,以及通过编写Servise辅助检测Uniy我的项目的活动状态。 如果你想编写安卓间接交互Unity的代码,也能够像这样写:mUnityPlayer.UnitySendMessage(“receiveObj”, “UnityMethod”, “This is args.”); 感激Isle@UWA问答社区提供了答复 RenderingQ:镜头固定,如果模型应用Mesh LOD请问除了升高顶点函数执行次数,还会升高片元函数次数? 或者影响片元函数的执行次数跟哪些因素无关? A1:那就只能降屏幕分辨率或者进行深度剔除,AlphaTest等提前剔除的伎俩。感激欧月松@UWA问答社区提供了答复 A2:补充楼上,次要是看Primitive光栅化之后的占屏幕像素数量(这个受到渲染分辨率的影响)。当然还要思考暗藏面打消,Early-Z等防止Fragment计算的因素。感激Xuan@UWA问答社区提供了答复 TextMeshProQ:当初在应用Unity 2019.4.34f1版本和TextMeshPro 2.1.6版本开发性能,在我的项目中有两个字体,Font_Main和Font_Main_Fallback。 如果在Font_Main设置了Fallback Font Assets,创立的TextMeshPro文本组件下会有一个子节点TMP SubMeshUI [Font_Test_1 Material + KaiTiJianCu Atlas],而且还无奈删除,删除后会再次主动呈现;如果不设置Fallback Font Assets,那么就能够把子节点TMP SubMeshUI [Font_Test_1 Material + KaiTiJianCu Atlas]删除,请问这个有什么作用?会对性能有影响吗? A:如果主字体中没有对应的Unicode的文字时,就会应用Fallback的字体显示。感激萧小俊@UWA问答社区提供了答复 封面图来源于网络 明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。 官网:www.uwa4d.com官网技术博客:blog.uwa4d.com官网问答社区:answer.uwa4d.comUWA学堂:edu.uwa4d.com官网技术QQ群:793972859(原群已满员)

July 12, 2022 · 1 min · jiezi

关于performance:如何定位游戏发热问题

1)如何定位游戏发热问题2)Unity获取指定脚本的援用对象3)如何晓得打包时的一个Shader有多少变体4)如何优化Font.CacheFontForText频繁造成的耗时峰值 这是第300篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) PerformanceQ:目前我的项目的发热问题很头疼,2D游戏,基于TileMap、SpriteRenderer和UGUI的渲染,敞开了垂直同步,TargetFrameRate设置为60。Android和iOS上发热都很重大,而且在比拟好的机型(比方iPhone 12这种),发热景象甚至更显著。 跟大部分状况不同,发热并没有怎么影响帧率,在大部分机型上,帧率都不是问题,连iPhone 8机型,都能60帧跑满。 在Unity Profile和Xcode都进行过性能剖析,CPU最显著的热点函数就是Spine的骨骼动画更新计算。然而到一个没有骨骼动画的场景,发热景象稍好点,但还是比预期的要烫不少(场景中除了高空和一些动态贴图,根本就没有多少货色)。 另一个比拟广泛的发热点是网络测试,在某场景敞开网络后,发热仍然重大。甚至启动游戏,停留在登录界面一会儿,发热景象都比别的游戏更显著。 猜想是否因为每帧的顶点数量过多造成的,在游戏中Unity的Status面板,顶点数量Verts达到了40KB,三角形Tris也有简直20KB。看起来很多,但我不太分明以后支流游戏这个数值的级别大略是多少。而且,顶点和三角形的数量很难解释登录界面仍然容易发热,毕竟登录界面这些数值不可能很高。 本人也做了很多测试了,切实搞不清楚问题到底在哪儿,Unity是还有什么特地须要优化的,针对发热的点吗? A1:能够用Xcode抓帧看看带宽,Load Store Action是否正当。感激littlesome@UWA问答社区提供了答复 A2:以下是我的倡议: iPhone 8上跑满60帧,证实CPU、GPU都没有达到瓶颈,耗费在较为正当的范畴。举荐在Unity Profiler看一下CPU端的耗费,以及查看一下DrawCall数量。举荐应用FrameDebugger,看一下是否有冗余的物体或者后处理在渲染,尤其是你说的启动界面有发热。如果低级谬误都排查过了,那么倡议看一下是否用了原生的插件。渲染的分辨率是否调整过了,RenderScale的值和FrameDebugger能够查出来分辨率。感激张振东@UWA问答社区提供了答复 A3:对于发热的问题,通常要从几个角度排查:CPU压力(耗时)、GPU压力(耗时和带宽,能够思考升高分辨率看看发热问题是否会有改善)和IO等几个角度。从题主的问题上看,耗时应该是没问题,都能跑满帧(当然60帧自身就是对发热影响比拟大的一点,能够看看限度30帧会不会发热有降落),所以要看看一些隐形的货色是否有问题。比方带宽,能够用Snapdragon在高通手机上跑一跑。如果带宽较高,看看纹理的一些设置是否正当,比方是否压缩、是否开启Mipmap,这两项通常都是须要设置成开启的。还能够查看是否有不必要的BlitCopy操作,在URP我的项目中比拟容易呈现Copy Color和Copy Depth节约。对于IO,须要看看是否存在子线程外面有频繁IO的景象。感激Xuan@UWA问答社区提供了回 ScriptQ:Unity获取指定脚本的援用对象:一个GameObject上挂载了一个Script,这个Script援用了很多资源。如何只取得这个Script援用的资源呢?AssetDatabase是会获取所有的援用,然而并没有做辨别。 A1:有Guid能够应用以下代码加载:var assetPath = AssetDatabase.GUIDToAssetPath(guid);var texture2D = AssetDatabase.LoadAssetAtPath(assetPath);感激萧小俊@UWA问答社区提供了答复 A2:通过SerializedObject拿到了所有的ObjectReference,而后AssetDatabase.GetAssetPath获取对应门路: var assetObj = AssetDatabase.LoadAssetAtPath<Object>(path);if (assetObj != null){ GameObject gameObj = assetObj as GameObject; if (gameObj != null) { MeshRendererTextureStreamingData streamingOBJ = gameObj.GetComponent<“taget component”>(); if (streamingOBJ != null) { SerializedObject so = new SerializedObject(streamingOBJ); SerializedProperty it = so.GetIterator(); bool first = true; while (it.Next(true)) { if (it.propertyType == SerializedPropertyType.ObjectReference && it.objectReferenceValue!=null) { // Debug.LogError(it.objectReferenceValue); var depPath = AssetDatabase.GetAssetPath(it.objectReferenceValue); streamingAssets.Add(depPath); } } } }}感激题主null@UWA问答社区提供了答复 ...

June 8, 2022 · 1 min · jiezi

关于performance:socket编程项目性能优化之perf-trace实践

在一个网络编程性能优化我的项目中,利用perf trace进行性能剖析。背景:一个过程负责解决socket音讯,在须要解决音讯数量达到32k条100+字节的音讯量时,耗时大略须要25分钟指标:定位耗时的热区环境:linux 假如1:用户态耗时多,过程耗时多是耗费在算法计算上?该过程只进行简略的音讯解决,不波及过多数据结构和算法,排除该可能性。 假如2:零碎态耗时多,过程耗时多是耗费在零碎调用上?因为音讯量大,进行了32k*n 数量级的零碎调用,假如有可能成立。 在这里,抉择应用linux的perf工具进行统计分析:perf trace -p $PID -s 由上图可见,在解决一条音讯的过程中,大抵流程波及到与socket相干的 poll->recvfrom->sendto,同时还有与文件IO相干的零碎调用。解决32k条音讯,波及到的零碎调用数量在数量级上合乎预期。 指标统计分析: fsync(), 该零碎调用用时最多,耗费了大略15分钟。该零碎调用用于同步写入磁盘,调用后会阻塞,直至期待内核缓冲区数据写入磁盘后,内核才会返回。定位到该零碎调用位于过程的日志模块。针对该零碎调用改善:业务数据日志,升高其日志等级,在运行过程时通过日志等级开发将对应的等级敞开,缩小日志输入;同时在日志模块中删除fsync()调用,该类日志不须要实时同步至磁盘。 从新执行雷同的测试,过程总用时大概50s。从本次检测到的零碎调用指标来看,解决了fsync()带来了极大的晋升空间。 在这个实际中,能够看到perf trace的一个用途:统计肯定工夫内的零碎调用的次数以及其耗时散布。 思考1:perf trace统计的零碎调用,各列中的工夫是零碎工夫还是时钟工夫?集体认为是时钟工夫,因为只管是零碎调用,内核态也会处于一种阻塞状态,该状态不耗费CPU资源。例如过程调用fsync(),陷入内核态,DMA把零碎缓冲区的数据同步至磁盘,此过程过程睡眠,没有占用CPU资源,同步至磁盘实现后,DMA会中断,CPU进行响应解决,此时fsync()调用完结,返回用户态。

June 22, 2021 · 1 min · jiezi

关于performance:第37问自旋锁-旋着旋着人就糊涂了

问题谋求 MySQL 的性能时,总据说要调整自旋锁的参数: innodb_spin_wait_delay 和 innodb_sync_spin_loops,是真的么? 试验首先咱们要晓得自旋锁的长处:自旋锁要上锁时,如果须要期待其余线程开释锁,那么: 在期待锁的过程中会先线程会先自旋一段时间 自旋阶段,线程不会放弃 CPU自旋过后: 如果能够获取锁了,那么响应会比拟快(自旋没产生上下文切换)如果还须要期待锁,再用更高老本的形式进行锁期待innodb_spin_wait_delay 参数决定了自旋阶段的长度。当初咱们试着调整 innodb_spin_wait_delay 参数,来测试一下: 先宽油起一个数据库,此处疏忽步骤 建个表,放点数据: 配置好 performance_schema: 检查一下相干参数: 清理 performance_schema 的统计值: 来点压力: 查问一下锁期待老本最高的锁: 能够看到锁期待老本最高的是 lock_mutex,是爱护 MySQL 锁零碎的锁 上面咱们来调整一下 innodb_spin_wait_delay,让自旋的工夫变长: 重做一次压力(记得先清理统计数据),查看统计数据: 能够看到 lock_mutex 的均匀等待时间从 751267 减少到了 1399041。咱们让自旋阶段减少了 10 倍,锁期待的工夫也会随之增大。 目前的试验看上去自旋阶段越短越好,那么自旋阶段是不是就没有意义了?当然不会。 大家能够将 innodb_spin_wait_delay 设置为 1,再进行测试,随着自旋阶段的缩小,锁期待的工夫也会随之增大(大部分锁都应用了高老本的形式来进行期待)。 那么如何抉择自旋的参数呢?咱们倡议“不出问题不瞎调”。 在之前的统计数据中,工夫的单位是:cycle,依据以下换算表,锁的均匀工夫是 1399041 cycle,大略也就 0.5ms(1399041 / 2385353233 = 0.00058 s),占 SQL 的整体工夫很低,能够不必瞎调。 一个驰名的 CPU 问题自旋阶段,MySQL 会调用 CPU 的 PAUSE 指令,既能占用了 CPU,同时 PAUSE 指令(比起其余占用 CPU 的指令)也比拟节能。 ...

May 21, 2021 · 1 min · jiezi

关于performance:厚积薄发MMORPG手游合理的性能参数

1)MMORPG手游正当的性能参数2)应用ScriptableBuildPipeline打包的疑难3)如何获取到Animation批改材质球色彩后的色彩值4)嵌套预设AssetBundle打包的疑难5)LWRP渲染下,Profiler中函数开销高 这是第219篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) PerformanceQ:想求教一下大家,一款MMORPG游戏上线时的性能参数是如何制订的?有没有可参考的性能参数能够领导一下? A1:制订指标要依据多方维度来综合思考,包含产品定位、团队程度、老本估算和工夫估算等等,很难一概而论,以下是咱们针对新我的项目立项要指定美术指标时一贯的做法,能够参考: 拿对标美术体现的竞品来看他们各项性能指标如何,用RenderDoc或者NSight可能失去比拟精确的数据,也能够抽出他们资源来做下一步操作。制作成果Demo,尝试技术上还原竞品的成果,这时尽量做到体现上和性能上都能还原。有了成果Demo后,根本指标曾经有了,不过还是得本人团队从美术到技术再到策动残缺地把流程跑通,而且可能还要对体现的要求进行进步以晋升产品竞争力,这时候尽可能依据上线工夫推断适合的指标机型,略微超前地指定渲染能力指标。感激邓永健@UWA问答社区提供了答复 A2:能够参考UWA的蓝皮书《Unity手游性能蓝皮书》。感激jessica@UWA问答社区提供了答复 BuildQ:我应用ScriptableBuildPipeline打包AssetBundle比原始Build Pipeline慢,有人应用ScriptableBuildPipeline打包取得更快的打包速度了吗?想弄清楚是我本人某些做法不对,还是自身ScriptableBuildPipeline就是打包更慢。 通过测试失去以下数据:全量打包 增量打包(资源无变动)原生 560s 7sSBP 610s 480s A:测试的数据(AssetBundle的数量大略在3200个左右, 统计耗时:ms) 所以集体感觉SBP + CacheServer, 可能在肯定水平上晋升打包速度。 感激杨宇杰@UWA问答社区提供了答复 RenderingQ:如果用Animation动静更新_Color后,材质球上的_Color色彩获取不到,GetColor获取到的色彩始终是Animation运行前的色彩,求教怎么获取动画更新后材质球的色彩? 复现步骤:1. 新建材质球A,轻易应用Shader带Color属性的,(我这里应用Shader: Legacy Shaders/Diffuse),_Color默认红色;2. 新建物体Cube,将材质球A拖到Cube上;3. 创立Animation,批改Cube上A的_Color色彩,给蓝色或者非红色;4. 创立一个脚本在Update中打印_Color的色彩(mat.GetColor("_Color"));5. 运行后色彩值应用时初始值红色,不是动画Animation批改后的色彩。 A1:实测应用MaterialPropertyBlock是能够获取到的。代码如下: void Start() { render = GetComponent<MeshRenderer>(); mpb = new MaterialPropertyBlock(); } void Update() { render.GetPropertyBlock(mpb); Color color= mpb.GetColor("_Color"); ; Debug.Log(color); } 感激Xuan@UWA问答社区提供了答复 A2:最初发现是美术在Animation里把_Color指定一个固定值,没有勾选刷新,所以始终不对。感激题主王@UWA问答社区提供了答复 AssetBundleQ:嵌套预设AssetBundle打包存在两份资源冗余,目前解决办法有预设b的材质球和Mesh独自打成AssetBundle,然而这样会导致AssetBundle打得太散。求问有什么解决办法? 目前解决办法如下:1. 空工程,预设b带了贴图tex_b和tex_c.应用嵌套预设把b嵌套在预设a外面(Unity版本2018.4.24)。2. 预设a和b都独自打成AssetBundle。3. 打包AssetBundle后,应用AssetStudio后查看预设a的AssetBundle,蕴含了预设b的贴图和Mesh。 A:设计使然:在运行时不存在预制体的概念。预制体在构建过程中是被 “烘焙”好的,因而每个预制体(无论嵌套与否)都是残缺且彼此独立的。https://issuetracker.unity3d.com/issues/nestedprefabs-when-building-assetbundles-with-nested-prefab-assets-are-duplicated-in-parent-prefab-assetbundle 能够将子预制体依赖的资源显式地与子预制体打包在一起(如果能辨别出哪个是子预制体)。 ...

September 23, 2020 · 1 min · jiezi

Lua-Web快速开发指南8-利用httpd提供Websocket服务

Websocket的技术背景WebSocket是一种在单个TCP连接上进行全双工通信的协议, WebSocket通信协议于2011年被IETF定为标准RFC 6455并由RFC7936补充规范. WebSocket使得客户端和服务器之间的数据交换变得更加简单, 使用WebSocket的API只需要完成一次握手就直接可以创建持久性的连接并进行双向数据传输. WebSocket支持的客户端不仅限于浏览器(Web应用), 在现今应用市场内的众多App客户端的长连接推送服务都有一大部分是基于WebSocket协议来实现交互的. Websocket由于使用HTTP协议升级而来, 在协议交互初期需要根据正常HTTP协议交互流程. 因此, Websocket也很容易建立在SSL数据加密技术的基础上进行通信. 协议WebSocket与HTTP协议实现类似但也略有不同. 前面提到: WebSocket协议在进行交互之前需要进行握手, 握手协议的交互就是利用HTTP协议升级而来. 众所周知, HTTP协议是一种无状态的协议. 对于这种建立在请求->回应模式之上的连接, 即使在HTTP/1.1的规范上实现了Keep-alive也避免不了这个问题. 所以, Websocket通过HTTP/1.1协议的101状态码进行协议升级协商, 在服务器支持协议升级的条件下将回应升级请求来完成HTTP->TCP的协议升级. 原理客户端将在经过TCP3次握手之后发送一次HTTP升级连接请求, 请求中不仅包含HTTP交互所需要的头部信息, 同时也会包含Websocket交互所独有的加密信息. 当服务端在接受到客户端的协议升级请求的时候, 各类Web服务实现的实际情况, 对其中的请求版本、加密信息、协议升级详情进行判断. 错误(无效)的信息将会被拒绝. 在两端确认完成交互之后, 双方交互的协议将会从抛弃原有的HTTP协议转而使用Websocket特有协议交互方式. 协议规范可以参考RFC文档. 优势在需要消息推送、连接保持、交互效率等要求下, 两种协议的转变将会带来交互方式的不同. 首先, Websocket协议使用头部压缩技术将头部压缩成2-10字节大小并且包含数据载荷长度, 这显著减少了网络交互的开销并且确保信息数据完整性. 如果假设在一个稳定(可能)的网络环境下将尽可能的减少连接建立开销、身份验证等带来的网络开销, 同时还能拥有比HTTP协议更方便的数据包解析方式. 其次, 由于基于Websocket的协议的在请求->回应上是双向的, 所以不会出现多个请求的阻塞连接的情况. 这也极大程度上减少了正常请求延迟的问题. 最后, Websocket还能给予开发者更多的连接管控能力: 连接超时、心跳判断等. 在合理的连接管理规划下, 这可提供使用者更优质的开发方案. APIcf框架中的httpd库内置了Websocket路由, 提供了上述Websocket连接管理能力. Websocket路由需要开发者提供一个lua版的class对象来抽象路由处理的过程, 这样的抽象能简化代码编写难度. lua classclass 意译为'类'. 是对'对象'的一种抽象描述, 多用于各种面相对象编程语言中. lua没有原生的class类型, 但是提供了基本构建的元方法. cf为了方便描述内置对象与内置库封装, 使用lua table的相关元方法建立了最基本的class模型. 几乎大部分内置库都依赖cf的class库. 同时为了简化class的学习成本, 去除了class原本拥有的'多重继承'概念. 将其仅作为类定义, 用于完成从class->object的初始化工作. ...

June 18, 2019 · 2 min · jiezi

帧动画内存OOM不存在的-SurfaceView逐帧解析

Android 提供了AnimationDrawable用于实现帧动画。在动画开始之前,所有帧的图片都被解析并占用内存,一旦动画较复杂帧数较多,在低配置手机上容易发生 OOM。即使不发生 OOM,也会对内存造成不小的压力。下面代码展示了一个帧数为4的帧动画: 原生帧动画AnimationDrawable drawable = new AnimationDrawable();drawable.addFrame(getDrawable(R.drawable.frame1), frameDuration);drawable.addFrame(getDrawable(R.drawable.frame2), frameDuration);drawable.addFrame(getDrawable(R.drawable.frame3), frameDuration);drawable.addFrame(getDrawable(R.drawable.frame4), frameDuration);drawable.setOneShot(true);ImageView ivFrameAnim = ((ImageView) findViewById(R.id.frame_anim));ivFrameAnim.setImageDrawable(drawable);drawable.start();有没有什么办法让帧动画的数据逐帧加载,而不是一次性全部加载到内存?SurfaceView就提供了这种能力。 SurfaceView屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。 上述过程中的计算,对于View来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()、onLayout()、onDraw()来定义帧内容,但不能定义帧刷新频率。 SurfaceView可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView的模版代码: public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback { public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50; //用于计算帧数据的线程 private HandlerThread handlerThread; private Handler handler; //帧刷新频率 private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND; //用于绘制帧的画布 private Canvas canvas; private boolean isAlive; public BaseSurfaceView(Context context) { super(context); init(); } protected void init() { getHolder().addCallback(this); //设置透明背景,否则SurfaceView背景是黑的 setBackgroundTransparent(); } private void setBackgroundTransparent() { getHolder().setFormat(PixelFormat.TRANSLUCENT); setZOrderOnTop(true); } @Override public void surfaceCreated(SurfaceHolder holder) { isAlive = true; startDrawThread(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { stopDrawThread(); isAlive = false; } //停止帧绘制线程 private void stopDrawThread() { handlerThread.quit(); handler = null; } //启动帧绘制线程 private void startDrawThread() { handlerThread = new HandlerThread("SurfaceViewThread"); handlerThread.start(); handler = new Handler(handlerThread.getLooper()); handler.post(new DrawRunnable()); } private class DrawRunnable implements Runnable { @Override public void run() { if (!isAlive) { return; } try { //1.获取画布 canvas = getHolder().lockCanvas(); //2.绘制一帧 onFrameDraw(canvas); } catch (Exception e) { e.printStackTrace(); } finally { //3.将帧数据提交 getHolder().unlockCanvasAndPost(canvas); //4.一帧绘制结束 onFrameDrawFinish(); } //不停的将自己推送到绘制线程的消息队列以实现帧刷新 handler.postDelayed(this, frameDuration); } } protected abstract void onFrameDrawFinish(); protected abstract void onFrameDraw(Canvas canvas);}用HandlerThread作为独立帧绘制线程,好处是可以通过与其绑定的Handler方便地实现“每隔一段时间刷新”,而且在Surface被销毁的时候可以方便的调用HandlerThread.quit()来结束线程执行的逻辑。DrawRunnable.run()运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView的子类FrameSurfaceView:逐帧解析 & 及时回收public class FrameSurfaceView extends BaseSurfaceView { public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE; private List<Integer> bitmaps = new ArrayList<>(); //帧图片 private Bitmap frameBitmap; //帧索引 private int bitmapIndex = INVALID_BITMAP_INDEX; private Paint paint = new Paint(); private BitmapFactory.Options options = new BitmapFactory.Options(); //帧图片原始大小 private Rect srcRect; //帧图片目标大小 private Rect dstRect = new Rect(); private int defaultWidth; private int defaultHeight; public void setDuration(int duration) { int frameDuration = duration / bitmaps.size(); setFrameDuration(frameDuration); } public void setBitmaps(List<Integer> bitmaps) { if (bitmaps == null || bitmaps.size() == 0) { return; } this.bitmaps = bitmaps; //默认情况下,计算第一帧图片的原始大小 getBitmapDimension(bitmaps.get(0)); } private void getBitmapDimension(Integer integer) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(this.getResources(), integer, options); defaultWidth = options.outWidth; defaultHeight = options.outHeight; srcRect = new Rect(0, 0, defaultWidth, defaultHeight); requestLayout(); } public FrameSurfaceView(Context context) { super(context); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); dstRect.set(0, 0, getWidth(), getHeight()); } @Override protected void onFrameDrawFinish() { //在一帧绘制完后,直接回收它 recycleOneFrame(); } //回收帧 private void recycleOneFrame() { if (frameBitmap != null) { frameBitmap.recycle(); frameBitmap = null; } } @Override protected void onFrameDraw(Canvas canvas) { //绘制一帧前需要先清画布,否则所有帧都叠在一起同时显示 clearCanvas(canvas); if (!isStart()) { return; } if (!isFinish()) { drawOneFrame(canvas); } else { onFrameAnimationEnd(); } } //绘制一帧,是张Bitmap private void drawOneFrame(Canvas canvas) { frameBitmap = BitmapUtil.decodeOriginBitmap(getResources(), bitmaps.get(bitmapIndex), options); canvas.drawBitmap(frameBitmap, srcRect, dstRect, paint); bitmapIndex++; } private void onFrameAnimationEnd() { reset(); } private void reset() { bitmapIndex = INVALID_BITMAP_INDEX; } //帧动画是否结束 private boolean isFinish() { return bitmapIndex >= bitmaps.size(); } //帧动画是否开始 private boolean isStart() { return bitmapIndex != INVALID_BITMAP_INDEX; } //开始播放帧动画 public void start() { bitmapIndex = 0; } private void clearCanvas(Canvas canvas) { paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawPaint(paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); }}FrameSurfaceView继承自BaseSurfaceView,所以它复用了基类的绘制框架算法,并且定了自己每一帧的绘制内容:一张Bitmap。Bitmap资源 id 通过setBitmaps()传递进来, 绘制一帧解析一张 ,在每一帧绘制完毕后,调用Bitmap.recycle()释放图片 native 内存并去除 java 堆中图片像素数据的引用。这样当 GC 发生时,图片像素数据可以及时被回收。一切都是这么地能够自圆其说,我迫不及待地运行代码并打开AndroidStudio的Profiler标签页,切换到MEMORY,想用真实内存数据验证下性能。但残酷的事实狠狠地打了下脸。。。多次播放帧动画后,内存占用居然比原生AnimationDrawable还大,而且每播放一次,内存中都会多出 N 个Bitmap对象(N为帧动画总帧数)。唯一令人欣慰的是,手动触发 GC 后帧动画图片能够被回收。(AnimationDrawable中的图片数据不会被 GC) ...

May 10, 2019 · 4 min · jiezi

前端性能优化策略

注意: 最好是在修改之后进行前后对比,在使用了优化方法之后和没使用的时候,因为就算使用了优化方式,也不一定会起到性能优化的效果,需要据场景而定。http 请求过程当中潜在的性能优化点静态资源一般放到 cdn 加快静态资源的获得,但是当我们 cdn 的域名和页面的域名相同的时候,请求会带上不许不需要的 cookies ,所以两者的域名不能一样。dns 缓存, 减少 访问 dns 的时间。对于接口,我们可以减少 http 请求的数量和大小。浏览器缓存。页面渲染过程优化。使用服务端渲染。资源压缩与合并在真实的开发中都是使用构建工具(webpack、gulp等)来完成下面的事情html 压缩去掉空格、注释、回车、换行。使用在线网网站进行压缩(实际公司中不适用)。使用 nodejs 提供的 html-minifier。后端模板引擎渲染压缩。css 压缩去掉空格、注释、回车、换行无效代码删除语义合并。使用在线网网站进行压缩(实际公司中不适用)。使用 nodejs 提供的 html-minifier。使用clean-cs对css进行压缩。js压缩与混乱无效字符的删除、剔除注释、代码语义的缩减和优化(如变量名缩短)、代码保护(将Js代码混乱,使其不可读)。使用在线网站进行压缩。使用html-minifier。使用uglifyjs2对js进行压缩 。文件合并文件合并可以减少网络请求, 而且浏览器能够同时并发请求有限,当请求过多的时候可能需要等待,文件合并可以减少请求,避免等待。但是存在首屏渲染问题(第一次请求时间长)、缓存失效(单个文件的缓存失效会导致合并当中其他文件的缓存失效)的问题。所以有如下的合并建议。公共库合并: 公共库一般不会变,我们可以将它们进行合并。不同页面的合并: 针对单页应用,只有当我们跳转到那个页面的时候,才去请求这个页面的资源,将这个页面涉及的资源进行合并。那么怎么进行文件合并呢?如下:使用在线网站进行文件合并。使用nodejs实现文件合并。图片优化每种图片格式都有自己的特点,针对不同的业务场景选择不同的图片格式很重要jgp有损压缩png: 颜色类型丰富的图片,应该选择位数高的pngpng8: 256色,支持透明png24: 2^24色 不支持透明png32: 2^24色 支持透明不同格式图片常用的业务场景jpg有损压缩,压缩率高,不支持透明: 大部分不需要透明图片的业务场景png支持透明,浏览器兼容好: 大部分需要透明图片的业务场景webp压缩程度更好,在ios webview有兼容性问题: 安卓全部svg矢量图,代码内嵌,相对较小,图片样式相对简单的场景: 图片样式相对简单的业务场景图片压缩可使用tinypng这个网站来压缩css 雪碧图把网站上用到的一些图片整合到一张单独的图片当中,从而减少大量的 http 请求的数量。缺点是图片会变大第一步是将图片合并到一张图片上第二步是使用在线网站,上传图片,选中指定的图片,然后会有相应的css。Image inline将图片内嵌到html当中, 减少网站的 http 请求数量, 如使用 base6 的方式插入图片, 一般网站当中一些小的图标可以用它,实际根具情况而定使用矢量图使用 svg 进行矢量图的绘制,图片的质量和速度都特别好,使用得也比较多。寄予页面渲染过程的优化https://segmentfault.com/a/11…并发浏览器对于一个域名的并发请求数量是有限的,所以我们需要设置多个域名,比如 cdn 设置多个域名懒加载和预加载懒加载一般都是对于图片来说,当图片进入可视区域的时候再加载图片将 url 地址放置到 img 标签当中,当进入可视区的时候,将其取出来设置预加载图片等静态资源在使用之前的提前请求资源使用到的时候从缓存当中加载有三种方式:直接在页面上把 img 标签放到前面, 然后 display: none;var image = new Image(); image.src = xxxx; 使其下载下来,后面使用的时候后会直接去缓存拿使用XMLHttpRequset, url为image.src的url, 可以使用它更加精细的对预加载的过程进行控制使用preload.js库 ...

March 16, 2019 · 1 min · jiezi

Visual Studio Code使用中CPU占用率异常暴增过高原因

今天要说的是一个困扰我好几个月的问题,Visual Studio Code(下文简称VSCode)在使用中突然增高,风扇开始狂转,温度骤增,影响心情的故障原因。其实,无论是Windows还是OSX,很多人可能或多或少都遇到过VSCode突然就不好使了,我就遇到过好多次疑难杂症,折腾很久才弄出来,比如下面三点:tab键突然就不好使了,卡顿很久或者压根无法缩进,并且sidebar的git那块功能彻底失效!写Markdown文档的时候,tab键的缩进只能向右,不能收回。。。这真是奇葩。使用中莫名其妙的风扇就响了起来,看看进程和温度,CPU满载执行,完全不知道怎么回事,这也是本次要专门提到的问题。系统使用环境及VSCode状态检测我使用的是黑苹果,当然这个与CPU占用率增高并无关系,通过code –status查看一些基本信息如下:P750TM:~ whidy$ code –statusVersion: Code 1.30.2 (61122f88f0bf01e2ac16bdb9e1bc4571755f5bd8, 2019-01-07T22:48:31.260Z)OS Version: Darwin x64 17.7.0CPUs: Intel(R) Core(TM) i5-8600K CPU @ 3.60GHz (6 x 3600)Memory (System): 16.00GB (5.22GB free)Load (avg): 2, 2, 2VM: 0%Screen Reader: noProcess Argv: –inspect-extensions=9993GPU Status: 2d_canvas: enabled checker_imaging: disabled_off flash_3d: enabled flash_stage3d: enabled flash_stage3d_baseline: enabled gpu_compositing: enabled multiple_raster_threads: enabled_on native_gpu_memory_buffers: enabled rasterization: unavailable_software video_decode: enabled video_encode: enabled webgl: enabled webgl2: enabledCPU % Mem MB PID Process 0 98 1775 code main 0 49 1776 gpu-process 0 229 1777 window (settings.json — mpa-stat-sdk) 0 0 1780 /bin/bash -l 0 115 1783 extensionHost 0 82 1787 /Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper –nolazy –inspect=10785 /Applications/Visual Studio Code.app/Contents/Resources/app/extensions/json-language-features/server/dist/jsonServerMain –node-ipc –clientProcessId=1783 0 49 1784 watcherService 0 49 1789 searchService 0 33 1785 utility 0 82 1817 shared-process 0 311 1830 window (ald-stat.js — one-plus-sport) 0 49 1831 watcherService 0 98 1832 extensionHost 4 66 1870 electron_node eslintServer.js 0 131 1871 electron_node tsserver.js 0 66 1879 electron_node typingsInstaller.js typesMap.js 0 49 1835 searchServiceWorkspace Stats: | Window (ald-stat.js — one-plus-sport)| Window (settings.json — mpa-stat-sdk)| Folder (one-plus-sport): 273 files| File types: js(75) json(58) wxss(57) wxml(56) png(21) md(2)| gitignore(1) xlsx(1) jpg(1) zip(1)| Conf files:| Folder (mpa-stat-sdk): 21 files| File types: js(13) md(3) json(2) zip(2) gitignore(1)| Conf files: gulp.js(1) package.json(1)故障现象先来看看正常情况下和非正常情况的运行情况对比图:上图为正常情况下的截图上图为异常情况下的截图这个问题真的令我很苦恼,我这两张截图期间绝对没有做任何可能会产生高计算需求的工作,但是正常的操作怎么会出现这种情况呢。故障分析及解决于是进行了大量的搜索,百度就不用看了,屎一样的结果:前5篇内容完全一致,结论:“search.followSymlinks”:true,在我这一点用也没用。顺便吐槽,我完全不理解,在中国尤其是CSDN,为什么一个简单的小问题,一大堆人转载,完全一样的内容,如果真的是神一般的技巧,敢不敢多写一点,为什么这样能解决问题,出现故障的原因呢,无脑抄袭就算了,做笔记请使用自己的笔记本,比如有道云笔记,印象笔记不好吗,难道没人知道你是抄的?简直浪费搜索时间!垃圾!吐槽完毕,该用google了,实际上,我一开始就没用百度,只是写这篇文章,担心有人遇到过这样的问题,写过相同的解决方案,说我是抄来的。就索性百度搜一下。用谷歌自然用英文,虽然我英语很渣,但是谷歌懂我。只需要几个关键词:无论是微软官方的issue查,还是stackoverflow查,总能有很大的收获,但是,我这个问题比较特殊,我尝试过最基本的两种处理办法:屏蔽所有插件测试重置自定义的settings.json文件然而都不好使。可怜我英文也不是特别好,有可能有些有用的信息被我忽略掉了。这里补充一下,其实大部分原因,可以通过官方提供的自排除方案来检查Performance Issues,我很推荐遇到CPU占用率过高的情况下先看看这篇文章。不过也不是全无收获,至少开头提到的三个问题,前两个查出来了。第一个问题是插件Auto Rename Tag造成的,这个至少在一年前是非常流行的,我自己也觉得很好用,就一直装了,完全想不到这个简单的功能居然会造成VSCode某些功能异常,去插件主页看看,作者也不更新维护了,插件评价页面全是一星,可见目前已经是垃圾插件了查看评论,不过过年很多无脑转载的还在推荐这个插件,所以为了避免大家入坑,建议不要使用Auto Rename Tag。第二个问题也是插件问题,就是Markdown All in One这个插件导致缩进功能不好使,原因我也不知道,其实这个问题并不严重,有强烈依赖改插件的朋友还是可以继续使用,我也很推荐这个插件写markdow,有些还是挺便捷的,不过我是删了哈哈哈,看个人意愿了~好了第三个问题才是最重要的,我反复观察了很久,做了大量测试和查阅文档,终于得出结论:当且仅当VSCode的窗口大于1个的时候,才会出现该现象出现异常经常出现在切换不同窗口之后发生我发现切换窗口后出现异常就搜索关键词two/multi vscode switch cause a high cpu useage终于找到了一丝丝线索,仔细阅读了下面几篇:Switching between VSCode windows with any custom app switcher causes high CPU usageapplication processes consume 200% CPU combinedExtreme CPU usage when multiple windows are openRenderer high CPU on OSX with custom window switchers我终于,发现了一个问题,我切换VSCode的窗口的方式有问题!!!我是用了罗技鼠标的快捷键功能导致,如图:啊,我的天啊!我反复尝试,在多个窗口,直接用键盘的Cmd + 来切换内部应用窗口,妥妥的一点毛病都没有。结论很多情况下VSCode功能异常都是插件引起的,尝试关闭所有插件来检查,建议阅读Performance Issues。其次是**第三方Switcher应用切换VSCode窗口会造成异常!比如常用的鼠标功能键!啊,坑了我好多个月,反复重装VSCode和系统都没法解决的毛病终于解决了。。。以后只能用Cmd + 来切换了 ...

January 28, 2019 · 2 min · jiezi

使用jMeter构造逻辑上有依赖关系的一系列并发请求

相信前端开发工程师对CSRF(Cross-site request forgery)跨站请求伪造这个概念都非常熟悉,有的时候也简写成XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户的请求来利用受信任的网站。CSRF攻击的防御方式有多种,最简单最易实现的一种思路就是在客户端向服务器发起的请求中放入攻击者无法伪造的信息,并且该信息没有存储于 cookie 之中。技术上来说,当客户端向服务器发起请求执行一些敏感操作之前(比如用HTTP post实现的转账,扣款等功能),服务器端随机产生一个token,返回给客户端。客户端接下来的操作,必须在HTTP请求中以参数的形式把这个服务器端颁发的token带上。同时服务器端在实现给客户端分配token的同时,也要加入一个token校验机制。如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这个token我们一般称为CSRF token。讲了这么多,是为了引入本文想要讨论的话题。假设我想用jMeter测试一个OOdata服务创建Service Ticket的性能。因为创建功能不像读操作,执行之后会对系统产生持久化影响(Persistence side-effect), 因此服务器端的实现加入了CSRF token的校验。这就是说,如果我们直接用jMeter构造并发的HTTP post请求,是没有办法完成测试的,这些请求因为没有包含CSRF token,会被服务器端直接拒绝掉。根据前面描述的CSRF攻防原理,CSRF token是服务器端随机生成的,客户端无法用任何技术进行伪造,因为为了测试接口HTTP post操作进行Service Ticket的创建,我们必须构造一个它的前置HTTP GET请求,专门用于得到服务器返回的CSRF token,然后再构造真正用于性能测试的HTTP POST请求,把第一步GET请求获得的CSRF token附到POST请求的头部中去。本文介绍在jMeter里如何维护并配置这种具有依赖关系的一组请求。当然如果您不喜欢用jMeter,想自己写代码实现,也是可以的。可以参考我放在github上的Java代码实现。用jMeter的好处是不需要编程,通过简单的配置就能实现这个性能测试需求,一般没有开发背景的测试人员也能独立完成。First let us have a look how JMeter could archive the same without even one line of programming.My project in JMeter is displayed with the following hierarchy. I have configured with “Number of 5 threads” in my thread group, so once executed, the response time of these 5 threads are displayed in result table together with average response time.从下图能看出,因为拿CSRF token的HTTP GET在逻辑上必须先于实际需要测试性能的HTTP POST请求,这实际上构成了一个Transaction-事务,所以我使用jMeter里提供的Transaction Controller来管理。Some key points for this JMeter project creation(1) Since now one thread should cover both XSRF token fetch via HTTP get and Service request creation via HTTP post, so a transaction controller is necessary to include both request.(2) Create the first HTTP request to fetch XSRF token. The setting could be found below: adding a http header field with name asx-csrf-token and value as “fetch”:在HTTP GET请求的头部加上一个名为x-csrf-token的字段,值赋成fetch。这样服务器接到这个请求,就知道这是客户端发起的CSRF token请求,于是服务器响应这个请求,把创建好的随机CSRF token通过HTTP response头部字段的方式返回给客户端。下一个问题就是,服务器返回给客户端合法的CSRF token后,jMeter如何读取到这个token,并用于接下来的请求?幸运的是,jMeter提供了正则表达式提取式,可以让我们很方便地从HTTP响应结构中提取出token来。Create a Regular Expression Extractor to parse the XSRF token from response header and stored it to a variable named “jerrycsrftoken”.下图构造了一个jMeter正则表达式提取器,工作于HTTP响应的头部字段,解析出的token值存储于变量jerrycsrftoken中。Before you continue, please make sure that the XSRF token is correctly parsed from request header, which could be confirmed by printing it out in a debug sample:这个请求构造完之后,我们先试着运行一次,确保在变量jerrycsrftoken里确实看到解析好的CSRF token。(3) Create another HTTP request with type POST.这时万事俱备,我们可以开始构造真正要进行性能测试的HTTP post,即Service Ticket的创建请求了。请求的报文正文:Just paste the following text to the tab “Body Data”:–batch_1Content-Type: multipart/mixed; boundary=changeset_1–changeset_1Content-Type: application/httpContent-Transfer-Encoding: binaryPOST ServiceRequestCollection HTTP/1.1Content-Length: 5000Accept: application/jsonContent-Type: application/json{“ServicePriorityCode”: “2”,“Name”: {“content”: “Jerry Testing ticket creation via JMeter ${uuid} “},“ServiceRequestDescription”: [{“Text”: “Piston Rattling 1 - Generic OData Test Create”,“TypeCode”: “10004”},{“Text”: “Piston Rattling 2 - Generic OData Test Create”,“TypeCode”: “10007”}]}–changeset_1—-batch_1–In the body text I use a user-defined variable ${uuid} which we could create it in last step. And for this post request, use the XSRF token fetched from previous HTTP get request.前面说过,POST请求的头部需要加上合法的CSRF token,此处我们使用前面GET请求已经拿到的并且存储于变量jerrycsrftoken中的token值:我希望最后通过并发测试生成的Service Ticket的描述信息的后缀是1到100的随机正整数,因此我使用jMeter里自带的一个随机数发生器:(4) As the last step, create a user variable by using JMeter built-in function __Random, to create a random number between 1 ~ 100 as a fragment of created Service Request description.Now execute the Thread group, and the execution detail for these three HTTP request could be reviewed separately in tree view:试着运行一下,发现这个POST操作确实按照我们期望的那样,在HTTP头部字段里加上了正确合法的CSRF token:For example, the XSRF token is successfully fetched in the first request: rdPy7zNj_uKDYvQLgfQCFA==And used as one header field in second HTTP Post request as expected:And finally in UI we could find the created Service request with random number between 1 ~ 100 as postfix:在UI上观测到我构造的5个并发请求创建的Service Ticket,说明CSRF token在服务器端的校验成功,同时发现描述信息都带上了随机数,说明我的jMeter随机数生成器的用法也正确。希望本文对大家的工作有所帮助。要获取更多Jerry的原创文章,请关注公众号"汪子熙”: ...

January 1, 2019 · 2 min · jiezi

使用Performance对页面进行分析优化(实战篇)

这篇文章将介绍下实际使用performance对页面进行优化的过程。总的来说,chrome performance工具让我们更方便的发现在代码运行过程中的问题在哪里,便于对一些可能注意不到的问题进行定位、分析和优化。原文首发于个人博客渲染优化首先,我们对进入整个详情页进行分析,整个页面的结构大致如下,主要包含三个部分:基本信息,可视化图和时间轴。时间轴内部包含时间轴的详情信息,包括表格和几种类型的可视化图等,内部比较重。如图所示:我们点击录制,查看进入页面的性能图:优化点1可以看到,渲染当前页面的耗时将近1.2s,我们看看到底是哪里出现了问题。对渲染部分进行放大,我们发现在渲染时间轴的过程中,大部分时间耗费在了每个时间节点详情内容的展示上面,但是默认状态下,他们是进行隐藏的。也就是说,我们进行了N个节点的不必要的渲染,只需把他们干掉就好了。查看代码,发现是以下位置导致的,我们进行了一个展开盒子的封装,类似这样:<Box data={data} border collapse loading={loading}> <AlertDetail /></Box>在非展开状态下,我们依然对内容进行了渲染,只是使用样式进行了隐藏,但是这样React仍然会进行虚拟dom的渲染和计算,在这里我们对其进行改造,让其只在展开时进行渲染,并尽量缓存渲染的结果。修改完成后,我们会发现,Scripting由之前的880+ms变成了670ms减少了200ms左右:优化点2我们继续看,会发现页面初始化时,详情部分会有大量的Evaluate Script耗时,主要是耗费在告警详情右侧的分类及各分类下的可视图及详情引起的。我们可能会想,这里在初始化时并没有进行渲染,为什么仍然会耗费时长进行计算呢?继续追查我发现原因在这里:在整个详情组件中,我们对包括http,tcp等所有类型的组件进行了引用,然后根据其类型进行组件的匹配,在各个组件中可能包含了每个类型对应的定义、分类和计算等等等等,不仅增加了加载时间,更延长了初始化时间,显然我们这么做是不对的。在此,我们可以使用懒加载方式对其进行优化,仅展示其对应类型的图,避免了不必要的资源浪费和计算时间。修改之后,我们再次进行性能测试,发现在进入页面时,详情组件的耗费时长由260ms变为不到2ms:而Scripting由之前的670+ms变成了415ms减少了250ms左右:至此,进入页面的耗时已由最开始的1.2s左右变成了现在的0.7s左右。更新优化我们在点击时间轴查看详情时,会进行几个操作。关闭其他已开启的详情内容,展开当前详情内容,根据当前的类型进行对应类型的详情内容展示,上边已经提到过。这个过程会涉及到React的update操作,我们来对这个过程进行一下优化。优化点1首先我们点击录制按钮,然后点击展开,并对其进行录制。我们会发现以下的结果:点击展开按钮,Timeline组件进行了多次重复渲染,显然这是不应该的,我们来看下是哪里导致的。我们看到整个时间轴组件中,有这样一段代码,在时间轴组件中使用connect连接了timeline和alertList两个数据,其中,alertList数据是详情内种中对应的告警列表。两组数据对应的更改都会映射到组件的更新,比如时间轴的展开收起,以及alertList请求,请求成功及失败等。按理来说,alertList对应的请求,仅对应到当前展开内容的更新即可。因此,我们对此有几种修改方案:时间轴组件中弃用对alertList的引用,以保证alertList不会牵连到时间轴组件整体更新将时间轴的渲染和详情渲染进行分离,向其传递各自对应的数据,通过PureComponent来控制更新使用shouldComponentUpdate进行优化在此我们采用第一种,避免alertList对整个组件的影响。我们会发现,点开详情后,整个timeLine只进行了一次大更新,详情的更新只在展开的组件中进行。优化点2我们会发现,在对某一个时间点进行展开时,整个list列表的节点都进行了更新,如下图所示。显然这样的性能耗费是及其大且不重要的。假如列表的内容很多,那极有可能造成大量的更新导致页面卡死。因为其他的list列表节点都引用了alertList这个prop, 当其进行改变时,所有的节点都会进行更新,所以我们需要使用shouldComponentUpdate手动对其进行优化:// 当开关状态变化时,才从新渲染,否则不需要shouldComponentUpdate (nextProps, nextState) { const opened = .get(this.props, ‘open’) const willOpen = .get(nextProps, ‘open’) if (opened === willOpen && !willOpen) { return false } else { // 类似pureComponent进行浅比较 return !.isEqual(this.props, nextProps) || !.isEqual(this.state, nextState) }}再次进行录制,我们发现只要当前Item进行了更新,整个Timeline更新时间缩短到不到40ms,极大的增加了体验。其他优化过程与上面类似,不再赘述。总结通过配合performance工具进行一步步优化,整个页面的渲染和更新性能有了极大的提升,我们也借此知道了在平时书写代码过程中需要注意的问题。我们简单的做一下总结:避免非展示状态的不必要的渲染必要时,手动进行懒加载以避免大型模块对页面进行营销,避免加载不必要的模块保证展示组件props的纯净性,避免因其他props的更改导致组件进行更新必要时,可使用shouldComponent进行手动优化平时可通过PureComponent来避免不必要的组件更新

October 10, 2018 · 1 min · jiezi