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

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

  • 一、前言
  • 二、逻辑封装的原则
  • 三、实现 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.mzph.cn/bicheng/65626.shtml

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

相关文章

python读写文件的三种做法

对于文件操作&#xff0c;python提供了3种做法&#xff1a;open(), os.open() 和with open()语句。 1. open()函数&#xff1a;一般用于更高级的文件读写操作&#xff0c;即人能读懂的用法&#xff0c;如果是写入数据&#xff0c;可用传入字符串。 用法&#xff1a;open(path…

MySQL如何只取根据某列连续重复行的第一条记录

前言 MySQL如何只取根据某列连续重复行的第一条记录&#xff0c;条件&#xff1a;某列、连续、验重 建表准备 DROP TABLE IF EXISTS test; CREATE TABLE test (id bigint NOT NULL,time datetime NULL DEFAULT NULL,price int NULL DEFAULT NULL,PRIMARY KEY (id) USING BT…

Fetch处理大模型流式数据请求与解析

为什么有的大模型可以一次返回多个 data&#xff1f; Server-Sent Events (SSE)&#xff1a;允许服务器连续发送多个 data: 行&#xff0c;每个代表一个独立的数据块。 流式响应&#xff1a;大模型服务通常以流式响应方式返回数据&#xff0c;提高响应速度。 批量处理&#x…

MySQL 中存储金额数据一般使用什么数据类型

在 MySQL 中存储金额数据时&#xff0c;应该谨慎选择数据类型&#xff0c;以确保数据的精度和安全性。以下是几种常用的数据类型及其适用性&#xff1a; DECIMAL 类型&#xff1a; 描述&#xff1a;DECIMAL 类型是专门为存储精确的小数而设计的。它可以指定小数点前后的数字位数…

【数据结构】链表(1):单向链表和单向循环链表

链表 链表是一种经典的数据结构&#xff0c;它通过节点的指针将数据元素有序地链接在一起&#xff0c;在链表中&#xff0c;每个节点存储数据以及指向其他节点的指针&#xff08;或引用&#xff09;。链表具有动态性和灵活性的特点&#xff0c;适用于频繁插入、删除操作的场景…

离散数学考前一天

判断强连通&#xff0c;单向连通&#xff0c;弱连通&#xff1a; 求可达性矩阵P&#xff0c;P里面全是1&#xff0c;就是强连通 否则看P与P的转置矩阵&#xff0c;如果除了主对角线是0&#xff0c;其他全是1&#xff0c;就是单向连通 否则看A1&#xff1d;A与A的转置矩阵&am…

【服务器项目部署】⭐️将本地项目部署到服务器!

目录 &#x1f378;前言 &#x1f37b;一、服务器选择 &#x1f379; 二、服务器环境部署 2.1 java 环境部署 2.2 mysql 环境部署 &#x1f378;三、项目部署 3.1 静态页面调整 3.2 服务器端口开放 3.3 项目部署 ​ &#x1f379;四、测试 &#x1f378;前言 小伙伴们大家好…

chrome缓存机制以及验证缓存机制

一、Chrome 缓存机制 浏览器缓存机制旨在提高网页加载速度、减少服务器负载和节约带宽。Chrome 的缓存主要包括以下几种类型&#xff1a; 1. 强缓存 (Strong Cache) 无需向服务器发送请求即可使用缓存的资源。由 HTTP 响应头控制&#xff0c;包括&#xff1a; Expires&…

西门子DBX DBD DBB DBW的关系

DB10.DBD0 DB10.DBW0DB10.DBW2 DB10.DBB0DB10.DBB1DB10.DBB2DB10.DBB3 DB10.DBX0.00.7DB10.DBX1.01.7DB10.DBX2.02.7DB10.DBX3.03.7 使用之前需要在DB10中先定义&#xff0c;如果你仅在DB10中定义了一个DBD0&#xff0c;那么原则上你是可以使用上述所有地址的&#xff0c;但…

Android `android.graphics` 包深度解析:架构与设计模式

