从零开始开发纯血鸿蒙应用之逻辑封装

从零开始开发纯血鸿蒙应用

  • 一、前言
  • 二、逻辑封装的原则
  • 三、实现 FileUtil
    • 1、统一的存放位置
    • 2、文件的增删改查
      • 2.1、文件创建与文件保存
      • 2.2、文件读取
      • 2.2.1、读取内部文件
      • 2.2.2、读取外部文件
    • 3、文件删除
  • 四、总结

一、前言

应用的动态,借助 UI 响应完成,所谓 UI响应,就是指对用户操作的回应。通常,UI 响应可分为纯逻辑响应和内容刷新两大类型,前者指用户触发动作发生后,不会在应用页面上有任何改变,而后者往往会产生页面内容的更新。

简单的UI响应,处理代码可以直接放在组件的事件处理方法中,鸿蒙UI组件支持的通用事件有如下:
在这里插入图片描述
复杂的响应处理,也即代码量比较多,无法通过只调用一个系统API来完成的,就需要封装在另外的方法体中,然后将对应的方法以函数参数的形式传入组件的事件处理函数中,这时候就涉及到逻辑封装了。

二、逻辑封装的原则

与 UI 封装一样,逻辑封装也有相应的原则必须遵守。于我而言,原则之一就是,能与UI实现文件独立的,就不要杂糅在 Component struct 里面,除非涉及到更新UI内容。用独立文件进行封装时,最好搞成工具类的形式,将负责相同类型处理的代码,统一放置在相同的 ets 文件中,方便进行源码管理,比如,在本工程中用到的文件读写处理,我就是专门放在了 lib_util模块的 FileUtil 中。

封装工具类的时候,应当将实现方法以静态方法的形式对外提供,所以,有必要将 类构造函数私有化,即如下:
在这里插入图片描述
要知道,很多UI响应处理都是面向过程的,因而面向对象的那一套类体实现,就不要采用了,即便类体里面存在某些需要初始化操作的字段,也应该有同样是静态方法的 init 方法去实现。

最后,每个工具类的每个方法都应该有函数头注释和日志打印,函数头注释要采用文档类型,这样在其他ets 文件中进行使用的时候,才能通过鼠标悬停获知说明信息:
在这里插入图片描述
在这里插入图片描述

三、实现 FileUtil

正如第一篇所说,本工程旨在实现一个支持通用纯文本文件浏览和编辑的纯血鸿蒙应用,因此,文件的读写操作,在本工程里面是重中之重的,下面就分享一下我在实现 FileUtil 过程中的一些考量。

1、统一的存放位置

在应用内创建的纯文本格式的文件,不论文件后缀为何,我都是统一放在沙箱目录 fileDir下的 docs 文件夹中,如此一来,便可以降低获取文件名列表的方法的复杂性。

在鸿蒙API中,允许根据指定目录和指定文件后缀,去获取文件名列表,例如本工程里面实现的 getFileNameList 方法:

 /*** 获取文件名列表* @param ctx 上下文* @returns 返回指定目录下的纯文本文件的文件名列表*/static getFileNameList(ctx: common.UIAbilityContext) {const prefix: string = ctx.filesDir;const dir: string = DirectoryConstants.DOCUMENT_PATH;const path: string = `${prefix}/${dir}`;const option: ListFileOptions = {recursion: false,listNum: 0,filter: {suffix: ['.txt', '.log', '.csv', '.ini', '.conf', '.md', '.markdown', '.rtf', '.json','.xml', '.ets', '.java', '.py','.c', '.cpp', '.h', '.html']}}return fs.listFileSync(path, option)}

应用沙箱路径可以借助 UI 上下文进行获取,所以,方法参数就是一个 UI 上下文。将文件直接放在应用沙箱的一级目录,如 file 目录下,是不明智的,所以,必须另辟一个子目录进行存放,而子目录名可以记录在 lib_constants 中。

2、文件的增删改查

就像数据库一样,文件也是可以增删改查的。

2.1、文件创建与文件保存

首先,看一下文件的新增和修改:

/*** 保存文本数据到文件* @param ctx 上下文* @param data 待保存的数据* @param fileName 目标文件名* @returns 是否写入成功*/static async saveToFile(ctx: common.UIAbilityContext, data: string, fileName: string): Promise<number> {const prefix: string = ctx.filesDir;const dir: string = DirectoryConstants.DOCUMENT_PATH;const path: string = `${prefix}/${dir}/${fileName.trimEnd()}`;const file = fs.openSync(path, fs.OpenMode.READ_WRITE|fs.OpenMode.CREATE);return new Promise((resolve, reject) => {fs.write(file.fd, data).then((writeLen) => {Logger.info(`write ${writeLen} bytes to ${path}`, TAG)resolve(writeLen);}).catch((err: BusinessError) => {Logger.error(`write file failed, ${err.message}`, TAG)reject(err);}).finally(() => {fs.closeSync(file)})})}

考虑到 IO 操作通常都比较慢,所以,采用异步方法的形式进行实现,为了保障做到文件不存在时创建、存在时就写入,需要将文件以 fs.OpenMode.READ_WRITE|fs.OpenMode.CREATE 打开。

为了减少重复的目录存在性判断代码,我在 entry 模块的 util 目录下的 EntryUtil 中,专门用一个 createDirectory 方法负责目录的创建:

 static createDirectoryDocs() {if (EntryUtil.context) {const prefix = EntryUtil.context.filesDir;const docsLocator = `${prefix}/${DirectoryConstants.DOCUMENT_PATH}`;if (fs.accessSync(docsLocator)) {Logger.info(`${docsLocator}已存在`, TAG);} else {fs.mkdir(docsLocator).then(() => {Logger.info(`${docsLocator}创建成功`, TAG);}).catch((err: BusinessError) => {Logger.error(`${docsLocator}创建失败: ${err.message}`, TAG);})}} else {throw new Error("context is not init");}}

并在 EntryAbility 的 onCreate 方法中调用。

回到 saveToiFile 方法,该方法会返回一个 Promise<number> 对象,这是 Typescript 或者说 Javascript 中,专门为异步方法提供的返回值类型;当文件内容成功写入目标文件中时,会将写入的字节数通过 resolve 回调函数返回给调用者,而如果写入失败,则用 reject 回调函数抛出错误。

由于是文件写入操作,所以,除了 UI 上下文外,还需要文件名和文件内容

2.2、文件读取

文件读取分为读取内部文件和外部文件两种,并且针对性地封装了相应的方法。对于内部文件,即在本应用中创建的文件,只需传入一个文件名即可,而对于外部文件、即其他应用通过系统的文件分享功能传入的文件,就需要传入完整的 file uri 才能打开。

2.2.1、读取内部文件

首先,看一下内部文件的读取实现代码:

/*** 读取文件内容* @param ctx 上下文* @param fileName 文件名* @returns 文件内容*/static async getFromFile(ctx: common.UIAbilityContext, filename: string): Promise<string>{const prefix: string = ctx.filesDir;const dir: string = DirectoryConstants.DOCUMENT_PATH;const path: string = `${prefix}/${dir}/${filename}`;Logger.info(`read file from ${path}`, TAG)return new Promise((resolve, reject) => {if (fs.accessSync(path)) {const stat = fs.statSync(path);if (stat.size > 0) {const readTextOption: ReadTextOptions = {offset: 1,length: stat.size,encoding: 'utf-8'};fs.readText(path, readTextOption).then((data) => {Logger.info(`read ${data.length} bytes from ${path}`, TAG)resolve(data);}).catch((err: BusinessError) => {Logger.error(`read file failed, ${err.message}`, TAG)reject(err);})} else {resolve("");}} else {reject(`${filename} is not exist!`);}})}

