HarmonyOS开发实战( Beta5版)优化实践/合理使用缓存提升性能

本文主要是介绍HarmonyOS开发实战( Beta5版)优化实践/合理使用缓存提升性能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

随着应用功能的日益丰富与复杂化,数据加载效率成为了衡量应用性能的重要指标。不合理的加载策略往往导致用户面临长时间的等待,这不仅损害了用户体验,还可能引发用户流失。因此,合理运用缓存技术变得尤为重要。 系统提供了Preferences、数据库、文件、AppStorage等缓存方式,开发者可以对应用数据先进行缓存,再次加载数据时优先展示缓存数据,减少加载时间,从而提升用户体验。本文将介绍如何通过缓存技术优化应用性能:针对网络数据和地址数据等内容,采用缓存策略,加速数据访问速度,提升应用性能。

问题场景

在应用启动流程中,开发者往往会遇到冷启动完成时延长的问题。这是由于大部分应用的首页数据依赖于网络请求或定位服务等方式来获取相应数据。如果网络、位置服务等信号差,就会导致应用请求网络和位置数据耗时变长,从而在页面冷启动过程中出现较长时间的白屏或白块现象。例如,在需要冷启动时即时加载首页地址数据的场景下,如果应用每次冷启动都通过getCurrentLocation获取位置数据,特别是在信号较弱的区域,这可能导致显著的延迟,迫使用户等待较长时间才能获取到所需的位置信息,从而极大地影响了应用的冷启动体验。针对上述问题,下面将通过使用缓存减少首次数据加载展示时间,优化应用启动性能,为开发者优化应用性能提供参考。

优化示例

在介绍示例前,先给开发者简单介绍一些冷启动首页中常用的缓存使用流程。

图1 三种常用的缓存使用流程

reasonable_using_cache_improve_performance_flow_chart

图1是三种常用的缓存使用流程。常用流程1的详细过程如下:

1.应用冷启动时,读取缓存。

2.判断是否有缓存数据。

3.如果本地没有缓存数据,则需要通过网络、位置服务等方式请求相应数据,然后把数据刷新到首页,同时异步更新缓存数据。

4.如果本地有缓存数据,则把缓存数据先刷新到应用首页,然后异步请求数据进行页面二刷,并更新缓存数据。

常用流程2和1的过程类似,只是常用流程2中省略了异步请求数据进行页面二刷并更新缓存的步骤。而常用流程3和2相比,常用流程3只是在本地有缓存数据时,增加了对缓存数据是否失效的处理。如果缓存数据没有失效,则把缓存数据刷新到应用首页。如果缓存数据已经失效,则需要重新请求数据,然后刷新到首页并更新缓存。

上述缓存使用流程仅为开发者提供参考,实际开发中需结合具体业务场景与需求进行灵活的调整与优化。下面将介绍缓存网络数据和缓存地址数据两个场景示例,并进行性能分析对比。

场景1缓存网络数据

图2 使用本地缓存首页数据流程图

reasonable_using_cache_improve_performance_network_flow_chart

图2是使用本地缓存首页数据的流程图。使用本地缓存优先展示冷启动首页数据,可以减少首帧展示完成时延,减少用户可见白屏或白块时间,提升用户的冷启动体验。

说明:

应用需根据自身对于数据的时效性要求,来决定是否使用缓存数据。例如时效性要求为一天时,一天前保存的缓存数据就不适合进行展示,需从网络获取新数据进行展示,并更新本地缓存数据。

下面是一个缓存网络数据的场景示例。示例中应用首页需展示一张从网站获取的图片信息,在aboutToAppear()中发起网络请求,待数据返回解析后展示在首页上。之后将图片信息缓存至本地应用沙箱内,再次冷启动时首先从沙箱内获取图片信息。若存在,即可解析并展示,在网络请求返回时再次更新图片信息。

以下为关键示例代码,源码参考:

