自学内容网 自学内容网

鸿蒙面试 --- 性能优化

性能优化可以从三个方面入手  感知流畅、渲染性能、运行性能

感知流畅

在应用开发中,动画可以为用户界面增添生动、流畅的交互效果,提升用户对应用的好感度。然而,滥用动画也会导致应用性能下降,消耗过多的系统资源,甚至影响用户体验。

视觉感知优化:应用的卡顿其实就是视觉上出现了不流畅的画面,引起了用户的注意,令其产生了一定的不适感。这也就意味着,在用户操作后,需要第一时间从视觉层面给与反馈响应,从而解决视觉动作带来的不适。

开发者可以在用户的交互动作开始时,从感知角度添加一些动画元素,比如单击效果、转场缩放、加载进度条、共享动画等,这些可以告诉用户目前状态发生了变化,APP在快速地运作着;而动画的背后是:数据的计算,布局的渲染,内容的加载等等,当新界面渲染显示完成,上述动画元素就可通过渐变消失、移出屏外等友好的方式退出视觉区域。

转场场景动效感知流畅 :HarmonyOS系统为开发者提供了丰富的转场动效库,使开发者能够轻松实现各种转场动画效果。开发者可以根据具体需求,在应用的不同场景中应用这些转场动效,以提升用户体验和界面的吸引力。

转场动画分为基础转场和高级模板化转场,有如下几类:

合理动画时长使应用感知流畅 :页面的转场动画是提升用户体验的重要环节。然而,当动画时延耗时较长时,它会对用户的点击完成时延产生显著影响。动画的完成时间直接关系到用户何时能够开始与应用进行交互。动画时延影响点击完成时延的根因主要为动画时长设置过长。

常见的页面转场动画时长参数有:

使用连贯动画使应用快速响应 :通过多种不同的连贯动画,让应用使用者在操作过程中能够感受到应用的快速响应。

渲染性能 

控制渲染范围、减少布局节点、优化组件绘制、控制状态刷新、优化动画帧率

控制渲染范围

在应用开发实践中,我们可以通过控制UI的渲染范围,从而防止阻塞UI渲染,引发界面卡顿或掉帧现象。

合理控制元素显示与隐藏:使用Visibility.Noneif条件判断等都能够实现该效果。其中if条件判断控制的是组件的创建、布局阶段,visibility属性控制的是元素在布局阶段是否参与布局渲染。

懒加载:LazyForEach是一种延迟加载的技术,它是在需要的时候才加载数据或资源,并在每次迭代过程中创建相应的组件,而不是一次性将所有内容都加载出来。通常应用于长列表优化长列表优化网格优化瀑布流优化等数据量较大、子组件可重复使用的场景,当用户滚动页面到相应位置时,才会触发资源的加载,以减少组件的加载时间,提高应用性能,提升用户体验。

懒加载可以通过设置cachedCount来指定缓存数量,在设置cachedCount后,除屏幕内显示的Item组件外,还会预先将屏幕可视区外指定数量的数据缓存。

组件复用:组件复用是优化用户界面性能,提升应用流畅度的一种重要手段,通过复用已存在的组件节点而非创建新的节点,从而确保UI线程的流畅性与响应速度。组件复用针对的是自定义组件,只要发生了相同自定义组件销毁和再创建的场景,都可以使用组件复用,例如长列表优化瀑布流优化Swiper组件优化等场景,

复用类型

描述

复用思路

标准型

复用组件之间布局完全相同

标准复用

有限变化型

复用组件之间布局有所不同,但是类型有限

使用reuseId或者独立成不同自定义组件

组合型

复用组件之间布局有不同,情况非常多,但是拥有共同的子组件

将复用组件改为@Builder,让内部子组件相互之间复用

全局型

组件可在不同的父组件中复用,并且不适合使用@Builder

使用BuilderNode自定义复用组件池,在整个应用中自由流转

嵌套型

复用组件的子组件的子组件存在差异

采用化归思想将嵌套问题转化为上面四种标准类型来解决

 分帧渲染:在应用开发中,页面内列表结构复杂,每个列表项包含的组件较多,就会导致嵌套层级较深,从而引起组件负载加重,绘制耗时增长。

减少布局节点

在进行页面布局开发时,应该尽量减少布局节点,避免系统绘制更多的布局组件,达到优化渲染性能、减少内存占用的目的。

优先使用@Builder方法代替自定义组件:由于@Builder不涉及生命周期,在自定义组件大量嵌套的场景中,更加轻量级的@Builder在性能方面更加出色。因此当自定义组件不涉及到状态变量和自定义生命周期时,可以优先使用@Builder替换自定义组件,提升性能。

合理使用布局容器组件:在进行UI布局时,子组件会根据父组件的布局算法得到相应的排列规则,然后按照规则进行子组件位置的摆放。不同的布局容器使用的布局算法对性能带来的影响不同。开发者应该根据场景选用合适的布局,除非必须,尽量减少使用性能差的布局组件

精简节点数:在进行UI布局时,在布局测算阶段下,额外的节点数将导致更多的计算过程,造成性能消耗。应该移除冗余节点来精简节点数,主要有如下几种优化方式:

  • 移除冗余节点、使用扁平化布局减少节点数。具体案例请参阅移除冗余节点
  • 自定义组件自身为非渲染节点,仅是组件树和状态数据的组合,常规使用自定义组件时并不会产生多余的节点。但是给自定义组件添加属性后,会将自定义组件作为一个整体节点进行处理。需通过优化手段减少自定义组件产生多余节点
  • 在组件嵌套的情况中,可以找到一些无用的容器组件嵌套。在考虑组件嵌套优化中,可以删除无用的Stack/Column/Row嵌套,移除冗余节点,从而避免冗余节点对性能的消耗。
  • 实际上有些场景直接使用组件属性或借助系统API的能力就能实现,例如使用overlay属性可以实现浮层场景,使用ColorMetrics可以实现颜色叠加效果。优先使用组件属性代替嵌套组件方式可以减少布局嵌套组件的使用,从而精简节点数。

优化组件绘制

应用启动后页面加载和渲染的性能与FrameNode树上的节点数量以及每个节点上的属性相关。因此,为缩短页面加载和布局渲染时长,在前端使用UI组件时可以考虑以下优化方案。

避免在定义组件生命周期内执行高耗时操作:自定义组件生命周期如图所示,自定义组件创建完成之后,在build函数执行之前,将先执行aboutToAppear()生命周期回调函数。此时若在该函数中执行耗时操作,将阻塞UI渲染,增加UI主线程负担。因此,应尽量避免在自定义组件的生命周期内执行高耗时操作。

按需注册组件属性 :在使用组件开发应用UI界面时,会为每个组件设置属性,进行UI样式、行为等逻辑处理。当应用中单个组件设置了大量属性且该组件在应用中被大量使用时,单个属性的设置对应用的整体性能会产生较大影响。

减少布局计算:对于组件的宽高不需要自适应的情况下,建议在UI描述时给定组件的宽高数值

控制状态刷新

在声明式UI编程范式中,UI是应用程序状态的函数,应用程序状态的修改会更新相应的UI界面。ArkUI采用了MVVM模式,其中ViewModel将数据与视图绑定在一起,更新数据的时候直接更新视图。

ArkUI提供了一系列装饰器实现ViewModel的能力,如@Prop@Link@ProvideLocalStorage等。当自定义组件内变量被装饰器装饰时变为状态变量,状态变量的改变会引起UI的渲染刷新。 

避免不必要的状态变量使用 :

  • 状态变量的管理有一定的开销,应在合理场景使用,普通的变量用状态变量标记可能会导致性能劣化,应删除冗余的状态变量标记
  • 通过使用临时变量的计算代替直接操作状态变量,可以使ArkUI仅在最后一次状态变量变更时查询并渲染组件,建议使用临时变量替换状态变量,减少不必要的行为,从而提高应用性能。

最小化状态共享范围 :

  • 状态变量使用范围不当,可能会带来冗余刷新的性能问题。
  • 在没有强烈的业务需求下,尽可能按照状态需要共享的最小范围选择合适的装饰器实现最小化状态共享范围。应用开发过程中,按照组件颗粒度,状态一般分为组件内独享的状态和组件间需要共享的状态。

减少不必要的参数层层传递 :当共享状态的组件间层级相差较大时,会出现状态层层传递的现象。对于状态传递过程中途经的全部组件,都需要增加入参接收该状态再将状态传递给子组件,因此应减少不必要的参数层层传递按照状态复杂度选择装饰器

精细化拆分复杂状态 :对于AppStorage的使用,由于其作用范围最广,开发者为了方便开发容易将各种状态存入其中以达到共享的目的,这通常会造成大量的性能损失

集中化状态修改逻辑:当多个子组件修改状态的逻辑基本相同时,建议将状态的修改集中到单个函数中,以提升逻辑的可复用性、代码的可维护性和可测试性。

使用监听和订阅精准控制组件刷新:在多个组件依赖同一个数据源并根据数据源变化刷新组件的情况下,直接关联数据源会导致每次数据源改变都刷新所有组件。为精准控制组件刷新,可以采取以下策略:

  • 在组件中使用 @Watch 装饰器监听数据源,当数据变化时执行业务逻辑,确保只有满足条件的组件进行刷新。
  • 当组件关系复杂或跨越层级过多时,推荐使用EventHub或者Emitter实现自定义事件发布订阅。当数据源改变时发布事件,依赖该数据源的组件通过订阅事件来获取数据源的改变,完成业务逻辑的处理,从而实现组件的精准刷新。

优化动画帧率

动画在应用开发中扮演着重要的角色,能够提升用户体验,传达信息,引导用户操作,提升应用品质和增加视觉吸引力。而动画的性能表现也至关重要,优化可以从属性更新和布局等几个方面考虑,尽可能减少冗余刷新。

使用系统提供的动画接口:一般而言,在HarmonyOS应用开发中,动画设计实现可以通过自定义动画或系统提供的动画接口两种方式来实现。系统接口经过精心设计和优化,使用系统提供的动画接口能够在不同设备上提供流畅的动画效果,最大程度地减少丢帧率和卡顿现象。

使用图形变换属性变化组件:通过使用图形变换属性变化组件,而不是直接修改组件的布局属性,可以减少不必要的布局计算和重绘操作,从而降低丢帧率,提升动画的流畅度和响应速度。

 合理使用animateTo:

  • 在组件中每次调用animateTo方法,都会触发一次属性变化,这意味着在每次动画执行时都需要进行动画前后的对比,以确定属性的变化情况。当多次连续调用animateTo时,会增加额外的布局计算和绘制开销,从而降低性能表现。在实际开发中,如果多个属性需要以相同的动画参数进行变化,推荐将它们放到同一个动画闭包中执行,即参数相同时使用同一个animateTo
  • 在进行多次动画操作时,统一更新状态变量可以避免不必要的状态更新和重复渲染,从而减少性能开销。如果多个animateTo之间存在状态更新,会导致执行下一个animateTo之前又存在需要更新的脏节点,可能造成冗余更新,因此应该多次animateTo时统一更新状态变量

 使用renderGroup缓存动效:在单一页面上存在大量应用动效的组件时,可以使用renderGroup来解决卡顿问题,从而提升动画性能。首次绘制组件时,若组件被标记为启用renderGroup状态,将对组件和其子组件进行离屏绘制,将绘制结果进行缓存。此后当需要重新绘制组件时,就会优先使用缓存而不必重新绘制,从而降低绘制负载,优化渲染性能。

运行性能

使用并发能力、提前加载资源、提高运行效率、减少耗时操作、延时触发操作

使用并发能力

应用中的并发优化就是在响应用户操作期间,尽可能地让主线程只执行UI绘制相关的任务,而将非UI的耗时任务分配给其他线程或者延迟处理。这样借助多线程的异步技术,充分利用多核处理器的能力,提高应用程序的并发处理能力,减少用户等待时间,保证用户界面的响应流畅性。

 使用多线程能力:自定义组件创建完成之后,在build函数执行之前,将先执行aboutToAppear()生命周期回调函数。此时若在该函数中执行耗时操作,将阻塞UI渲染,增加UI主线程负担。

