diff --git a/en/application-dev/application-models/arkts-ui-widget-update-by-proxy.md b/en/application-dev/application-models/arkts-ui-widget-update-by-proxy.md index c655582af7834992c42025823b94ecab71eaa4ab..b7c1c93d21ec24673b3d07c4e971eadd7cd13661 100644 --- a/en/application-dev/application-models/arkts-ui-widget-update-by-proxy.md +++ b/en/application-dev/application-models/arkts-ui-widget-update-by-proxy.md @@ -89,7 +89,7 @@ The update-through-proxy configuration varies by the type of shared data. } ``` -- In the widget page code file **widgets.abc**, use the variable in LocalStorage to obtain the subscribed data. The variable in LocalStorage is bound to a string and updates the subscribed data in the key:value pair format. The key must be the same as that subscribed to by the widget provider. In this example, the subscribed data is obtained through **'detail'** and displayed in the **\** component. +- In the [widget page code file](arkts-ui-widget-creation.md), use the variable in LocalStorage to obtain the subscribed data. The variable in LocalStorage is bound to a string and updates the subscribed data in the key:value pair format. The key must be the same as that subscribed to by the widget provider. In this example, the subscribed data is obtained through **'detail'** and displayed in the **\** component. ```ts let storage = new LocalStorage(); @Entry(storage) @@ -178,7 +178,7 @@ The update-through-proxy configuration varies by the type of shared data. } ``` -- In the widget page code file (generally the .ets file in the **pages** folder under the widget directory of the project), use the variable in LocalStorage to obtain the subscribed data. The variable in LocalStorage is bound to a string and updates the subscribed data in the key:value pair format. The key must be the same as that subscribed to by the widget provider. In the example, the subscribed data is obtained through **'list'**, and the value of the first element is displayed on the **\** component. +- In the [widget page code file](arkts-ui-widget-creation.md), use the variable in LocalStorage to obtain the subscribed data. The variable in LocalStorage is bound to a string and updates the subscribed data in the key:value pair format. The key must be the same as that subscribed to by the widget provider. In the example, the subscribed data is obtained through **'list'**, and the value of the first element is displayed on the **\** component. ```ts let storage = new LocalStorage(); @Entry(storage) @@ -215,4 +215,4 @@ The update-through-proxy configuration varies by the type of shared data. ## Data Provider Development -For details, see [Data Management](../database/data-mgmt-overview.md). +For details, see [Data Management](../database/share-data-by-silent-access.md). diff --git a/en/application-dev/application-models/arkts-ui-widget-working-principles.md b/en/application-dev/application-models/arkts-ui-widget-working-principles.md index b1b09dc409380da8e530f571b2e5711ec63edd10..25cb66f1b05eaf845c11ab05350f2e705de6cec8 100644 --- a/en/application-dev/application-models/arkts-ui-widget-working-principles.md +++ b/en/application-dev/application-models/arkts-ui-widget-working-principles.md @@ -15,10 +15,11 @@ - Widget rendering service: a service that manages widget rendering instances. Widget rendering instances are bound to the [widget components](../reference/arkui-ts/ts-basic-components-formcomponent.md) on the widget host on a one-to-one basis. The widget rendering service runs the widget page code **widgets.abc** for rendering, and sends the rendered data to the corresponding widget component on the widget host. - **Figure 2** Working principles of the ArkTS widget rendering service -![WidgetRender](figures/WidgetRender.png) + **Figure 2** Working principles of the ArkTS widget rendering service -Unlike JS widgets, ArkTS widgets support logic code running. The widget page code **widgets.abc** is executed by the widget rendering service, which is managed by the Widget Manager. Each widget component of a widget host corresponds to a rendering instance in the widget rendering service. Rendering instances of a widget provider run in the same virtual machine operating environment, and rendering instances of different widget providers run in different virtual machine operating environments. In this way, the resources and state data are isolated between widgets of different widget providers. During development, pay attention to the use of the [globalThis](uiability-data-sync-with-ui.md#using-globalthis-between-uiability-and-page) object. Use one **globalThis** object for widgets from the same widget provider, and different **globalThis** objects for widgets from different widget providers. + ![WidgetRender](figures/WidgetRender.png) + +Unlike JS widgets, ArkTS widgets support logic code execution. The widget page code **widgets.abc** is executed by the widget rendering service, which is managed by the Widget Manager. Each widget component of a widget host corresponds to a rendering instance in the widget rendering service. Rendering instances of a widget provider run in the same virtual machine operating environment, and rendering instances of different widget providers run in different virtual machine operating environments. In this way, the resources and state data are isolated between widgets of different widget providers. During development, pay attention to the use of the [globalThis](uiability-data-sync-with-ui.md#using-globalthis-between-uiability-and-ui-page) object. Use one **globalThis** object for widgets from the same widget provider, and different **globalThis** objects for widgets from different widget providers. ## Advantages of ArkTS Widgets @@ -57,6 +58,8 @@ In addition, ArkTS widgets do not support the following features: - Instant preview -- Breakpoint debugging. +- Breakpoint debugging - Hot reload + +- **setTimeOut** diff --git a/en/application-dev/database/Readme-EN.md b/en/application-dev/database/Readme-EN.md index a39db8ad5a79562b7c77bb500641df29f9345c08..74a44f63945d867ff76bb783e2ef0a6feb35861c 100644 --- a/en/application-dev/database/Readme-EN.md +++ b/en/application-dev/database/Readme-EN.md @@ -19,8 +19,8 @@ - Cross-Application Data Sharing - [Data Sharing Overview](data-share-overview.md) - [Unified Data Definition](unified-data-definition.md) - - One-to-Many Data Sharing (Only for System Applications) - - [Sharing Data Using DataShareExtensionAbility](share-data-by-datashareextensionability.md) - - [Silent Access via the DatamgrService](share-data-by-silent-access.md) + - One-to-Many Data Sharing (for System Applications Only) + - [Sharing Data Using DataShareExtensionAbility](share-data-by-datashareextensionability.md) + - [Silent Access via the DatamgrService](share-data-by-silent-access.md) - Many-to-Many Data Sharing - [Sharing Data Using Unified Data Channels](unified-data-channels.md) \ No newline at end of file diff --git a/en/application-dev/faqs/faqs-arkui-arkts.md b/en/application-dev/faqs/faqs-arkui-arkts.md index 30372ac1e4f810f87e225b397b2aa5f95208ed0c..4cae24c691f9dab62d4c3452f32f69d3cf5b0da0 100644 --- a/en/application-dev/faqs/faqs-arkui-arkts.md +++ b/en/application-dev/faqs/faqs-arkui-arkts.md @@ -760,147 +760,6 @@ Text in the **\** component is centered by default. You do not need to set [Text](../reference/arkui-ts/ts-basic-components-text.md#example-1) -## How do I set the controlButton attribute for the \ component? - -Applicable to: OpenHarmony 3.2 Beta5 (API version 9) - -**Solution** - -The sample code is as follows: - -``` -@Entry -@Component -struct SideBarContainerExample { - normalIcon : Resource = $r("app.media.icon") - selectedIcon: Resource = $r("app.media.icon") - @State arr: number[] = [1, 2, 3] - @State current: number = 1 - - build() { - SideBarContainer(SideBarContainerType.Embed) - { - Column() { - ForEach(this.arr, (item, index) => { - Column({ space: 5 }) { - Image(this.current === item ? this.selectedIcon : this.normalIcon).width(64).height(64) - Text("Index0" + item) - .fontSize(25) - .fontColor(this.current === item ? '#0A59F7' : '#999') - .fontFamily('source-sans-pro,cursive,sans-serif') - } - .onClick(() => { - this.current = item - }) - }, item => item) - }.width('100%') - .justifyContent(FlexAlign.SpaceEvenly) - .backgroundColor('#19000000') - - - Column() { - Text('SideBarContainer content text1').fontSize(25) - Text('SideBarContainer content text2').fontSize(25) - } - .margin({ top: 50, left: 20, right: 30 }) - } - .sideBarWidth(150) - .minSideBarWidth(50) - .controlButton({left:32, - top:32, - width:32, - height:32, - icons:{shown: $r("app.media.icon"), - hidden: $r("app.media.icon"), - switching: $r("app.media.icon")}}) - .maxSideBarWidth(300) - .onChange((value: boolean) => { - console.info('status:' + value) - }) - } -} -``` - -## How do I implement the dragging feature for the \ component? - -Applicable to: OpenHarmony 3.2 Beta5 (API version 9) - -**Solution** - -1. Set the **editMode\(true\)** attribute of the **\** component to specify whether the component enters the editing mode. In the editing mode, you can drag grid items. -2. Set the image displayed during dragging in the [onItemDragStart](../reference/arkui-ts/ts-container-grid.md#events) callback. -3. Obtain the drag start position and drag insertion position from the [onItemDrop](../reference/arkui-ts/ts-container-grid.md#events) callback, and complete the array position exchange logic in the [onDrag](../reference/arkui-ts/ts-universal-events-drag-drop.md#events) callback. The sample code is as follows: - - ``` - @Entry - @Component - struct GridExample { - @State numbers: String[] = [] - scroller: Scroller = new Scroller() - @State text: string = 'drag' - - @Builder pixelMapBuilder () { // Drag style - Column() { - Text(this.text) - .fontSize(16) - .backgroundColor(0xF9CF93) - .width(80) - .height(80) - .textAlign(TextAlign.Center) - } - } - - aboutToAppear() { - for (let i = 1;i <= 15; i++) { - this.numbers.push(i + '') - } - } - - changeIndex(index1: number, index2: number) {// Exchange the array item position. - [this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]]; - } - - build() { - Column({ space: 5 }) { - Grid(this.scroller) { - ForEach(this.numbers, (day: string) => { - GridItem() { - Text(day) - .fontSize(16) - .backgroundColor(0xF9CF93) - .width(80) - .height(80) - .textAlign(TextAlign.Center) - .onTouch((event: TouchEvent) => { - if (event.type === TouchType.Up) { - this.text = day - } - }) - } - }) - } - .columnsTemplate('1fr 1fr 1fr') - .columnsGap(10) - .rowsGap(10) - .onScrollIndex((first: number) => { - console.info(first.toString()) - }) - .width('90%') - .backgroundColor(0xFAEEE0) - .height(300) - .editMode(true) // Set whether the grid enters the editing mode. In the editing mode, you can drag grid items. - .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { // Triggered when a grid item starts to be dragged. - return this.pixelMapBuilder() // Set the image displayed during dragging. - }) - .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => { // Triggered when the dragged item is dropped on the drop target of the grid. - console.info('beixiang' + itemIndex + '', insertIndex + '') // itemIndex indicates the initial position of the dragged item; insertIndex indicates the index of the position to which the dragged item will be dropped. - this.changeIndex(itemIndex, insertIndex) - }) - }.width('100%').margin({ top: 5 }) - } - } - ``` - ## Which API is used for URL encoding? diff --git a/en/application-dev/faqs/faqs-arkui-component.md b/en/application-dev/faqs/faqs-arkui-component.md index 0bb884119bda149effc09337d957ccd2231bf1c7..a61d4cb828cc8cab4b8a4636adbf66729caba10b 100644 --- a/en/application-dev/faqs/faqs-arkui-component.md +++ b/en/application-dev/faqs/faqs-arkui-component.md @@ -1,10 +1,10 @@ # ArkUI Component Development (ArkTS) -## Can custom dialog boxes be defined or used in .ts files? +## Can custom dialog boxes be defined and used in .ts files? Applicable to: OpenHarmony 3.2 Beta 5 (API version 9) -Unfortunately, no. ArkTS syntax is required for defining and initializing custom dialog boxes. Therefore, they can be defined and used only in .ets files. +Unfortunately not. Custom dialog boxes require ArkTS syntax for definition and initialization. Therefore, they can be defined and used only in .ets files. **Reference** @@ -245,8 +245,8 @@ When a custom dialog box contains a child component whose area size can be chang **Solution** -- Method 1: Use the default style of the custom dialog box. In this case, the dialog box automatically adapts its width to the grid system and its height to the child components; the maximum height is 90% of the container height. -- Method 2: Use a custom style of the custom dialog box. In this case, the dialog box automatically adapts its width and height to the child components. +- Method 1: Set the custom dialog box to the default style. In this style, the dialog box automatically adapts its width to the grid system and its height to the child components; the maximum height is 90% of the container height. +- Method 2: Set the custom dialog box to a custom style. In this style, the dialog box automatically adapts its width and height to the child components. **Reference** @@ -685,3 +685,64 @@ You can use **focusControl.requestFocus** to control the focus of the text input **Reference** [Focus Control](../reference/arkui-ts/ts-universal-attributes-focus.md) + +## How do I set the controlButton attribute for the \ component? + +Applicable to: OpenHarmony 3.2 Beta5 (API version 9) + +**Solution** + +Refer to the following sample code: + +``` +@Entry +@Component +struct SideBarContainerExample { + normalIcon : Resource = $r("app.media.icon") + selectedIcon: Resource = $r("app.media.icon") + @State arr: number[] = [1, 2, 3] + @State current: number = 1 + + build() { + SideBarContainer(SideBarContainerType.Embed) + { + Column() { + ForEach(this.arr, (item, index) => { + Column({ space: 5 }) { + Image(this.current === item ? this.selectedIcon : this.normalIcon).width(64).height(64) + Text("Index0" + item) + .fontSize(25) + .fontColor(this.current === item ? '#0A59F7' : '#999') + .fontFamily('source-sans-pro,cursive,sans-serif') + } + .onClick(() => { + this.current = item + }) + }, item => item) + }.width('100%') + .justifyContent(FlexAlign.SpaceEvenly) + .backgroundColor('#19000000') + + + Column() { + Text('SideBarContainer content text1').fontSize(25) + Text('SideBarContainer content text2').fontSize(25) + } + .margin({ top: 50, left: 20, right: 30 }) + } + .sideBarWidth(150) + .minSideBarWidth(50) + .controlButton({left:32, + top:32, + width:32, + height:32, + icons:{shown: $r("app.media.icon"), + hidden: $r("app.media.icon"), + switching: $r("app.media.icon")}}) + .maxSideBarWidth(300) + .onChange((value: boolean) => { + console.info('status:' + value) + }) + } +} +``` diff --git a/en/application-dev/performance/Readme.md b/en/application-dev/performance/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..8172a210af94348fdb22831295c90a19b74965e6 --- /dev/null +++ b/en/application-dev/performance/Readme.md @@ -0,0 +1,33 @@ +# Best Practices for Application Performance + +This topic outlines some best practices for improving your application performance to live up to user expectations for quick startup, timely response, and no frame freezing. + +Following these practices, you can reduce your application's startup time, response time, and frame loss. + +- Improving application startup and response time + + - [Speeding Up Application Cold Start](../performance/improve-application-startup-and-response/improve-application-cold-start-speed.md) + + Application startup latency is a key factor that affects user experience. To speed up the application cold start, you are advised to perform optimization in the following four phases: + + ​ 1. Application process creation and initialization + + ​ 2. Application and ability initialization + + ​ 3. Ability lifecycle + + ​ 4. Home page loading and drawing + + - [Speeding Up Application Response](../performance/improve-application-startup-and-response/improve-application-response.md) + + A premium interaction experience requires quick response to user input. To improve your application's response time, you are advised to prevent the main thread from being blocked by non-UI tasks and reduce the number of component to be refreshed. + +- Reducing frame loss + + - [Reducing Nesting](../performance/reduce-frame-loss-and-frame-freezing/reduce-view-nesting-levels.md) + + The smoothness of rendering the layout to the screen affects the user perceived quality. It is recommended that you minimize nesting in your code to shorten the render time. + + - [Reducing Frame Loss](../performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md) + + Whether animations in your application run smoothly is a key factor that affects user experience. You are advised to use the system-provided animation APIs to reduce frame loss. diff --git a/en/application-dev/performance/figure/application-cold-start.png b/en/application-dev/performance/figure/application-cold-start.png new file mode 100644 index 0000000000000000000000000000000000000000..210664879280518713b3ccb309875a059523319c Binary files /dev/null and b/en/application-dev/performance/figure/application-cold-start.png differ diff --git a/en/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md b/en/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md new file mode 100644 index 0000000000000000000000000000000000000000..b99c5dfc0ac84d75954b53b3a5c291bf60f4314e --- /dev/null +++ b/en/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md @@ -0,0 +1,110 @@ +# Speeding Up Application Cold Start + +Application startup latency is a key factor that affects user experience. When an application is started, the background does not have a process of the application, and therefore the system creates a new process and allocates it to the application. This startup mode is called cold start. + +## Analyzing the Time Required for Application Cold Start + +The cold start process of OpenHarmony applications can be divided into four phases: application process creation and initialization, application and ability initialization, ability lifecycle, and home page loading and drawing, as shown in the following figure. + +![application-cold-start](../figure/application-cold-start.png) + +## 1. Shortening Time Required for Application Process Creation And Initialization + +In the phase of application process creation and initialization, the system creates and initializes an application process, including decoding the icon of the startup page (specified by **startWindowIcon**). + +### Using startWindowIcon of Appropriate Resolution + +With regard to the icon of the startup page, the recommended maximum resolution is 256 x 256 pixels. Larger resolutions may result in slow startup. + +```json + "abilities": [ + { + "name": "EntryAbility", + "srcEntrance": "./ets/entryability/EntryAbility.ts", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startWindowIcon", // Modify the icon of the startup page. It is recommended that the icon be less than or equal to 256 pixels x 256 pixels. + "startWindowBackground": "$color:start_window_background", + "visible": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ] +``` + +## 2. Shortening Time Required for Application and Ability Initialization + +In this phase of application and ability initialization, resources are loaded, VMs are created, application and ability related objects are created and initialized, and dependent modules are loaded. + +### Minimizing the Number of Imported Modules + +Before the application code is executed, the application must find and load all imported modules. Each additional third-party framework or module to be loaded by the application increases the startup time. The time required depends on the number and size of loaded third-party frameworks or modules. To speed up startup, use system-provided modules when possible and load the modules as required. + +## 3. Shortening Time Required for Ability Lifecycle + +In this phase of ability lifecycle, the ability lifecycle callbacks are executed. + +### Avoiding Time-Consuming Operations in Ability Lifecycle Callbacks + +In the application startup process, the system executes the ability lifecycle callbacks. Whenever possible, avoid performing time-consuming operations in these callbacks. You are advised to perform time-consuming operations through asynchronous tasks or execute them in other threads. + +In these lifecycle callbacks, perform only necessary operations. For details, see [UIAbility Lifecycle](https://gitee.com/openharmony/docs/blob/master/en/application-dev/application-models/uiability-lifecycle.md). + +## 4. Shortening Time Required for Home Page Loading and Drawing + +In this phase of home page loading and drawing, the home page content is loaded, the layout is measured, and components are refreshed and drawn. + +### Avoid time-consuming operations in the custom component lifecycle callbacks. + +When the lifecycle of a custom component changes, the corresponding callback is called. + +The **aboutToAppear** function is executed after the custom component instance is created and before the page is drawn. The following code asynchronously processes the time-consuming computing task in **aboutToAppear** to avoid executing the operation in this function and blocking the page drawing. + +```javascript +@Entry +@Component +struct Index { + @State private text: string = undefined; + private count: number = undefined; + + aboutToAppear() { + this.computeTaskAsync(); // Asynchronous task + this.text = "hello world"; + } + + build() { + Column({space: 10}) { + Text(this.text).fontSize(50) + } + .width('100%') + .height('100%') + .padding(10) + } + + computeTask() { + this.count = 0; + while (this.count < 10000000) { + this.count++; + } + this.text = 'task complete'; + } + + // Asynchronous processing of the computing task + private computeTaskAsync() { + new Promise((resolved, rejected) => { + setTimeout(() => {// setTimeout is used to implement asynchronous processing. + this.computeTask(); + }, 1000) + }) + } +} +``` diff --git a/en/application-dev/performance/improve-application-startup-and-response/improve-application-response.md b/en/application-dev/performance/improve-application-startup-and-response/improve-application-response.md new file mode 100644 index 0000000000000000000000000000000000000000..b740edb6f1f4e2df007631f6feef966762e0d2ba --- /dev/null +++ b/en/application-dev/performance/improve-application-startup-and-response/improve-application-response.md @@ -0,0 +1,325 @@ +# Speeding Up Application Response + +This topic provides the following tips for improving your application's response to user input. + +- Prevent the main thread from being blocked by non-UI tasks. +- Reduce the number of components to be refreshed. + +## Preventing Main Thread from Being Blocked by Non-UI Tasks + +When the application responds to user input, its main thread should execute only UI tasks (such as preparation of data to be displayed and update of visible components). It is recommended that non-UI, time-consuming tasks (such as long-time content loading) be executed through asynchronous tasks or allocated to other threads. + +### Using Asynchronous Component Loading + +The **\** component has the asynchronous loading feature enabled by default. When an application loads a batch of local images to be displayed on the page, blank placeholder icons are displayed first, and then replaced by the images when these images have finished loading in other threads. In this way, image loading does not block page display. The following code is recommended only when the image loading takes a short time. + +```javascript +@Entry +@Component +struct ImageExample1 { + build() { + Column() { + Row() { + Image('resources/base/media/sss001.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + Image('resources/base/media/sss002.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + Image('resources/base/media/sss003.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + Image('resources/base/media/sss004.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + } + // Several containers are omitted here. Each container contains the preceding components. + } + } +} +``` + +Recommendation: If it takes a short time to load an image, the benefits of asynchronous loading will be greatly undermined. In this case, change the value of the syncLoad attribute. + +```javascript +@Entry +@Component +struct ImageExample1 { + build() { + Column() { + Row() { + Image('resources/base/media/sss001.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + Image('resources/base/media/sss002.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + Image('resources/base/media/sss003.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + Image('resources/base/media/sss004.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + } + // Several containers are omitted here. Each container contains the preceding components. + } + } +} +``` + +### Using TaskPool for Asynchronous Processing + +Compared with the worker thread, [TaskPool](https://gitee.com/sqsyqqy/docs/blob/master/en/application-dev/reference/apis/js-apis-taskpool.md) provides the task priority setting and automatic thread pool management mechanism. The following is an example: + +```javascript +import taskpool from '@ohos.taskpool'; + +@Concurrent +function computeTask(arr: string[]): string[] { + // Simulate a compute-intensive task. + let count = 0; + while (count < 100000000) { + count++; + } + return arr.reverse(); +} + +@Entry +@Component +struct AspectRatioExample { + @State children: string[] = ['1', '2', '3', '4', '5', '6']; + + aboutToAppear() { + this.computeTaskInTaskPool(); + } + + async computeTaskInTaskPool() { + const param = this.children.slice(); + let task = new taskpool.Task(computeTask, param); + // @ts-ignore + this.children = await taskpool.execute(task); + } + + build() { + // Component layout + } +} +``` + +### Creating Asynchronous Tasks + +The following code shows how to declare a long-running non-UI task as an asynchronous task through **Promise**. This allows the main thread to first focus on providing user feedback and completing the initial render, and then execute the asynchronous task when it is idle. After the asynchronous task is complete, related components are redrawn to refresh the page. + +```javascript +@Entry +@Component +struct AspectRatioExample { + @State private children: string[] = ['1', '2', '3', '4', '5', '6']; + private count: number = undefined; + + aboutToAppear() { + this.computeTaskAsync(); // Invoke the asynchronous compute function. + } + + // Simulate a compute-intensive task. + computeTask() { + this.count = 0; + while (this.count < 100000000) { + this.count++; + } + this.children = this.children.reverse(); + } + + computeTaskAsync() { + new Promise((resolved, rejected) => { + setTimeout(() => {// setTimeout is used to implement asynchronous processing. + this.computeTask(); + }, 1000) + }) + } + + build() { + // Component layout + } +} +``` + +## Reducing the Number of Components to Be Refreshed + +When an application refreshes a page, the number of components to be refreshed must be reduced as much as possible. If this number is too large, the main thread will take a long time to perform measurement and layout. In addition, the **aboutToAppear()** and **aboutToDisappear()** APIs will be called multiple times during the creation and destruction of custom components, increasing the load of the main thread. + +### Limiting the Refresh Scope with Containers + +Negative example: If a component in a container is included in the **if** condition, changes in the **if** condition result will trigger the creation and destruction of the component. If the container layout is affected in this case, all components in the container are refreshed. As a result, the UI refresh of the main thread takes a long time. + +In the following example, the **Text('New Page')** component is controlled by the state variable **isVisible**. When **isVisible** is set to **true**, the component is created. When **isVisible** is set to **false**, the component is destroyed. This means that, when the value of **isVisible** changes, all components in the **\** container are refreshed. + +```javascript +@Entry +@Component +struct StackExample { + @State isVisible : boolean = false; + + build() { + Column() { + Stack({alignContent: Alignment.Top}) { + Text().width('100%').height('70%').backgroundColor(0xd2cab3) + .align(Alignment.Center).textAlign(TextAlign.Center); + + // 100 identical components are omitted here. + + 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); + }) + } + } +} +``` + +Recommendation: For the component controlled by the state variable, add a container to the **if** statement to reduce the refresh scope. + +```javascript +@Entry +@Component +struct StackExample { + @State isVisible : boolean = false; + + build() { + Column() { + Stack({alignContent: Alignment.Top}) { + Text().width('100%').height('70%').backgroundColor(0xd2cab3) + .align(Alignment.Center).textAlign(TextAlign.Center); + + // 100 identical components are omitted here. + + 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); + }) + } + } +} +``` + +### Implementing On-Demand Loading of List Items + +Negative example: Each of the 10000 elements in **this.arr** is initialized and loaded. As a result, the execution of the main thread takes a long time. + +```javascript +@Entry +@Component +struct MyComponent { + @State arr: number[] = Array.from(Array(10000), (v,k) =>k); + build() { + List() { + ForEach(this.arr, (item: number) => { + ListItem() { + Text(`item value: ${item}`) + } + }, (item: number) => item.toString()) + } + } +} +``` + +Recommendation: In similar cases, replace **ForEach** with **LazyForEach** so that only visible elements are loaded. + +```javascript +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = [] + + public totalCount(): number { + return 0 + } + + public getData(index: number): any { + return undefined + } + + 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(10000), (v, k) => k.toString()); + + public totalCount(): number { + return this.dataArray.length + } + + public getData(index: number): any { + 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 => item) + } + } +} +``` diff --git a/en/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md b/en/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md new file mode 100644 index 0000000000000000000000000000000000000000..a61c8649e392b7fd0055e63ab48e0fa335faab7f --- /dev/null +++ b/en/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md @@ -0,0 +1,142 @@ +# Reducing Frame Loss + +Frame loss in the animation arena is a phenomenon where the frame rate of an animation drops when it is running or being created. + +When playing an animation, the system needs to calculate the animation curve and draw the component layout within a refresh period. You are advised to use the system-provided animation APIs. With these APIs, setting the curve type, end point position, and duration is enough for meeting common animation needs, thereby reducing the load of the UI main thread. + +Negative example: The application uses a custom animation, which involves animation curve calculation. This calculation process may cause high load of the UI thread and frame loss. + +```javascript +@Entry +@Component +struct AttrAnimationExample { + @State widthSize: number = 200 + @State heightSize: number = 100 + @State flag: boolean = true + + computeSize() { + let duration = 2000 + let period = 16 + let widthSizeEnd = undefined + let heightSizeEnd = undefined + if (this.flag) { + widthSizeEnd = 100 + heightSizeEnd = 50 + } else { + widthSizeEnd = 200 + heightSizeEnd = 100 + } + let doTimes = duration / period + let deltaHeight = (heightSizeEnd - this.heightSize) / doTimes + let deltaWeight = (widthSizeEnd - this.widthSize) / doTimes + for (let i = 1; i <= doTimes; i++) { + let t = period * (i); + setTimeout(() => { + this.heightSize = this.heightSize + deltaHeight + this.widthSize = this.widthSize + deltaWeight + }, t) + } + this.flag = !this.flag + } + + build() { + Column() { + Button('click me') + .onClick(() => { + let delay = 500 + setTimeout(() => { this.computeSize() }, delay) + }) + .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff) + }.width('100%').margin({ top: 5 }) + } +} +``` + +## Using System-Provided Attribute Animation APIs + +The following uses the system-provided attribute animation APIs to implement the preceding animation features: + +```javascript +@Entry +@Component +struct AttrAnimationExample { + @State widthSize: number = 200 + @State heightSize: number = 100 + @State flag: boolean = true + + build() { + Column() { + Button('click me') + .onClick((event: ClickEvent) => { + if (this.flag) { + this.widthSize = 100 + this.heightSize = 50 + } else { + this.widthSize = 200 + this.heightSize = 100 + } + this.flag = !this.flag + }) + .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff) + .animation({ + duration: 2000, // Animation duration. + curve: Curve.Linear, // Animation curve. + delay: 500, // Animation delay. + iterations: 1, // Number of playback times. + playMode: PlayMode.Normal // Animation playback mode. + }) // Animation configuration for the width and height attributes of the