import { http } from '@kit.NetworkKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { fileIo as fs } from '@kit.CoreFileKit';const PERMISSIONS: Array<Permissions> = ['ohos.permission.READ_MEDIA','ohos.permission.WRITE_MEDIA'
];
AppStorage.link('net_picture');
PersistentStorage.persistProp('net_picture', '');@Entry
@Component
struct Index {@State image: PixelMap | undefined = undefined;@State imageBuffer: ArrayBuffer | undefined = undefined; // 图片ArrayBuffer/*** 通过http的request方法从网络下载图片资源*/async getPicture() {http.createHttp().request('https://www.example1.com/POST?e=f&g=h',(error: BusinessError, data: http.HttpResponse) => {if (error) {return;}// 判断网络获取到的资源是否为ArrayBuffer类型if (data.result instanceof ArrayBuffer) {this.imageBuffer = data.result as ArrayBuffer;}this.transcodePixelMap(data);})}/*** 使用createPixelMap将ArrayBuffer类型的图片装换为PixelMap类型* @param data:网络获取到的资源*/transcodePixelMap(data: http.HttpResponse) {if (http.ResponseCode.OK === data.responseCode) {const imageData: ArrayBuffer = data.result as ArrayBuffer;// 通过ArrayBuffer创建图片源实例。const imageSource: image.ImageSource = image.createImageSource(imageData);const options: image.InitializationOptions = {'alphaType': 0, // 透明度'editable': false, // 是否可编辑'pixelFormat': 3, // 像素格式'scaleMode': 1, // 缩略值'size': { height: 100, width: 100 }}; // 创建图片大小// 通过属性创建PixelMapimageSource.createPixelMap(options).then((pixelMap: PixelMap) => {this.image = pixelMap;setTimeout(() => {if (this.imageBuffer !== undefined) {this.saveImage(this.imageBuffer);}}, 0)});}}/*** 保存ArrayBuffer到沙箱路径* @param buffer:图片ArrayBuffer* @returns*/async saveImage(buffer: ArrayBuffer | string): Promise<void> {const context = getContext(this) as common.UIAbilityContext;const filePath: string = context.cacheDir + '/test.jpg';AppStorage.set('net_picture', filePath);const file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);await fs.write(file.fd, buffer);await fs.close(file.fd);}async useCachePic(): Promise<void> {if (AppStorage.get('net_picture') !== '') {// 获取图片的ArrayBufferconst imageSource: image.ImageSource = image.createImageSource(AppStorage.get('net_picture'));const options: image.InitializationOptions = {'alphaType': 0, // 透明度'editable': false, // 是否可编辑'pixelFormat': 3, // 像素格式'scaleMode': 1, // 缩略值'size': { height: 100, width: 100 }};imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {this.image = pixelMap;});}}async aboutToAppear(): Promise<void> {const context = getContext(this) as common.UIAbilityContext;const atManager = abilityAccessCtrl.createAtManager();await atManager.requestPermissionsFromUser(context, PERMISSIONS);this.useCachePic(); // 从本地缓存获取数据this.getPicture(); // 从网络端获取数据}build() {Column() {Image(this.image).objectFit(ImageFit.Contain).width('50%').height('50%')}}
}

效果对比

下面对优化前后启动性能进行对比分析。分析阶段的起点为启动Ability(即H:void OHOS::AppExecFwk::MainThread::HandleLaunchAbility的开始点),阶段终点为应用首次解析Pixelmap(即H:Napi execute, name:CreatePixelMap, traceid:0x0)后的第一个vsync(即H:ReceiveVsync dataCount: 24bytes now:timestamp expectedEnd:timestamp vsyncId:int的开始点)。

图3 优化前未使用本地缓存 

reasonable_using_cache_improve_performance_network_use_api

图4 优化后使用本地缓存 

reasonable_using_cache_improve_performance_network_use_cache

图3是优化前未使用本地缓存(从网络端获取数据)的耗时,图4是优化后使用本地缓存的耗时,对比数据如下(性能耗时数据因设备版本环境而异,以实测为准):

