在开发HarmonyOS NEXT应用时,优化应用性能是至关重要的。文章将性能优化分为四篇按照顺序(1.并行化、预加载和缓存 2.布局嵌套层数 3.管理状态变量 4.系统接口)来介绍介绍应用开发过程中常见的一些性能问题,配合相关参考示例。
合理使用并行化、预加载和缓存
优化篇一:合理地使用并行化、预加载和缓存等方法,例如使用多线程并发、异步并发、Web预加载等能力,提升系统资源利用率,减少主线程负载,加快应用的启动速度和响应速度。
使用并行化提升启动速度
自定义组件创建完成之后,在build函数执行之前,将先执行aboutToAppear()生命周期回调函数。此时若在该函数中执行耗时操作,将阻塞UI渲染,增加UI主线程负担。因此,应尽量避免在自定义组件的生命周期内执行高耗时操作。在aboutToAppear()生命周期函数内建议只做当前组件的初始化逻辑,对于不需要等待结果的高耗时任务,可以使用多线程处理该任务,通过并发的方式避免主线程阻塞;也可以把耗时操作改为异步并发或延后处理,保证主线程优先处理组件绘制逻辑。
使用多线程执行耗时操作
在日常开发过程中经常会碰到这样的问题:主页的开发场景中有多个Tab页展示不同内容,在首次加载完主页后,切换到第二个Tab页时需要加载和处理网络数据,导致第二个Tab页的页面显示较慢,有较大的完成时延。
碰到此类问题,我们可以在生命周期aboutToAppear中,使用多线程并发的方法执行第二个Tab页的网络数据访问解析、数据加载等耗时操作,既可以提前完成数据加载,也不会影响主线程UI绘制和渲染。
使用TaskPool进行耗时操作的示例代码如下:
import taskpool from'@ohos.taskpool';aboutToAppear(){...// 在生命周期中,使用TaskPool加载和解析网络数据this.requestByTaskPool();}@ConcurrentgetInfoFromHttp():string[]{// 从网络加载数据return http.request();}requestByTaskPool():void{// 创建任务项let task: taskpool.Task =newtaskpool.Task(this.getInfoFromHttp);try{// 执行网络加载函数
taskpool.execute(task, taskpool.Priority.HIGH).then((res:string[])=>{});}catch(err){
logger.error(TAG,"failed, "+(err as BusinessError).toString());}}
使用异步执行耗时操作
问题:在aboutToAppear生命周期函数中,运行了业务数据解析和处理等耗时操作,影响了上一页面点击跳转该页面的响应时延。
可以把耗时操作的执行从同步执行改为异步或者延后执行,比如使用setTimeOut执行耗时操作,示例如下:
aboutToAppear(){...// 在生命周期中,使用异步处理数据,延时大小视情况确定setTimeout(()=>{this.workoutResult();},1000)}workoutResult():string[]{// 处理需要展示的业务数据let data: Data[]=[];for(let i =1; i <100; i++){
result += data[i];}return result;}
使用预加载提升页面启动和响应速度
应该合理使用系统的预加载能力,例如Web组件的预连接、预加载、预渲染,使用List、Swiper、Grid、WaterFlow等组件的cachedCount属性实现预加载,使用条件渲染实现预加载)等,提升页面的启动和响应速度。
使用Web组件的预连接、预加载、预渲染能力
当我们碰到Web页面加载慢的场景,我们可以使用Web组件的预连接、预加载、预渲染能力,在应用空闲时间提前进行Web引擎初始化和页面加载,提升下一页面的启动和响应速度。
示例代码如下:
import webview from'@ohos.web.webview';preload(){// Web组件引擎初始化
webview.WebviewController.initializeWebEngine();// 启动预连接,连接地址为即将打开的网址
webview.WebviewController.prepareForPageLoad('https://gitee.com/harmonyos-cases/cases',true,2);}...
使用cachedCount属性实现预加载
推荐在使用List、Swiper、Grid、WaterFlow等组件时,配合使用cachedCount属性实现预加载,示例代码如下所示:
private source: MyDataSource =newMyDataSource();build(){List(){LazyForEach(this.source, item =>{ListItem(){Text("Hello"+ item).fontSize(50).onAppear(()=>{console.log("appear:"+ item)})}})}.cachedCount(3)// 扩大数值appear日志范围会变大}
使用条件渲染实现预加载
问题:页面布局复杂度较高,导致跳转该页面的响应时延较高。
可以使用条件渲染的方式,添加页面的简单骨架图作为默认展示页面,等数据加载完成后再显示最终的复杂布局,加快点击响应速度。
示例代码如下:
import skeletonComponent from"./skeletonComponent"import businessComponent from"./businessComponent"@State isInitialized:boolean=falsebuild(){// 当数据未就位时展示骨架图,提升点击响应速度,减少页面渲染时间if(!this.isInitialized){// 网络数据未获取前使用骨架图skeletonComponent();}else{// 数据获取后再刷新显示内容businessComponent();}}
使用缓存提升启动速度和滑动帧率
在列表场景中,我们推荐使用LazyForEach+组件复用+缓存列表项的能力,替代Scroll/ForEach实现滚动列表场景的实现,加快页面启动速度,提升滑动帧率;在一些属性动画的场景下,我们可以使用renderGroup缓存提升属性动画性能;也可以使用显隐控制对页面进行缓存,加快页面的显示响应速度。
组件复用
HarmonyOS应用框架提供了组件复用能力,可复用组件从组件树上移除时,会进入到一个回收缓存区。后续创建新组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。
若业务实现中存在以下场景,并成为UI线程的帧率瓶颈,推荐使用组件复用:
- 列表滚动(本例中的场景):当应用需要展示大量数据的列表,并且用户进行滚动操作时,频繁创建和销毁列表项的视图可能导致卡顿和性能问题。在这种情况下,使用列表组件的组件复用机制可以重用已经创建的列表项视图,提高滚动的流畅度。
- 动态布局更新:如果应用中的界面需要频繁地进行布局更新,例如根据用户的操作或数据变化动态改变视图结构和样式,重复创建和销毁视图可能导致频繁的布局计算,影响帧率。在这种情况下,使用组件复用可以避免不必要的视图创建和布局计算,提高性能。
- 地图渲染:在地图渲染这种场景下,频繁创建和销毁数据项的视图可能导致性能问题。使用组件复用可以重用已创建的视图,只更新数据的内容,减少视图的创建和销毁,能有效提高性能。
示例代码如下:
// xxx.etsclassMyDataSourceimplementsIDataSource{private dataArray:string[]=[];private listener: DataChangeListener |undefined;...}@Entry@Component
struct MyComponent {private data: MyDataSource =newMyDataSource();aboutToAppear(){for(let i =0; i <1000; i++){this.data.pushData(i.toString());}}build(){List({ space:3}){LazyForEach(this.data,(item:string)=>{ListItem(){ReusableChildComponent({ item: item })}},(item:string)=> item)}.width('100%').height('100%')}}@Reusable@Component
struct ReusableChildComponent {@State item:string=''// 复用时触发的生命周期aboutToReuse(params: ESObject){this.item = params.item;}build(){Row(){Text(this.item).fontSize(20).margin({ left:10})}.margin({ left:10, right:10})}}
使用renderGroup缓存提升属性动画性能
页面响应时,可能大量使用属性动画和转场动画,当复杂度达到一定程度之后,就有可能出现卡顿的情况。renderGroup是组件通用方法,它代表了渲染绘制的一个组合。
具体原理是在首次绘制组件时,若组件被标记为启用renderGroup状态,将对组件及其子组件进行离屏绘制,将绘制结果合并保存到缓存中。此后当需要重新绘制相同组件时,就会优先使用缓存而不必重新绘制了,从而降低绘制负载,进而加快响应速度。
示例代码如下:
// Index.etsimport{ IconItem }from'./IconItem'// IconItem相关数据classIconItemSource{
image:string| Resource =''
text:string| Resource =''...}@Entry@Component
struct Index {private iconItemSourceList: IconItemSource[]=[];aboutToAppear(){// 遍历添加IconItem的数据this.iconItemSourceList.push(newIconItemSource($r('app.media.img1'),`label1`),newIconItemSource($r('app.media.img2'),`label2`),newIconItemSource($r('app.media.img3'),`label3`),);}build(){Column(){// IconItem放置在grid内GridRow({}){ForEach(this.iconItemSourceList,(item: IconItemSource)=>{GridCol(){IconItem({ image: item.image, text: item.text }).transition(
TransitionEffect.scale({}).animation({}).combine(TransitionEffect.rotate({}).animation({})))}})}}}}// IconItem.ets@Componentexport struct IconItem {...build(){Flex(){Image(this.image)Text(this.text)}// 在IconItem内开启renderGroup.renderGroup(true)}}
使用显隐控制进行页面缓存
控制元素显示与隐藏是一种常见的场景,使用Visibility.None、if条件判断等都能够实现该效果。其中if条件判断控制的是组件的创建、布局阶段,visibility属性控制的是元素在布局阶段是否参与布局渲染。使用时如果使用的方式不当,将引起性能上的问题。
如果会频繁响应显示与隐藏的交互效果,建议使用切换Visibility.None和Visibility.Visible来控制元素显示与隐藏,在组件无需展示的时候进行缓存,提高性能。
示例代码如下:
@State isVisible:boolean=true;build(){Column(){Button("Switch visible and hidden").onClick(()=>{this.isVisible =!(this.isVisible);})Stack(){Scroll(){Column(){Image($r('app.media.icon'))}}.visibility(this.isVisible ? Visibility.Visible : Visibility.None)// 使用显隐控制切换,不会频繁创建与销毁组件}}}
版权归原作者 周星0927 所有, 如有侵权,请联系我们删除。