HarmonyOS开发 - 本地持久化之实现LocalStorage支持多实例

用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。

在上一篇中,对Preference实例进行封装,实现LocalStorage相关功能,并在基础上再增加对json对象数据存储能力,还实现了数据缓存具有时效性等功能。地址:HarmonyOS开发 - 本地持久化之实现LocalStorage实例-CSDN博客

不过LocalStorage示例为单例模式,当在多个Ability或多module情况下,不希望所有数据都存储在一个Preferences实例中,这就需要LocalStorage有支持多实例的能力。

一、Ability和Module

在HarmonyOS开发中,Ability是应用或服务所具备的能力的抽象,它可以响应用户的操作,与用户进行交互。一个Module可以包含一个或多个Ability。HarmonyOS提供了两种应用模式:FA(Feature Ability)模型和Stage模型。

FA模型从API7开始支持,但已不再是主推的模型。而Stage模型从API 9开始新增,是目前主推且会长期演进的模型。在Stage模型中,Ability分为两种组件类型:

  1. UIAbility组件:包含UI界面,提供展示UI的能力,主要用于和用户交互。
  2. ExtensionAbility组件:提供特定场景的扩展能力,满足更多的使用场景。当前仅OpenHarmony工程支持使用ExtensionAbility组件。

在HarmonyOS中,应用可以设计为包含多个Ability或者多个module,这取决于应用的复杂性和模块化需求。

1.1 多个Ability

一个应用可以包含多个Ability,每个Ability代表应用的一种能力。Ability分为Feature Ability(FA)和Particle Ability(PA)两种类型。FA是有UI界面的能力,用于与用户交互,例如一个页面或一个服务。PA是后台运行的能力,可以是Service Ability或Data Ability,用于提供后台任务或数据访问的能力。在Stage模型中,Ability进一步细分为UIAbility组件和ExtensionAbility组件,其中UIAbility组件包含UI界面,而ExtensionAbility组件提供特定场景的扩展能力。

多个Ability

1.2 多个module

在HarmonyOS中,应用可以被分解为多个功能模块,每个模块负责执行特定的功能。这些模块可以是Shared Library、Static Library或Visual Library。Shared Library是HSP(Harmony Shared Package)动态共享包,Static Library是HAR(Harmony Archive)静态共享包。模块化设计提高了代码的可理解性和可复用性,使应用的扩展和维护变得更为简便,同时降低了系统各部分之间的耦合度。

多个Module

1.3 二者选择

对于多应用的情况,你可以选择使用多个Ability或多个module,这取决于你的应用架构和业务需求。如果应用功能较为独立,且需要在不同应用间共享,那么可能更适合使用多个module。每个module可以包含一个或多个Ability,这样可以在不同的module之间实现功能的解耦和复用。如果应用的功能较为集中,且主要通过不同的页面或服务来提供用户交互,那么使用多个Ability可能更为合适。

在实际开发中,你可以根据应用的特点和需求,灵活选择使用多个Ability或多个module,或者将两者结合起来,以实现最佳的应用架构和性能。

二、LocalStorage升级

在前一篇(地址:HarmonyOS开发 - 本地持久化之实现LocalStorage实例-CSDN博客)中封装的LocalStorage类,创建Preferences实例时指定的实例名称用的是Module模块名称(context.abilityInfo.moduleName);在了解Ability和Module关系后,决定将实例名称更新为Ability名称(context.abilityInfo.name)。

2.1 LocalStorage类

这里将LocalStorageObj文件复制一份,在新文件中增加多实例功能,同时applicationability和Productability的Ability界面和pages页面中,引入的模块地址也需要同步修改为:"../utils/LocalStorageMulti"。

这里只需要在之前LocalStorage类基础上,增加一个静态变量用于存储键值对的实例,和一个创建实例的静态函数即可。

  • 键值对的存储器(multiPreferences变量):利用Map对象的特性,键(key)为string类型,用于存储ability名称;值(value)为LocalStorge类型,用于存储类的实例对象。
  • 创建实例函数(multiInstance()函数):通过Map对象中has()方法判断该对应的ability界面实例是否存在,不存在则创建并返回创建的LocalStorage实例;若存在则直接返回该键对应的LocalStorage实例。

示例代码如下:

TypeScript 复制代码
import common from '@ohos.app.ability.common'
import preferences from '@ohos.data.preferences'
import { isJsonObject } from './utils'

// 定义存储值类型
type valueType = string | number | boolean
// 定义json对象存储类型
type dataType = { value: valueType | object, expire: number }

/**
 * 定义LocalStorage类
 */
export class LocalStorage {
  private preference: preferences.Preferences // 用户首选项实例对象
  // 定义多Preferences实例 存储变量字段
  private static multiPreferences: Map<string, LocalStorage> = new Map()

  /**
   * 创建多实例
   * @param context
   */
  static multiInstance(context?: common.UIAbilityContext): LocalStorage {
    const name = context.abilityInfo.name
    // 如果存在该实例,则直接返回
    if(LocalStorage.multiPreferences.has(name)) {
      console.log('testTag context.abilityInfo.name update', name)
      return LocalStorage.multiPreferences.get(name)
    }
    // 如果不存在,则创建实例
    else {
      console.log('testTag context.abilityInfo.name create', name)
      const instance = new LocalStorage()     // 实例LocalStorage对象
      instance.initial(context)               // 初始化Preferences实例
      // 存储LocalStorage对象
      LocalStorage.multiPreferences.set(name, instance)
      // 返回实例对象
      return instance
    }
  }

  // 定义初始化函数
  initial(context: common.UIAbilityContext): void {
    // 这里将UIAbility中应用上下文的name作用为实例名称,即该项目的ApplicationAbility或ProductAbility
    preferences.getPreferences(context, context.abilityInfo.name).then(preference => {
      this.preference = preference
      console.log('testTag', 'success~')
    }).catch(e => {
      console.log('testTag error', e)
    })
  }

  // 略...
}

注意:initial()函数中须将之前context.abilityinfo.moduleName修改为context.abilityinfo.name

2.2 Ability中初始化实例

在两个Ability中,引入LocalStorage模块,并初始化实例。由于multiinstance()为静态函数,通过LocalStorage类直接调用即可。

applicationability组件:

Productability组件:

2.3 page页面中获取实例

在HarmonyOS中,aboutToAppear是页面生命周期中的一个关键回调,它在创建自定义组件的新实例后,在执行其build()函数之前执行。这个回调允许开发者在组件实例创建后改变状态变量,并这些更改将在后续执行build()函数中生效。

aboutToAppear的使用场景包括但不限于:

  1. 初始化页面状态变量
  2. 执行数据绑定和事件监听的设置
  3. 执行与页面显示相关的动画或过渡效果

总的来说,aboutToAppear是HarmonyOS中管理页面生命周期的一个重要工具,它提供了页面显示前执行初始化操作的能力。而且多应用环境中,合理地使用多个Ability或module可以有效地组织和管理复杂的应用结构。

因此,我们将通过aboutToAppear来获取本Ability界面中的LocalStorage实例对象。

applicationability界面下的pages/index.ets示例代码如下:

TypeScript 复制代码
import { LocalStorage } from '../utils/LocalStorageMulti'
import common from '@ohos.app.ability.common'
import Want from '@ohos.app.ability.Want'

@Entry
@Component
struct Index {
  @State message: string = ''
  private localStorage: LocalStorage    // 定义变量,接收本Ability界面下的LocalStorage实例对象

  aboutToAppear(){
    // 获取当前上下文的LocalStorage实例对象
    this.localStorage = LocalStorage.multiInstance(getContext(this) as common.UIAbilityContext)
  }

  // 重置内容
  renderMsg(message: string | number | boolean){
    this.message = message.toString()
  }

  build() {
    Row() {
      Column() {
        Row(){
          Text(this.message || '-')
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
        }.width('100%').height('150vp')

        Row(){
          // 添加相关操作按钮
          Button('添加').onClick(() => {
            const testData = { name: 'Tom', age: 18 };
            const expireDate = new Date()
            // 设置为24小时后失效
            expireDate.setHours(expireDate.getHours() + 24)
            // 存储数据
            this.localStorage.put('indexValue', testData, expireDate)
            this.renderMsg('add:' + JSON.stringify(testData))
            console.log('testTag add', testData)
          })
          Button('读取').onClick(() => {
            this.localStorage.getValue('indexValue').then(value => {
              this.renderMsg('get:' +  (null !== value ? JSON.stringify(value) : value))
              console.log('testTag get', value)
            }).catch(err => {
              console.log('testTag error', err)
            })
          })
          Button('删除').onClick(async () => {
            this.localStorage.remove('indexValue')
            const value = await this.localStorage.getValue('indexValue')
            this.renderMsg('delete:' + value)
            console.log('testTag delete', value)
          })
          Button('跳转新页面').onClick(() => {
            const context = getContext(this) as common.UIAbilityContext
            const want: Want = {
              bundleName: 'com.example.myapplication',
              abilityName: 'ProductAbility',
              moduleName: 'application',
              parameters: {
                instanceKey: 'product'
              }
            }
            context.startAbility(want)
          })
        }.width('100%').justifyContent(FlexAlign.Center)
      }
      .width('100%')
    }
    .height('100%').alignItems(VerticalAlign.Top)
  }
}

对LocalStorage类进行多实例改造后,数据的存储能力依然能正常执行。如下图:

Productability界面下的pages/indexProduct.ets示例代码如下:

TypeScript 复制代码
import { LocalStorage } from '../utils/LocalStorageMulti'
import common from '@ohos.app.ability.common'
let index = 0

@Entry
@Component
struct Index {
  @State message: string = ''
  private localStorage: LocalStorage // 定义变量,接收本Ability界面下的LocalStorage实例对象

  aboutToAppear(){
    // 获取当前上下文的LocalStorage实例对象
    this.localStorage = LocalStorage.multiInstance(getContext(this) as common.UIAbilityContext)
  }

  // 重置内容
  renderMsg(message: string | number | boolean){
    this.message = message.toString()
  }

  build() {
    Row() {
      Column() {
        Row(){
          Text(this.message || '-')
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
        }.width('100%').height('150vp')

        Row(){
          // 添加相关操作按钮
          Button('添加').onClick(() => {
            this.localStorage.put('indexValue', ++index)
            this.renderMsg('add:' + index)
            console.log('testTag add', index)
          })
          Button('读取').onClick(() => {
            this.localStorage.getValue('indexValue').then(value => {
              this.renderMsg('get:' +  (null !== value ? JSON.stringify(value) : value))
              console.log('testTag get', value)
            }).catch(err => {
              console.log('testTag error', err)
            })
          })
          Button('删除').onClick(async () => {
            this.localStorage.remove('indexValue')
            const value = await this.localStorage.getValue('indexValue')
            this.renderMsg('delete:' + value)
            console.log('testTag delete', value)
          })
        }.width('100%').justifyContent(FlexAlign.Center)
      }
      .width('100%')
    }
    .height('100%').alignItems(VerticalAlign.Top)
  }
}

在Applicationability界面点击添加和读取后,再跳转到Productability界面,点击"读取"按钮,看是否能获取到Applicationability界面的indexValue键对应的值。如下图,显示是获取不到的,虽然两则都存的键为indexValue,但都存储在不能的Preferences实例中。

在Productability界面,点击添加后,再读取。结果如下图:

2.4 multiInstance()函数

在LocalStorage实现多实例功能时,对multiInstance()函数的设计,是可以无须先初始化实例,直接在页面中aboutToAppear回调函数并获取当前Ability界面实例。

此时我们将两个Ability中的onCreate()函数,执行的LocalStorage.multiInstance()删除掉,只在page页面直接获取,再看下结果。

如上图,初始化实例调用的函数注释掉后,结果是怎样的。

如上图,此时只执行了页面中的multiInstance()函数,但操作结果依然是正常的;不过注意的是,在aboutToAppear回调函数中只是初始化LocalStorage实例是没问题的,但是直接调用实例对象的put()、get()、remove()、clear()等函数,可能会报错,显示preference实例不在存,这是因为initial()函数中是通过Promise异步回调获取的preference实例的。

所以,是否需要在ability中执行初始化,还是在页面中执行初始化,根据实现需求而定。

2.5 总结

在多应用的环境中,你可能会使用多个Ability或多个module来组织你的应用。Ability允许你创建多个具有不同功能的页面或服务,而module则允许你将应用分解为多个功能模块,每个模块可以包含一个或多个Ability。这样的设计有助于提高代码的可维护性和可扩展性。

该篇是为上一篇(HarmonyOS开发 - 本地持久化之实现LocalStorage实例-CSDN博客)的补充内容,希望对大家有所帮助。

相关推荐
开心工作室_kaic19 分钟前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā20 分钟前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年2 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder2 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727572 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
SoaringHeart2 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
会发光的猪。2 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
鸿蒙自习室3 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
天下代码客3 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记3 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html