一样采用异步方法的形式进行实现,在处理逻辑中,会先判断文件的存在性,如果不存在则抛错。接着利用 fs.statSync(path) 去获取文件信息,如文件大小等,该 API 的官方说明如下:
在这里插入图片描述
而 Stat 对象的组成如下:

  • ino:文件标识,通常同设备上的不同文件的INO不同。
  • mode:文件权限。
  • uid:文件所有者的ID。
  • gid:文件所有组的ID。
  • size:文件的大小,以字节为单位。仅对普通文件有效。
  • atime:上次访问该文件的时间,表示距1970年1月1日0时0分0秒的秒数。
  • mtime:上次修改该文件的时间,表示距1970年1月1日0时0分0秒的秒数。
  • ctime:最近改变文件状态的时间,表示距1970年1月1日0时0分0秒的秒数。
  • location:文件的位置,表示该文件是本地文件或者云端文件。

有了文件的 Stat 信息后,就可以利用其中的 size 字段,去设置 ReadTextOptions,该 option 是 readText 方法所必传的,readText 方法官方说明如下:
在这里插入图片描述
在这里插入图片描述

成功读取,则将文件内容通过 resolve 回调函数外传。

2.2.2、读取外部文件

外部文件的读取实现,代码如下:

/*** 读取其他应用分享的文件* @param fileUri* @returns*/static async readExternalFile(fileUri: string): Promise<string> {return new Promise((resolve, reject) => {const file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);if (file) {Logger.info(`success open file: ${file.path}`, TAG);const fileStat = fs.statSync(file.fd);Logger.info(`file size: ${fileStat.size}`, TAG);const buf: ArrayBuffer = new ArrayBuffer(fileStat.size);fs.read(file.fd, buf).then((readLen) => {if (readLen > 0) {const decoder = util.TextDecoder.create('utf-8');const content = decoder.decodeToString(new Uint8Array(buf));resolve(content);} else {reject(new Error("read file failed"))}}).then(() => {reject(new Error("read file failed"))}).finally(() => {fs.closeSync(file);})} else {Logger.error(`open file failed: ${fileUri}`);reject(new Error("open file failed"))}})}

大致上和内部文件的读取实现相同,除了参数只需传入 file uri 和使用 fs.read API 外。

fs.read 方法读取文件时,会将内容读取到一个 ArrayBuffer 中,所以,在利用 resolve 回调外传文件内容前,需要 ArrayBuffer 进行转码操作,将其转成 string 类型。

3、文件删除

在鸿蒙框架中,文件删除是通过调用 fileIo 的 unlink 或 unlinkSync 实现的,从方法名就可以看出,文件的删除实际上,只是将原本指向文件所在存储区域的指针或者链接,进行摘除和悬空,并非是将对应的存储区域用二进制零进行覆盖。

在这里插入图片描述

四、总结

其他的功能逻辑的封装,基本上跟 FileUtil 的封装大同小异,都是通过一组系统 API 的相互配合,达到功能的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/498408.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0开始的opencv之旅(1)cv::Mat的使用

目录 Mat 存储方法 创建一个指定像素方式的图像。 尽管我们完全可以把cv::Mat当作一个黑盒&#xff0c;但是笔者的建议是仍然要深入理解和学习cv::Mat自身的构造逻辑和存储原理&#xff0c;这样在查找问题&#xff0c;或者是遇到一些奇奇怪怪的图像显示问题的时候能够快速的想…

(一)开发环境搭建以及配置

文章目录 Vmware安装Ubuntu的搭建Ubuntu常规配置换源更新源安装open-vm-tools Samba服务器安装第一步安装第二步 建立共享文件夹第三步配置 Samba 文件 以及 设置Samba用户密码第四步重启 Samba 服务器第五步Windows 和 Ubuntu 如何借助Samba互传 建立虚拟机自带的共享文件夹建…