对于不需要等待结果的高耗时任务,可以使用多线程处理该任务,通过并发的方式避免主线程阻塞;也可以把耗时操作改为异步并发或延后处理,保证主线程优先处理组件绘制逻辑。ArkTS提供了TaskPoolWorker两种多线程并发方案,具体两种并发方案区别对比请参阅TaskPool和Worker的对比实践

【ArkTS】“一篇带你掌握TaskPool与Worker两种多线程并发方案”-CSDN博客

使用异步能力:Promiseasync/await提供异步并发能力,是标准的JS异步语法。异步代码会被挂起并在之后继续执行,同一时间只有一段代码执行,适用于单次I/O任务的场景开发,例如一次网络请求、一次文件读写等操作。无需另外启动线程执行。可以把耗时操作的执行从同步执行改为异步或者延后执行,比如使用setTimeOut执行耗时操作实现应用冷启动优化

HarmonyOs DevEco Studio小技巧24--异步编程(Promises、async/await)_harmonyos await-CSDN博客

 多线程间通信:对象/方法在跨线程传递时均会涉及到其序列化和反序列化的过程。当对象本身较大且结构复杂时,序列化/反序列化的耗时就会增加,从而影响应用运行的整体性能。使用异步能力Sendable对象可以实现数据在多线程间的引用传递,具体使用场景请参阅Sendable使用场景案例分析

提前加载资源

网络请求优化:在附带网络请求的页面跳转场景中,完成时延耗时长的绝大多数原因都是因为网络数据Http请求时间长。由于网络是从操作系统侧发起和控制的,且网络环境存在不可控性,所以我们很难在业务逻辑的代码中优化请求速度。因此应尽可能的提前发起网络请求

Web组件的预连接、预加载、预渲染 :当遇到Web页面加载慢的场景,可以使用Web组件的预连接、预加载、预渲染能力,在应用空闲时间提前进行Web引擎初始化和页面加载,提升下一页面的启动和响应速度,具体原理与案例请参阅Web组件开发性能提升指导

预下载优化Image白块 :为了减少白块的出现,开发者可以采用预下载的方式,将网络图片通过应用沙箱的方式进行提前缓存,将图片下载解码提前到组件创建之前执行,当Image组件加载时从应用沙箱中获取缓存数据。非首次请求时会判断应用沙箱里是否存在资源,如存在直接从缓存里获取,不再重复下载,减少Image加载大的网络图片时白屏或白块出现时长较长的问题,提升用户体验。

提高运行效率 

在语法使用过程中,通过优化一些影响性能的代码片段,使代码以最优的方式执行提高运行效率。

变量声明 :

  • 对于初期明确不会改变的变量,尽量使用const声明常量。这里的常量包含基础类型和引用类型。通过const保证地址不会发生变化,能够极大减少由于编码时误操作导致的赋值等行为,造成对原有逻辑的改变,声明为const能够在编辑时及时发现错误。
  • 对于number类型,编译器在优化时会区分int和double类型。开发者在初始化number类型的变量时指定number的类型,如果预期是整数类型就初始化为0小数类型就初始化为0.0,避免将一个number类型初始化为undefined或者null。
  • ESObject主要用于在ArkTS和TS/JS跨语言调用的场景中作为类型标注,在非跨语言场景中使用ESObject标注类型,会引入不必要的跨语言调用,造成额外的性能开销,建议在非跨语言调用的场景下减少使用ESObject,引入明确的类型进行注释。