Android android.graphics 包深度解析:架构与设计模式 目录 引言android.graphics 包概述核心类与架构 CanvasPaintBitmapColorPathShaderMatrix设计模式在 android.graphics 中的应用 工厂模式装饰者模式策略模式享元模式高级图形处理技术 硬件加速离屏渲染自定义 View 中的…

Nginx的性能分析与调优简介

Nginx的性能分析与调优简介 一、Nginx的用途二、Nginx负载均衡策略介绍与调优三、其他调优方式简介四、Nginx的性能监控 一、Nginx的用途 ‌Nginx是一种高性能的HTTP和反向代理服务器&#xff0c;最初作为HTTP服务器开发&#xff0c;主要用于服务静态内容如HTML文件、图像、视…

vue2使用pdfjs-dist和jsPDF生成pdf文件

vue2使用pdfjs-dist和jsPDF生成pdf文件 1、安装依赖 npm install pdfjs-dist2.6.3472、引入依赖 import { jsPDF } from jspdf// 使用require方式导入pdfjs-dist v2.6.347&#xff0c;高版本报错&#xff08;import导入会报错&#xff1a;GlobalWorkerOptions undefined&…

sklearn_pandas.DataFrameMapper的用法

文章目录 介绍主要作用基本用法示例对不同列应用不同的转换器对多列应用相同的转换器输出为 Pandas DataFrame 注意事项转换器的适用性&#xff1a;输出格式&#xff1a;与 scikit-learn 的兼容性&#xff1a; 介绍 DataFrameMapper 是 sklearn-pandas 库中的一个工具&#xf…

HTML——31.定义媒介资源

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>定义媒介资源</title></head><body><!--source标签用来为<video>视频和<audio>音频 &#xff0c;定义媒介资源--><!--src属性&…

宝塔-firefox(Docker应用)-构建自己的Web浏览器

安装基础软件 宝塔中安装firefox(Docker应用) 。宝塔中需要先安装docker及docker-composefirefox配置安装 点击firefox应用&#xff0c;选择【安装配置】点击右边绿色按钮&#xff0c;进行安装&#xff0c;这一步等待docker-compose根据你的配置初始化docker应用 等待安装 …

ArcGIS土地利用数据制备、分析及基于FLUS模型土地利用预测(数据采集、处理、分析、制图)

FLUS&#xff08;Flexible Land Use Simulation&#xff09;模型是一个用于模拟土地利用变化的模型&#xff0c;它结合了经济理论、土地利用和土地覆盖变化的动态过程。FLUS模型由美国农业部农业经济研究服务局&#xff08;ERS&#xff09;开发&#xff0c;旨在提供对美国及全球…

【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(一)

****非斜体正文为原文献内容&#xff08;也包含笔者的补充&#xff09;&#xff0c;灰色块中是对文章细节的进一步详细解释&#xff01; 三、传统微调范式&#xff08;Traditional Fine-Tuning Paradigm&#xff09; 在这个范式中&#xff0c;首先在大量未标记的文本数据上预…

【泰克生物】从酵母细胞表面展示到抗体筛选:实现精准药物发现

在现代药物发现领域&#xff0c;精准筛选和优化抗体已成为一种必不可少的技术手段。传统的抗体筛选方法依赖于动物免疫或体外筛选&#xff0c;这些方法往往成本高且周期长。近年来&#xff0c;酵母细胞表面展示技术&#xff08;Yeast Surface Display, YSD&#xff09;成为一种…

C++ 设计模式:门面模式(Facade Pattern)

链接&#xff1a;C 设计模式 链接&#xff1a;C 设计模式 - 代理模式 链接&#xff1a;C 设计模式 - 中介者 链接&#xff1a;C 设计模式 - 适配器 门面模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供一个一致&#…

基于YOLOV5+Flask安全帽RTSP视频流实时目标检测

1、背景 在现代工业和建筑行业中&#xff0c;安全始终是首要考虑的因素之一。特别是在施工现场&#xff0c;工人佩戴安全帽是确保人身安全的基本要求。然而&#xff0c;人工监督难免会有疏漏&#xff0c;尤其是在大型工地或复杂环境中&#xff0c;确保每个人都佩戴安全帽变得非…