闲谭Scala(2)--安装与环境配置

1. 概述 Java开发环境安装&#xff0c;需要两步&#xff0c;第一安装JDK&#xff0c;第二配置环境变量。 Scala的话&#xff0c;也是两步&#xff0c;第一安装Scale环境&#xff0c;第二配置环境变量。 需要注意的是&#xff0c;配置环境变量&#xff0c;主要是想让windows操…

MySQL语句学习第二篇_数据库

MySQL语句学习第三篇_数据库 专栏记录MySQL的学习&#xff0c;感谢大家观看。 本章的专栏&#x1f4da;➡️MySQL语法学习 本博客前一章节指向➡️MySQL语句学习第一篇 本人的博客➡️:如烟花般绚烂却又稍纵即逝的主页 目录 MySQL是什么&#xff1f;关于数据库的基础操作MySQL…

基于ArcGIS Pro的SWAT模型在流域水循环、水生态模拟中的应用及案例分析;SWAT模型安装、运行到结果读取全流程指导

目前&#xff0c;流域水资源和水生态问题逐渐成为制约社会经济和环境可持续发展的重要因素。SWAT模型是一种基于物理机制的分布式流域水文与生态模拟模型&#xff0c;能够对流域的水循环过程、污染物迁移等过程进行精细模拟和量化分析。SWAT模型目前广泛应用于流域水文过程研究…

太速科技-519-基于ZU19EG的4路100G光纤的PCIe 加速计算卡

基于ZU19EG的4路100G光纤的PCIe 加速计算卡 一、板卡概述 本板卡系我司自主设计研发&#xff0c;基于Xilinx公司Zynq UltraScale MPSOC系列SOC XCZU19EG-FFVC1760架构&#xff0c;支持PCIE Gen3x16模式。其中&#xff0c;ARM端搭载一组64-bit DDR4&#xff0c;总容量达…

一个C#开发的APP

开发方式 C#Web、AndroidWebView 系统设计 系统主要分两个部分。一个是内容&#xff08;文章&#xff09;发布系统&#xff0c;另一个是预约和支付系统。 内容发布系统 和普通的文章发布系统不一样的地方在于&#xff0c;我们把每篇文章和大师关联起来。在文章的下方会显示…

【LLM】Langflow 的简单使用

