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

相关文章

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

Redis与缓存解读

《Redis与缓存解读》文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步... 目录缓存缓存优缺点缓存更新策略超时剔除先删缓存再更新数据库旁路缓存(先更新数据库,再删缓存)先更新数

Jsoncpp的安装与使用方式

《Jsoncpp的安装与使用方式》JsonCpp是一个用于解析和生成JSON数据的C++库,它支持解析JSON文件或字符串到C++对象,以及将C++对象序列化回JSON格式,安装JsonCpp可以通过... 目录安装jsoncppJsoncpp的使用Value类构造函数检测保存的数据类型提取数据对json数

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

springboot整合 xxl-job及使用步骤

《springboot整合xxl-job及使用步骤》XXL-JOB是一个分布式任务调度平台,用于解决分布式系统中的任务调度和管理问题,文章详细介绍了XXL-JOB的架构,包括调度中心、执行器和Web... 目录一、xxl-job是什么二、使用步骤1. 下载并运行管理端代码2. 访问管理页面,确认是否启动成功

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —