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

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

  • 一、前言
  • 二、逻辑封装的原则
  • 三、实现 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 的相互配合,达到功能的实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《机器学习》——线性回归模型

文章目录 线性回归模型简介一元线性回归模型多元线性回归模型误差项分析一元线性模型实例完整代码 多元线性模型实例完整代码 线性回归模型简介 线性回归是利用数理统计中回归分析&#xff0c;来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。 相关关系&…

【深度学习环境】NVIDIA Driver、Cuda和Pytorch(centos9机器,要用到显示器)

文章目录 一 、Anaconda install二、 NIVIDIA driver install三、 Cuda install四、Pytorch install 一 、Anaconda install Step 1 Go to the official website: https://www.anaconda.com/download Input your email and submit. Step 2 Select your version, and click i…

在HTML中使用Vue如何使用嵌套循环把集合中的对象集合中的对象元素取出来(我的意思是集合中还有一个集合那种)

在 Vue.js 中处理嵌套集合&#xff08;即集合中的对象包含另一个集合&#xff09;时&#xff0c;使用多重 v-for 指令来遍历这些层次结构。每个 v-for 指令可以用于迭代一个特定级别的数据集&#xff0c;并且可以在模板中嵌套多个 v-for 来访问更深层次的数据。 例如&#xff…

ip归属地是什么意思?ip归属地是实时定位吗

在数字化时代&#xff0c;IP地址作为网络设备的唯一标识符&#xff0c;不仅关乎设备间的通信&#xff0c;还涉及到用户的网络身份与位置信息。其中&#xff0c;IP归属地作为IP地址的地理位置信息&#xff0c;备受用户关注。本文将详细解析IP归属地的含义&#xff0c;并探讨其是…

基于BP训练深度学习模型(用于回归)以及验证误差值

用原生Python训练了一个BP网络&#xff0c;适合没有pytorch等环境的电脑&#xff0c;并用训练的模型对原始数据进行了预测&#xff0c;拿来估测比较误差值了&#xff0c;可以直接拿去用&#xff08;需根据个人数据来调训练次数、学习效率&#xff09;&#xff0c;代码在文章末。…

C#冒泡排序

一、冒泡排序基本原理 冒泡排序是一种简单的排序算法。它重复地走访要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排序完成。 以一个简单的整数数…

折腾日记:如何让吃灰笔记本发挥余热——搭建一个相册服务

背景 之前写过&#xff0c;我在家里用了一台旧的工作站笔记本做了服务器&#xff0c;连上一个绿联的5位硬盘盒实现简单的网盘功能&#xff0c;然而&#xff0c;还是觉的不太理想&#xff0c;比如使用filebrowser虽然可以备份文件和图片&#xff0c;当使用手机使用网页&#xf…

从0入门自主空中机器人-2-1【无人机硬件框架】

关于本课程&#xff1a; 本次课程是一套面向对自主空中机器人感兴趣的学生、爱好者、相关从业人员的免费课程&#xff0c;包含了从硬件组装、机载电脑环境设置、代码部署、实机实验等全套详细流程&#xff0c;带你从0开始&#xff0c;组装属于自己的自主无人机&#xff0c;并让…

剑指Offer|LCR 013. 二维区域和检索 - 矩阵不可变

LCR 013. 二维区域和检索 - 矩阵不可变 给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总和&#xff0c;该子矩阵的左上角为 (row1, col1) &#xff0c;右下角为 (row2, col2) 。 实现 NumMatrix 类&#xff1a; NumMatrix(…

接口Mock技术介绍

相信学习过程序设计的读者朋友们&#xff0c;一定对“桩&#xff08;Stub&#xff09;”这个概念并不陌生。它是指用来替换一部分功能的程序代码段。桩程序代码段可以用来模拟已有程序的某些功或者是将实现的系统代码的一种临时替代方法。插桩方法被广泛应用于开发和测试工作中…

深入解析C#异步编程:await 关键字背后的实现原理

在C#中&#xff0c;async 和 await 关键字用于编写异步代码。本文将详细介绍 await 的实现原理&#xff0c;包括状态机的生成、回调函数的注册和触发等关键步骤。 1. 异步方法的基本概念 在C#中&#xff0c;async 关键字标记一个方法为异步方法&#xff0c;而 await 关键字用于…

【机器学习】SVM支持向量机(一)

介绍 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是一种监督学习模型&#xff0c;广泛应用于分类和回归分析。SVM 的核心思想是通过找到一个最优的超平面来划分不同类别的数据点&#xff0c;并且尽可能地最大化离该超平面最近的数据点&#xff08;支持向量…

Unity功能模块一对话系统(1)前置准备

也许你也曾被游戏中的对话系统深深吸引&#xff0c;那些精心设计的对白、鲜活的角色配音、甚至是简单的文字对话&#xff0c;往往能让玩家产生强烈的代入感和情感共鸣。如果你正在开发一款游戏&#xff0c;或者计划为你的项目加入一个引人入胜的对话系统&#xff0c;那么 Unity…

upload-labs关卡记录10

白名单&#xff0c;可以看到已经进行了限制&#xff0c;只能上传这三种后缀的文件&#xff0c;先试一试MIME绕过&#xff1a; 果然不行&#xff1a;观察到是post型&#xff0c;试一试00绕过&#xff1a;没找到路径&#xff0c;绕过失败。 看源码吧&#xff1a; $is_upload f…

专业140+总分410+南京大学851信号与系统考研经验南大电子信息通信集成电路,真题,大纲。参考书。

本人本科中等211&#xff0c;离保送本校差一点&#xff0c;考研前纠结本校还是追求更高目标&#xff0c;和家人聊了自己的想法&#xff0c;感谢父母对我的支持&#xff0c;坚定报考南大的目标&#xff0c;最终专业851信号与系统140&#xff0c;总分410顺利被南京大学录取&#…

《机器学习》——KNN算法

文章目录 KNN算法简介KNN算法——sklearnsklearn是什么&#xff1f;sklearn 安装sklearn 用法 KNN算法 ——距离公式KNN算法——实例分类问题完整代码——分类问题 回归问题完整代码 ——回归问题 KNN算法简介 一、KNN介绍 全称是k-nearest neighbors&#xff0c;通过寻找k个距…

Spring Boot 学习笔记

学习代码第一步&#xff1a;如何写 Hello world &#xff1f; 1、新建项目 新建一个 Maven Java 工程&#xff0c;在 pom.xml 文件中添加 Spring Boot Maven 依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spri…

基于python的扫雷游戏

游戏 游戏目标&#xff1a; 揭开所有非地雷的格子。 如果揭开地雷&#xff0c;游戏失败。 使用标记功能&#xff08;&#x1f6a9;&#xff09;来标记可能的地雷位置。 格子类型&#xff1a; 空白格子&#xff1a;表示周围没有地雷。 数字格子&#xff1a;显示周围 8 个格子…

【K8S系列】深入解析K8S服务的无状态与有状态

在容器编排领域&#xff0c;Kubernetes&#xff08;K8S&#xff09;无疑是占据主导地位的工具。它提供了强大的功能来管理和部署容器化应用程序&#xff0c;其中服务分类是理解和有效使用K8S的关键。K8S中的服务主要分为无状态服务和有状态服务&#xff0c;这两种类型在基础概念…

Linux第100步_Linux之设置LCD作为终端控制台和LCD背光调节

KMS是Kemmel Mode Setting的缩写&#xff0c;内核显示模式设置。它主要负责显示的控制&#xff0c;包括屏幕分辨率、屏幕刷新率和颜色深度等等。 CRTC是指显示控制器&#xff0c;在DRM里有多个显存&#xff0c;通过操作CRTC来控制要显示那个显存。 KMS包含了FB框架。DRM驱动默…