(PS&#xff1a;爆肝整理&#xff0c;请不要吝啬你的点赞和收藏。) 什么是 Langflow &#xff1f;Langflow 是一种用于构建多智能体和RAG应用的可视化框架。它提供了个无需编码的 AI 生态系统&#xff0c;能够无缝集成各种常用工具和技术栈。Langflow 以 Python 为基础&#x…

linux自动化批量分发SSH密钥同时批量测试SSH连接教程(包含自动化脚本代码)

1、检查端口 检查分发对象22端口是否打开 nmap -p22 ip地址如果要批量检查端口可以参考我写的这篇文章&#xff1a;linux自动化一键批量检查主机端口 2、命令行分发密钥原理 Linux分发密钥原理主要涉及SSH&#xff08;Secure Shell&#xff09;协议&#xff0c;该协议用于…

Ubuntu 下使用命令行将 U 盘格式化为 ext4、FAT32 和 exFAT 的详细教程

Ubuntu 下使用命令行将 U 盘格式化为 ext4、FAT32 和 exFAT 的详细教程 作者&#xff1a;Witheart更新时间&#xff1a;20241228 本教程将详细介绍如何将 U 盘格式化为 ext4、FAT32 和 exFAT 文件系统&#xff0c;同时包括如何安装必要工具&#xff08;如 exfat-utils&#x…

【漫话机器学习系列】028.CP

Mallows’ Cp&#xff1a;标准化公式解析与应用 Mallows’ Cp 是一种常用的模型选择工具&#xff0c;用于在一系列候选模型中权衡拟合度和复杂性&#xff0c;帮助我们选择性能最优的模型。本文将基于其标准化公式展开详细解析&#xff0c;并探讨其应用场景、实现方法、优点与局…

Python编程技术

设计目的 该项目框架Scrapy可以让我们平时所学的技术整合旨在帮助学习者提高Python编程技能并熟悉基本概念&#xff1a; 1. 学习基本概念&#xff1a;介绍Python的基本概念&#xff0c;如变量、数据类型、条件语句、循环等。 2. 掌握基本编程技巧&#xff1a;教授学生如何使…

论文阅读《Cross-scale multi-instance learning for pathological image diagnosis》

From&#xff1a;2024 MIA CS-MIL GitHub&#xff1a;https://github.com/hrlblab/CS-MIL 一、Abstract&#xff1a; 在数字病理学中&#xff0c;分析高分辨率全幻灯片图像&#xff08;WSIs&#xff09;时涉及多个尺度的信息是一个重大挑战。多实例学习&#xff08;MIL&#x…

短视频平台的视频水印怎么去除?

当你看到某个短视频&#xff0c;觉得内容非常有价值&#xff0c;想要个人收藏以便日后学习或回顾&#xff0c;但发现短视频平台无法直接下载且带有水印时&#xff0c;以下提供的几种方法将帮助你轻松去除水印&#xff0c;获取高清无水印的视频内容。 方法一&#xff1a;使用第…

【Redis】Redis 典型应用 - 缓存 (cache)

目录 1. 什么是缓存 2. 使用 Redis 作为缓存 3. 缓存的更新策略 3.1 定期生成 3.2 实时生成 4. 缓存的淘汰策略 5. 缓存预热, 缓存穿透, 缓存雪崩 和 缓存击穿 关于缓存预热 (Cache preheating) 关于缓存穿透 (Cache penetration) 关于缓存雪崩 (Cache avalanche) 关…

解决springdoc-openapi-ui(Swagger3)跳转默认界面问题

文章目录 问题现象解决方法 问题现象 项目正确引入springdoc-openapi-ui依赖&#xff0c;但是访问/swagger-ui/index.html界面时&#xff0c;跳转到了默认的界面&#xff0c;如下图所示&#xff1a; 解决方法 1、升级maven依赖为1.8.0以上&#xff1a; <dependency>…

绝美的数据处理图-三坐标轴-散点图-堆叠图-数据可视化图

clc clear close all %% 读取数据 load(MyColor.mat) %读取颜色包for iloop 1:25 %提取工作表数据data0(iloop) {readtable(data.xlsx,sheet,iloop)}; end%% 解析数据 countzeros(23,14); for iloop 1:25index(iloop) { cell2mat(table2array(data0{1,iloop}(1,1)))};data(i…

HALCON中用于分类的高斯混合模型create_class_gmm

目录 一、创建用于分类的高斯混合模型函数二、代码和效果展示三、相关函数 一、创建用于分类的高斯混合模型函数 create_class_gmm( : : NumDim, NumClasses, NumCenters, CovarType, Preprocessing, NumComponents, RandSeed : GMMHandle)create_class_gmm创建用于分类的高斯…

lua-debug for Sublime

目标 Sublime 也支持 lua-debug&#xff0c;操作体验与 VSCode 一致。 优势 执行效率高&#xff0c;不掉帧 可随时开启 配置简单&#xff0c;一份配置兼容 VSCode 和 Sublime 安装 要求 Sublime 4 的版本&#xff08;注&#xff1a;从 Sublime 3 升到 4 的不算&#xff0c;…

Kafka消息不丢失与重复消费问题解决方案总结

1. 生产者层面 异步发送与回调处理 异步发送方式&#xff1a;生产者一般使用异步方式发送消息&#xff0c;异步发送有消息和回调接口两个参数。在回调接口的重写方法中&#xff0c;可通过异常参数判断消息发送状态。若消息发送成功&#xff0c;异常参数为null&#xff1b;若发…