属性访问 :

  • 在要求性能的场景下,建议通过使用将全局变量存储为局部变量的方式来减少变量的属性查找,因为访问局部变量的速度要比访问全局变量的速度更快。重复的访问同一个变量,将造成不必要的消耗,尤其当类似的访问出现在循环过程中,其对于性能的影响更大。
  • 在ArkTS中,对于类结构的属性提供了private、protected和public可访问修饰符。默认情况下一个属性的可访问修饰符为public。给类属性添加访问修饰符选取适当的可访问修饰符可以提升代码的安全性、可读性。

 数值计算与数据结构:

  • 如果是纯数值计算的场合,推荐数值计算使用TypedArray数据结构。TypedArray类型化数组是一种类似数组的对象,其提供了一种用于在内存缓冲中访问原始二进制数据的机制。在一些图像数据处理、加解密的数据计算过程中使用TypedArray可以提高数据处理的效率,因为TypedArray是基于ArrayBuffer实现,在性能方面也能够进行较大提升。
  • 通过选取合适的数据结构提高运行效率。例如有些时候会采用Record的方式作为临时容器来处理属性存取的逻辑,HashMap是ArkTS提供的高性能容器类,底层使用红黑树实现,提供了高性能的数据读写操作,可以用来实现快速读写键值。

减少使用嵌套export */import *的方式 :

  • 由于依赖模块解析采用深度优先遍历的方式来遍历模块依赖关系图中每一个模块记录,会先从入口文件的第一个导入语句开始一层层往更深层查找,直到最后一个没有导入语句的模块为止,连接好这个模块的导出变量之后又会回到上一级的模块重复这个步骤,因此减少使用嵌套export *的方式全量导出,降低依赖模块解析、文件执行阶段耗时增长。
  • 当工具类中存在较多暴露函数或变量时,推荐按需引用使用到的变量减少import *的方式全量引用,可以减少该阶段中.ets文件执行耗时,即减少文件中所有export变量的初始化过程。

这里的话 导出我们有一个统一资源管理的文件 index

减少耗时操作 

在应用开发实践中,有效避免主线程执行冗余与易耗时操作是至关重要的策略。此举能有效降低主线程负载,提升UI的响应速度。

避免主线程冗余操作:在软件开发中,冗余操作指的是那些不必要、重复执行且对程序功能无实质性贡献的操作。这些操作不仅会浪费计算资源,还可能降低程序的运行效率,特别是在高频调用的场景下,其负面影响更为显著。

避免高频回调执行耗时操作:高频回调接口通常是指在应用程序运行过程中会被频繁触发的事件或回调函数。

避免使用耗时接口:

  • 在应用开发中,经常会调用系统提供的接口,比如读取本地文件、处理服务端数据等等。若对耗时接口使用不合理,可能引起延迟、卡顿、丢帧等性能问题。具体案例可参阅避免使用耗时接口
  • 通过系统框架封装的API,可以对数据及其结构进行访问、管理、增添或更新等一系列操作。通常来讲,数据库的调用会比较耗时,常见的增、删、改、查等都提供了异步接口,合理使用这些接口不会对响应性能产生影响。具体案例与性能实验数据请参阅减少调用数据库API次数

延时触发操作 

延迟加载Lazy-Import与动态加载await import:随着应用功能持续增加,应用规模不断扩大,依赖的模块文件逐渐变多,应用冷启动加载模块的时间也越来越长。

  • 动态加载(动态import)是一种模块加载机制,允许应用程序在运行时按照实际需求去加载相关模块。在某些条件满足时(比如用户交互时,或ABTest分支切换时)再加载特定模块,可以减少初始化import的加载时间和资源消耗,这将有助于提高应用程序的内存性能和响应速度。
  • 以通过延迟加载 Lazy-Import 的方法延缓对这些冗余文件的加载,使待加载文件在冷启动阶段不被加载,而在后续导出变量被真正使用时再同步加载执行文件,节省资源以提高应用冷启动性能。具体案例与实验数据请参阅延迟加载Lazy-Import使用指导

延迟执行资源释放操作 :将资源关闭和释放操作放在setTimeout函数中执行,使其延迟到系统相对空闲的时刻进行,可以避免在程序忙碌时段占用关键资源,提升整体性能及响应能力。例如相机正常使用后,延迟执行释放相机资源的相关操作。


原文地址:https://blog.csdn.net/hqy1989/article/details/144074941

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!