方案阶段时长(毫秒)
(优化前)未使用本地缓存641.8
(优化后)使用本地缓存68.9

可以看到在使用本地缓存后,应用冷启动时从Ability启动到图片显示的阶段耗时明显减少。

场景2缓存地址数据

下面是一个使用PersistentStorage(持久化存储UI状态)缓存地址数据的场景示例,源码参考。主要步骤如下:

1.通过persistProp初始化PersistentStorage。

2.创建状态变量@StorageLink(MYLOCATION) myLocation,和AppStorage中MYLOCATION双向绑定。

3.应用冷启动时,先判断缓存AppStorage里MYLOCATION值是否为空(UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问)。

4.如果缓存为空,则从getCurrentLocation获取地址数据,并加载到页面,同时保存到缓存。如果缓存不为空,则直接从缓存获取地址数据,并加载到页面。

说明:

为了方便对比性能差异,本例中未做缓存数据是否失效和页面二刷的业务处理。实际业务开发中冷启动时虽然是优先从缓存获取地址数据进行刷新,但是后面还需要再使用getCurrentLocation获取最新地址数据进行页面二刷,以确保地址数据的准确性。

import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'; // 程序访问控制管理模块
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog, hiTraceMeter } from '@kit.PerformanceAnalysisKit'; // 性能打点模块
import { geoLocationManager } from '@kit.LocationKit'; // 位置服务模块。需要在module.json5中配置ohos.permission.APPROXIMATELY_LOCATION权限。// 写入与读取缓存位置数据的key值
const MYLOCATION = 'myLocation';
// 定义获取模糊位置的权限
const PERMISSIONS: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION'];
// 获取上下文信息
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
// 初始化PersistentStorage。PersistentStorage用于持久化存储选定的AppStorage属性
PersistentStorage.persistProp(MYLOCATION, '');@Entry
@Component
struct Index {// 创建状态变量@StorageLink(MYLOCATION) myLocation,和AppStorage中MYLOCATION双向绑定@StorageLink(MYLOCATION) myLocation: string = '';aboutToAppear() {// ApiDataTime表示从getCurrentLocation接口获取位置信息的性能打点起始位置。hiTraceMeter.startTrace("ApiDataTime", 1);// CacheDataTime表示从AppStorage缓存中获取位置信息的性能打点起始位置。hiTraceMeter.startTrace("CacheDataTime", 1);// 从AppStorage缓存中获取位置信息let cacheData = AppStorage.get<string>(MYLOCATION);// 缓存中如果有位置信息,则直接从缓存获取位置信息。如果没有,则从getCurrentLocation接口获取位置信息。if (cacheData !== '') {// 缓存中有位置信息,则从缓存中直接获取位置信息,并结束性能打点hiTraceMeter.finishTrace("CacheDataTime", 1);AlertDialog.show({message: 'AppStorage:' + cacheData,alignment: DialogAlignment.Center});} else {// 缓存中没有位置信息,则从接口获取位置信息this.apiGetLocation(PERMISSIONS, context);}}/*** 从getCurrentLocation接口获取位置信息。用户需要先授权。*/apiGetLocation(permissions: Array<Permissions>, context: common.UIAbilityContext): void {// 获取访问控制模块对象let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();// 拉起弹框请求用户授权。requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗atManager.requestPermissionsFromUser(context, permissions).then((data) => {// 获取相应请求权限的结果。 0表示已授权,否则表示未授权let grantStatus: Array<number> = data.authResults;let length: number = grantStatus.length;for (let i = 0; i < length; i++) {// 如果用户已授权模糊位置的权限,则调用getCurrentLocation获取位置信息,并保存到AppStorageif (data.permissions[i] === 'ohos.permission.APPROXIMATELY_LOCATION' && grantStatus[i] === 0) {// 设置位置请求参数let requestInfo: geoLocationManager.CurrentLocationRequest = {'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX, // 设置优先级信息。FIRST_FIX表示快速获取位置优先,如果应用希望快速拿到一个位置,可以将优先级设置为该字段。'scenario': geoLocationManager.LocationRequestScenario.UNSET // 设置场景信息。UNSET表示未设置场景信息。当scenario取值为UNSET时,priority参数生效,否则priority参数不生效;};try {// 获取当前位置geoLocationManager.getCurrentLocation(requestInfo).then((result) => {// 获取位置信息后,结束性能打点hiTraceMeter.finishTrace("ApiDataTime", 1);let locationData = JSON.stringify(result);// 保存到本地缓存AppStorage.setOrCreate(MYLOCATION, JSON.stringify(locationData));AlertDialog.show({message: 'getCurrentLocation:' + locationData,alignment: DialogAlignment.Center});}).catch((error: BusinessError) => {hilog.error(0x0000, "UseCacheInsteadAddressInquiry", `getCurrentLocation: error= ${error}`);});} catch (err) {hilog.error(0x0000, "UseCacheInsteadAddressInquiry", `err: ${err}`);}} else {// 如果用户未授权,提示用户授权。AlertDialog.show({message: '用户未授权,请到系统设置中打开应用的位置权限后再试。',alignment: DialogAlignment.Center});return;}}}).catch((err: BusinessError) => {hilog.error(0x0000, "UseCacheInsteadAddressInquiry", `failed to request permissions from user. Code is ${err.code} , message is ${err.message}`);})}build() {Column() {Button('clear cache').onClick(() => {// 清除AppStorage缓存中的位置信息this.myLocation = '';AlertDialog.show({message: 'cache cleared',alignment: DialogAlignment.Center});})}.height('100%').width('100%')}
}

效果对比

下面使用DevEco Studio内置的Profiler中的启动分析工具Launch,对使用getCurrentLocation获取地址数据及使用缓存获取地址数据的冷启动性能进行对比分析。本例中通过在aboutToAppear进行起始位置的性能打点,然后在使用本地缓存和使用getCurrentLocation获取到地址数据的位置分别进行结束位置的性能打点来分析两者的性能差异。对比性能前,需要先打开一次应用页面,在弹出位置信息授权弹窗时选择允许授权的选项。

优化前未使用本地缓存(通过getCurrentLocation获取地址数据)的测试步骤:先打开示例页面,点击'clear cache'按钮(清除本地位置信息的缓存)后退出应用,再使用Launch抓取性能数据。

图5 优化前未使用本地缓存

reasonable_using_cache_improve_performance_use_api

优化后使用本地缓存(通过PersistentStorage获取地址数据)的测试步骤:在使用getCurrentLocation获取地址数据后退出应用(本例中在getCurrentLocation获取地址数据数据后会保存到本地缓存),再使用Launch工具抓取性能数据。

图6 优化后使用本地缓存

reasonable_using_cache_improve_performance_use_cache

图5是优化前未使用本地缓存(从getCurrentLocation获取地址数据)的耗时,图6是优化后使用本地缓存(从PersistentStorage获取地址数据)的耗时,对比数据如下(性能耗时数据因设备版本环境而异,以实测为准):

方案阶段时长
(优化前)未使用本地缓存46ms
(优化后)使用本地缓存19μs

由此可见,在冷启动首页需要加载地址数据的场景中,先采用本地缓存策略获取地址数据相比调用getCurrentLocation接口,能显著缩短地址数据的获取时间,减少用户等待,提升冷启动完成时延性能与用户体验。

总结

应用性能优化中,合理使用缓存是提升体验的关键。通过将频繁请求的网络数据或位置信息等缓存起来,可以在下次启动时优先加载缓存数据,避免网络延迟或位置服务信号差导致的白屏或白块现象。这样不仅能减少页面数据加载时间,提升应用性能,还能显著改善用户体验。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。 

为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:


 鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)       

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线

这篇关于HarmonyOS开发实战( Beta5版)优化实践/合理使用缓存提升性能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121989

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k