class="hide-preCode-box">
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
以下代码展示了将一个长时间执行的非UI任务通过Promise声明成异步任务,主线程可以先进行用户反馈-绘制初始页面。等主线程空闲时,再执行异步任务。等到异步任务运行完毕后,重绘相关组件刷新页面。
@ Entry
@ Component
struct AspectRatioExample4 {
@ State private children: string [ ] = [ '1' , '2' , '3' , '4' , '5' , '6' ] ;
private count: number = 0 ;
aboutToAppear ( ) {
this . computeTaskAsync ( ) ;
}
computeTask ( ) {
this . count = 0 ;
while ( this . count < 100000000 ) {
this . count++ ;
}
this . children = this . children. reverse ( ) ;
}
computeTaskAsync ( ) {
setTimeout ( ( ) => {
this . computeTask ( ) ;
} , 1000 )
}
build ( ) {
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
减少刷新的组件数量
应用刷新页面时需要尽可能减少刷新的组件数量,如果数量过多会导致主线程执行测量、布局的耗时过长,还会在自定义组件新建和销毁过程中,多次调用aboutToAppear()、aboutToDisappear()方法,增加主线程负载。
使用容器限制刷新范围
反例:如果容器内有组件被if条件包含,if条件结果变更会触发创建和销毁该组件,如果此时影响到容器的布局,该容器内所有组件都会刷新,导致主线程UI刷新耗时过长。
以下代码的Text(‘New Page’)组件被状态变量isVisible控制,isVisible为true时创建,false时销毁。当isVisible发生变化时,Stack容器内的所有组件都会刷新:
@ Entry
@ Component
struct StackExample5 {
@ State isVisible : boolean = false ;
build ( ) {
Column ( ) {
Stack ( { alignContent: Alignment. Top} ) {
Text ( ) . width ( '100%' ) . height ( '70%' ) . backgroundColor ( 0xd2cab3 )
. align ( Alignment. Center) . textAlign ( TextAlign. Center) ;
if ( this . isVisible) {
Text ( 'New Page' ) . height ( "100%" ) . height ( "70%" ) . backgroundColor ( 0xd2cab3 )
. align ( Alignment. Center) . textAlign ( TextAlign. Center) ;
}
}
Button ( "press" ) . onClick ( ( ) => {
this . isVisible = ! ( this . isVisible) ;
} )
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
建议:对于这种受状态变量控制的组件,在if外套一层容器,减少刷新范围。
@ Entry
@ Component
struct StackExample6 {
@ State isVisible : boolean = false ;
build ( ) {
Column ( ) {
Stack ( { alignContent: Alignment. Top} ) {
Text ( ) . width ( '100%' ) . height ( '70%' ) . backgroundColor ( 0xd2cab3 )
. align ( Alignment. Center) . textAlign ( TextAlign. Center) ;
Stack ( ) {
if ( this . isVisible) {
Text ( 'New Page' ) . height ( "100%" ) . height ( "70%" ) . backgroundColor ( 0xd2cab3 )
. align ( Alignment. Center) . textAlign ( TextAlign. Center) ;
}
} . width ( '100%' ) . height ( '70%' )
}
Button ( "press" ) . onClick ( ( ) => {
this . isVisible = ! ( this . isVisible) ;
} )
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
按需加载列表组件的元素
反例:this.arr中的每一项元素都被初始化和加载,数组中的元素有10000个,主线程执行耗时长。
@ Entry
@ Component
struct MyComponent7 {
@ State arr: number [ ] = Array . from ( Array < number > ( 10000 ) , ( v, k) => k) ;
build ( ) {
List ( ) {
ForEach ( this . arr, ( item: number ) => {
ListItem ( ) {
Text ( ` item value: ${ item} ` )
}
} , ( item: number ) => item. toString ( ) )
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
建议:这种情况下用LazyForEach替换ForEach,LazyForEach一般只加载可见的元素,避免一次性初始化和加载所有元素。
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[ ] = [ ]
public totalCount ( ) : number {
return 0
}
public getData ( index: number ) : string {
return ''
}
registerDataChangeListener ( listener: DataChangeListener) : void {
if ( this . listeners. indexOf ( listener) < 0 ) {
console . info ( 'add listener' )
this . listeners. push ( listener)
}
}
unregisterDataChangeListener ( listener: DataChangeListener) : void {
const pos = this . listeners. indexOf ( listener) ;
if ( pos >= 0 ) {
console . info ( 'remove listener' )
this . listeners. splice ( pos, 1 )
}
}
notifyDataReload ( ) : void {
this . listeners. forEach ( listener => {
listener. onDataReloaded ( )
} )
}
notifyDataAdd ( index: number ) : void {
this . listeners. forEach ( listener => {
listener. onDataAdd ( index)
} )
}
notifyDataChange ( index: number ) : void {
this . listeners. forEach ( listener => {
listener. onDataChange ( index)
} )
}
notifyDataDelete ( index: number ) : void {
this . listeners. forEach ( listener => {
listener. onDataDelete ( index)
} )
}
notifyDataMove ( from: number , to: number ) : void {
this . listeners. forEach ( listener => {
listener. onDataMove ( from, to)
} )
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string [ ] = Array . from ( Array < number > ( 10000 ) , ( v, k) => k. toString ( ) ) ;
public totalCount ( ) : number {
return this . dataArray. length
}
public getData ( index: number ) : string {
return this . dataArray[ index]
}
public addData ( index: number , data: string ) : void {
this . dataArray. splice ( index, 0 , data)
this . notifyDataAdd ( index)
}
public pushData ( data: string ) : void {
this . dataArray. push ( data)
this . notifyDataAdd ( this . dataArray. length - 1 )
}
}
@ Entry
@ Component
struct MyComponent {
private data: MyDataSource = new MyDataSource ( )
build ( ) {
List ( ) {
LazyForEach ( this . data, ( item: string ) => {
ListItem ( ) {
Text ( item) . fontSize ( 20 ) . margin ( { left: 10 } )
}
} , ( item: string ) => item)
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
合理使用缓存提升响应速度
缓存可以存储经常访问的数据或资源,当下次需要访问相同数据时,可以直接从缓存中获取,避免了重复的计算或请求,从而加快了响应速度。
使用AVPlayer实例缓存提升视频加载速度
AVPlayer实例的创建与销毁都很消耗性能,针对这个问题可以使用实例缓存进行优化,首次加载页面时创建两个实例,在打开新页面时切换空闲实例,通过reset方法重置实例到初始化状态。优化点在于不需要频繁创建销毁实例,且reset方法性能优于release方法。下面以AVPlayer为例列出正反例对比供参考。
反例:打开新页面时创建实例,离开页面时使用release方法销毁实例。
import media from '@ohos.multimedia.media' ;
@ Entry
@ Component
struct Index {
private avPlayer: media. AVPlayer | undefined = undefined ;
aboutToAppear ( ) : void {
media. createAVPlayer ( ) . then ( ( ret) => {
this . avPlayer = ret;
} ) ;
}
aboutToDisappear ( ) : void {
if ( this . avPlayer) {
this . avPlayer. release ( ) ;
}
this . avPlayer = undefined ;
}
build ( ) {
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
正例:首次加载页面时维护两个实例,在切换页面时切换实例,并将之前的实例通过reset方法重置。
import media from '@ohos.multimedia.media' ;
@ Entry
@ Component
struct Index {
private avPlayer: media. AVPlayer | undefined = undefined ;
private avPlayerManager: AVPlayerManager = AVPlayerManager. getInstance ( ) ;
aboutToAppear ( ) : void {
this . avPlayerManager. switchPlayer ( ) ;
this . avPlayer = this . avPlayerManager. getCurrentPlayer ( ) ;
}
aboutToDisappear ( ) : void {
this . avPlayerManager. resetCurrentPlayer ( ) ;
this . avPlayer = undefined ;
}
build ( ) {
}
}
class AVPlayerManager {
private static instance? : AVPlayerManager;
private player1? : media. AVPlayer;
private player2? : media. AVPlayer;
private currentPlayer? : media. AVPlayer;
public static getInstance ( ) : AVPlayerManager {
if ( ! AVPlayerManager. instance) {
AVPlayerManager. instance = new AVPlayerManager ( ) ;
}
return AVPlayerManager. instance;
}
async AVPlayerManager ( ) {
this . player1 = await media. createAVPlayer ( ) ;
this . player2 = await media. createAVPlayer ( ) ;
}
switchPlayer ( ) : void {
if ( this . currentPlayer === this . player1) {
this . currentPlayer = this . player2;
} else {
this . currentPlayer = this . player1;
}
}
getCurrentPlayer ( ) : media. AVPlayer | undefined {
return this . currentPlayer;
}
resetCurrentPlayer ( ) : void {
this . currentPlayer?. pause ( ( ) => {
this . currentPlayer?. reset ( ) ;
} ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
合理使用预加载提升响应速度
使用NodeContainer提前渲染降低响应时延
应用启动时有广告页的场景下。如果先渲染广告页而后再渲染首页,很可能造成首页响应时延较长,影响用户体验。针对此类问题可以使用NodeContainer在广告页渲染时同步渲染首页,等到跳转到首页时直接送显,提高响应速度。
反例:按次序依次渲染送显
主要代码逻辑如下:
1、模拟广告页,通过点击不同按钮分别进入普通页面和预加载页面
import router from '@ohos.router' ;
@ Entry
@ Component
struct Index {
build ( ) {
Column ( { space: 5 } ) {
Button ( "普通页面" )
. type ( ButtonType. Capsule)
. onClick ( ( ) => {
router. pushUrl ( { url: 'pages/CommonPage' } )
} )
Button ( "预加载页面" )
. type ( ButtonType. Capsule)
. onClick ( ( ) => {
router. pushUrl ( { url: 'pages/PreloadedPage' } )
} )
} . height ( '100%' )
. width ( '100%' )
. justifyContent ( FlexAlign. Center)
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
2、普通首页,也即按顺序普通渲染的页面
import { MyBuilder, getNumbers } from '../builder/CustomerBuilder' ;
@ Entry
@ Component
struct CommonPage {
build ( ) {
Row ( ) {
MyBuilder ( getNumbers ( ) )
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3、自定义builder,用来定制页面结构
@ Builder
export function MyBuilder ( numbers: string [ ] ) {
Column ( ) {
List ( { space: 20 , initialIndex: 0 } ) {
ForEach ( numbers, ( item: string ) => {
ListItem ( ) {
Text ( '' + item)
. width ( '100%' )
. height ( 50 )
. fontSize ( 16 )
. textAlign ( TextAlign. Center)
. borderRadius ( 10 )
. backgroundColor ( 0xFFFFFF )
}
} , ( day: string ) => day)
}
. listDirection ( Axis. Vertical)
. scrollBar ( BarState. Off)
. friction ( 0.6 )
. divider ( { strokeWidth: 2 , color: 0xFFFFFF , startMargin: 20 , endMargin: 20 } )
. edgeEffect ( EdgeEffect. Spring)
. width ( '90%' )
. height ( '100%' )
}
. width ( '100%' )
. height ( '100%' )
. backgroundColor ( 0xDCDCDC )
. padding ( { top: 5 } )
}
export const getNumbers = ( ) : string [ ] => {
const numbers: string [ ] = [ ] ;
for ( let i = 0 ; i < 100 ; i++ ) {
numbers. push ( '' + i)
}
return numbers;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
正例:在启动时预加载首页
主要代码逻辑如下:
1、应用启动时提前创建首页
import { ControllerManager } from '../builder/CustomerController' ;
import { getNumbers } from '../builder/CustomerBuilder' ;
export default class EntryAbility extends UIAbility {
onWindowStageCreate ( windowStage: window. WindowStage) : void {
hilog. info ( 0x0000 , 'testTag' , '%{public}s' , 'Ability onWindowStageCreate' ) ;
windowStage. loadContent ( 'pages/Index' , ( err, data) => {
if ( err. code) {
hilog. error ( 0x0000 , 'testTag' , 'Failed to load the content. Cause: %{public}s' , JSON . stringify ( err) ?? '' ) ;
return ;
}
hilog. info ( 0x0000 , 'testTag' , 'Succeeded in loading the content. Data: %{public}s' , JSON . stringify ( data) ?? '' ) ;
} ) ;
window. getLastWindow ( this . context, ( err: BusinessError, data) => {
if ( err. code) {
console . error ( 'Failed to obtain top window. Cause:' + JSON . stringify ( err) ) ;
return ;
}
ControllerManager. getInstance ( ) . createNode ( data. getUIContext ( ) , getNumbers ( ) ) ;
} )
}
onWindowStageDestroy ( ) : void {
hilog. info ( 0x0000 , 'testTag' , '%{public}s' , 'Ability onWindowStageDestroy' ) ;
ControllerManager. getInstance ( ) . clearNode ( ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
2、预加载的首页,使用NodeContainer进行占位,当跳转到本页时直接将提前创建完成的首页填充
import { ControllerManager } from '../builder/CustomerController' ;
@ Entry
@ Component
struct PreloadedPage {
build ( ) {
Row ( ) {
NodeContainer ( ControllerManager. getInstance ( ) . getNode ( ) )
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3、自定义NodeController,并提供提前创建首页的能力
import { UIContext } from '@ohos.arkui.UIContext' ;
import { NodeController, BuilderNode, FrameNode } from "@ohos.arkui.node" ;
import { MyBuilder } from './CustomerBuilder' ;
export class MyNodeController extends NodeController {
private rootNode: BuilderNode< [ string [ ] ] > | null = null ;
private wrapBuilder: WrappedBuilder< [ string [ ] ] > = wrapBuilder ( MyBuilder) ;
private numbers: string [ ] | null = null ;
constructor ( numbers: string [ ] ) {
super ( ) ;
this . numbers = numbers;
}
makeNode ( uiContext: UIContext) : FrameNode | null {
if ( this . rootNode != null ) {
return this . rootNode. getFrameNode ( ) ;
}
return null ;
}
initNode ( uiContext: UIContext) {
if ( this . rootNode != null ) {
return ;
}
this . rootNode = new BuilderNode ( uiContext)
this . rootNode. build ( this . wrapBuilder, this . numbers)
}
}
export class ControllerManager {
private static instance? : ControllerManager;
private myNodeController? : MyNodeController;
static getInstance ( ) : ControllerManager {
if ( ! ControllerManager. instance) {
ControllerManager. instance = new ControllerManager ( ) ;
}
return ControllerManager. instance;
}
createNode ( uiContext: UIContext, numbers: string [ ] ) {
this . myNodeController = new MyNodeController ( numbers) ;
this . myNodeController. initNode ( uiContext) ;
}
getNode ( ) : MyNodeController | undefined {
return this . myNodeController;
}
clearNode ( ) : void {
this . myNodeController = undefined ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
通过SmartPerf-Host工具抓取相关trace进行分析首页响应时延,其中主要关注两个trace tag分别是DispatchTouchEvent代表点击事件和MarshRSTransactionData代表响应,如下图所示:
反例响应时延:18.1ms
正例响应时延:9.4ms
由上述对比数据即可得出结论,预加载首页能优化首页响应时延。
为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
《鸿蒙开发学习手册》:
基本概念 构建第一个ArkTS应用 ……
应用基础知识 配置文件 应用数据管理 应用安全管理 应用隐私保护 三方应用调用管控机制 资源分类与访问 学习ArkTS语言 ……
Ability开发 UI开发 公共事件与通知 窗口管理 媒体 安全 网络与链接 电话服务 数据管理 后台任务(Background Task)管理 设备管理 设备使用信息统计 DFX 国际化开发 折叠屏系列 ……
1.项目开发必备面试题 2.性能优化方向 3.架构方向 4.鸿蒙开发系统底层方向 5.鸿蒙音视频开发 方向 6.鸿蒙车载开发方向 7.鸿蒙南向开发方向
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/weixin_61845324/article/details/138132580","extend1":"pc","ab":"new"}">>
id="blogExtensionBox" style="width:400px;margin:auto;margin-top:12px" class="blog-extension-box"> class="blog_extension blog_extension_type2" id="blog_extension">
class="extension_official" data-report-click="{"spm":"1001.2101.3001.6471"}" data-report-view="{"spm":"1001.2101.3001.6471"}">
class="blog_extension_card_left">
class="blog_extension_card_cont">
鸿蒙开发学习资料领取!!!
class="blog_extension_card_cont_r">
微信名片
评论记录:
回复评论: