HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences)

news2025/5/25 18:24:18

在应用开发中存储一些配置是很常见的需求。在android中有SharedPreferences,一个轻量级的存储类,用来保存应用的一些常用配置。在HarmonyOS鸿蒙应用开发中,实现类似功能的也叫首选项,dataPreferences。

相关概念

ohos.data.preferences (用户首选项)

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

如何使用

导入模块

import dataPreferences from '@ohos.data.preferences';

常量

系统能力: SystemCapability.DistributedDataManager.Preferences.Core

名称

参数类型

可读

可写

说明

MAX_KEY_LENGTH

number

Key的最大长度限制为80个字节。

MAX_VALUE_LENGTH

number

Value的最大长度限制为8192个字节。

dataPreferences.getPreferences

getPreferences(context: Context, name: string, callback: AsyncCallback<Preferences>): void

获取Preferences实例,使用callback异步回调。

系统能力: SystemCapability.DistributedDataManager.Preferences.Core

Stage模型使用示例

import UIAbility from '@ohos.app.ability.UIAbility';

let preferences = null;

class EntryAbility extends UIAbility {
    onWindowStageCreate(windowStage) {
        try {
            dataPreferences.getPreferences(this.context, 'mystore', function (err, val) {
                if (err) {
                    console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
                    return;
                }
                preferences = val;
                console.info("Succeeded in getting preferences.");
            })
        } catch (err) {
            console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
        }
    }
}

首选项使用demo

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • HarmonyOS SDK版本:API version 9。

代码结构

├──entry/src/main/ets            // 代码区
│  ├──common
│  │  ├──constants
│  │  │  ├──CommonConstants.ets    // 公共常量类
│  │  │  └──StyleConstants.ets    // 样式常量类 
│  │  └──utils
│  │     └──Logger.ets        // 日志打印类
│  ├──entryability
│  │  └──EntryAbility.ts    // 程序入口类
│  ├──model
│  │  └──PreferenceModel.ets    // 首选项相关方法类
│  ├──pages
│  │  └──Index.ets        // 主界面    
│  ├──view
│  │  ├──ButtonComponent.ets    // 自定义Button组件类
│  │  └──TextItemComponent.ets  // 自定义Text组件类
│  └──viewmodel
│     ├──ButtonItemData.ets     // 按钮数据类
│     └──Fruit.ets            // 水果数据类
└──entry/src/main/resources    // 资源文件目录

构建主界面

在这个任务中,我们将完成示例主界面的开发,效果如图所示:

从上面效果图可以看出,主界面主要由2个相同样式的文本和文本输入框,以及3个相同样式的按钮组成。我们可以将文本和文本输入框抽取成一个TextItemComponent子组件。再将按钮抽取成一个ButtonComponent子组件。

在ets目录下,点击鼠标右键 > New > Directory,新建名为view的自定义子组件目录。然后在view目录下,点击鼠标右键 > New > ArkTS File,新建两个ArkTS文件,分别为TextItemComponent子组件、ButtonComponent子组件。可以看到文件目录结构,效果如图所示: 

// TextItemComponent.ets
@Component
export default struct TextItemComponent {
  private textResource: Resource = $r('app.string.empty'); // 按钮文本资源
  private placeholderResource: Resource = $r('app.string.empty'); // placeholder文本资源
  private marginBottom: string = '';
  private marginTop: string = '';
  private textInputType: InputType = InputType.Normal; // 输入框输入数据类型
  private textFlag: number = 0; // 文本框标记
  @Link fruit: Fruit; // 水果数据
  private textInputCallBack = (value: string) => {}; // TextInput的回调

  build() {
    Column() {
      Text(this.textResource)
        .fontSize(StyleConstants.TEXT_FONT_SIZE)
        .height(StyleConstants.TEXT_HEIGHT)
        .width(StyleConstants.FULL_PERCENT)
        .fontColor($r('app.color.text_font_color'))
        .letterSpacing(StyleConstants.LETTER_SPACING)
        .fontWeight(StyleConstants.FONT_WEIGHT)
        .margin({
          bottom: StyleConstants.TEXT_MARGIN_BOTTOM,
          left: StyleConstants.TEXT_MARGIN_LEFT,
          top: this.marginTop
        })

      TextInput({
        placeholder: this.placeholderResource,
        text: this.textFlag === 0 ? (this.fruit.fruitName) : (this.fruit.fruitNum)
      })
        .placeholderFont({ size: StyleConstants.FONT_SIZE, weight: StyleConstants.FONT_WEIGHT })
        .placeholderColor($r('app.color.placeholder_color'))
        .caretColor(Color.Blue)
        .type(this.textInputType)
        .height(StyleConstants.TEXT_INPUT_HEIGHT)
        .width(StyleConstants.TEXT_INPUT_WIDTH)
        .margin({ bottom: this.marginBottom })
        .fontSize(StyleConstants.FONT_SIZE)
        .fontColor($r('app.color.text_font_color'))
        .fontWeight(StyleConstants.FONT_WEIGHT)
        .backgroundColor($r('app.color.white'))
        .onChange((value: string) => {
          this.textInputCallBack(value);
        })
    }
  }
}
// ButtonComponent.ets
@Component
export default struct ButtonComponent {
  private buttonItemValues: Array<ButtonItemData> = this.getButtonItemValues();
  @Link fruit: Fruit; // 水果数据 

  build() {
    Column() {
      ForEach(this.buttonItemValues, (item: ButtonItemData) => {
        Button(item.resource, { type: ButtonType.Capsule, stateEffect: true })
          .backgroundColor($r('app.color.button_background_color'))
          .width(StyleConstants.BUTTON_WIDTH)
          .height(StyleConstants.BUTTON_HEIGHT)
          .fontWeight(StyleConstants.FONT_WEIGHT)
          .fontSize(StyleConstants.FONT_SIZE)
          .margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
          .onClick(() => {
            item.clickMethod();
          })
      }, (item: ButtonItemData) => JSON.stringify(item))
    }
  }
}

在Index.ets主界面中引用TextItemComponent和ButtonComponent子组件。 

// Index.ets
Column() {
  // 水果名称输入框
  TextItemComponent({
    textResource: $r('app.string.fruit_text'),
    placeholderResource: $r('app.string.fruit_placeholder'),
    textFlag: CommonConstants.FRUIT_FLAG,
    fruit: $fruit,
    textInputCallBack: (value: string) => {
      this.fruit.fruitName = value;
    }
  })

  // 水果数量输入框
  TextItemComponent({
    textResource: $r('app.string.number_text'),
    placeholderResource: $r('app.string.number_placeholder'),
    textFlag: CommonConstants.NUMBER_FLAG,
    fruit: $fruit,
    textInputCallBack: (value: string) => {
      this.fruit.fruitNum = value;
    }
  })

  // 按钮组件
  ButtonComponent({ fruit: $fruit })
}
.width(StyleConstants.FULL_PERCENT)
.height(StyleConstants.FULL_PERCENT)
.backgroundColor($r('app.color.main_background_color'))

创建数据文件

创建数据文件需要如下两个步骤:

  • 导入dataPreferences模块。

  • 通过dataPreferences模块的getPreferences(context, name)方法获取到对应文件名的Preferences实例。

Preferences的数据存储在文件中,因此需要指定存储的文件名PREFERENCES_NAME。再通过Preferences提供的方法进行数据库的相关操作。

// PreferenceModel.ets
// 导入dataPreferences模块
import dataPreferences from '@ohos.data.preferences';

let context = getContext(this);
let preference: dataPreferences.Preferences;
let preferenceTemp: dataPreferences.Preferences;

// 调用getPreferences方法读取指定首选项持久化文件,将数据加载到Preferences实例,用于数据操作
async getPreferencesFromStorage() {
  try {
    preference = await dataPreferences.getPreferences(context, CommonConstants.PREFERENCES_NAME);
  } catch (err) {
    Logger.error(CommonConstants.TAG, `Failed to get preferences, Cause: ${err}`);
  }
}

写入数据

获取Preferences实例后,使用Preferences的put方法,将用户输入的水果名称和水果数量数据,保存到缓存的实例中。再通过Preferences的flush方法将Preferences实例异步存储到首选项持久化文件中。

// PreferenceModel.ets
async putPreference(fruit: Fruit) {
  ...
  try {
    // 将用户输入的水果名称和水果数量数据,保存到缓存的Preference实例中
    await preference.put(CommonConstants.KEY_NAME, JSON.stringify(fruit));
  } catch (err) {
    Logger.error(CommonConstants.TAG, `Failed to put value, Cause: ${err}`);
  }
  // 将Preference实例存储到首选项持久化文件中
  await preference.flush();
}

读取数据

使用Preferences的get方法读取数据。如果键不存在,则返回默认值。例如获取下面代码中fruit的值,如果fruit的键KEY_NAME不存在,则会返回空字符串。通过默认值的设置,来避免程序出现异常。

// PreferenceModel.ets
async getPreference() {
  let fruit = '';
  ...
  try {
    fruit = (await preference.get(CommonConstants.KEY_NAME, '')).toString();
  } catch (err) {
    Logger.error(CommonConstants.TAG, `Failed to get value, Cause: ${err}`);
  }
}

删除数据文件

通过dataPreferences模块的deletePreferences(context, name)方法从内存中移除指定文件对应的Preferences单实例。移除Preferences单实例时,应用不允许再使用该实例进行数据操作,否则会出现数据一致性问题。

// PreferenceModel.ets
async deletePreferences() {
  try {
    await dataPreferences.deletePreferences(context, CommonConstants.PREFERENCES_NAME);
  } catch(err) {
    Logger.error(CommonConstants.TAG, `Failed to delete preferences, Cause: ${err}`);
  }
  ...
}

附上demo源码地址:demo源码

注意和AppStorage区别

AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。

和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。

AppStorage

AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。

AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。

AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见PersistentStorage)。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到@StorageProp和@StorageLink。

由于AppStorage是内存内数据,该行为会导致数据丧失持久化能力。

@StorageProp

在上文中已经提到,如果要建立AppStorage和自定义组件的联系,需要使用@StorageProp和@StorageLink装饰器。使用@StorageProp(key)/@StorageLink(key)装饰组件内的变量,key标识了AppStorage的属性。

当自定义组件初始化的时候,@StorageProp(key)/@StorageLink(key)装饰的变量会通过给定的key,绑定在AppStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证AppStorage一定存在给定的key,这取决于应用逻辑,是否在组件初始化之前在AppStorage实例中存入对应的属性。

@StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给@StorageProp,并覆盖掉本地的修改。

PersistentStorage

最后需要注意的是,LocalStorage和AppStorage都是运行时的内存,但是在应用退出再次启动后,依然能保存选定的结果,需要用到PersistentStorage。

PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。虽然借助PersistentStorage也可以实现数据的持久话,但它是用来持久化UI状态的,注意和用户首选项的区别(如果是一些配置存储,使用用户首选项)。

综上所述,什么时候用PersistentStorage?在应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage。

限制条件

PersistentStorage允许的类型和值有:

  • number, string, boolean, enum 等简单类型。
  • 可以被JSON.stringify()和JSON.parse()重构的对象。例如Date, Map, Set等内置类型则不支持,以及对象的属性方法不支持持久化。

PersistentStorage不允许的类型和值有:

  • 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
  • 不支持undefined 和 null 。

持久化数据是一个相对缓慢的操作,应用程序应避免以下情况:

  • 持久化大型数据集。
  • 持久化经常变化的变量。

PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。

PersistentStorage只能在UI页面内使用,否则将无法持久化数据。

PersistentStorage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些AppStorage属性应借助PersistentStorage持久化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。

PersistentStorage和AppStorage中的属性建立双向同步。应用开发通常通过AppStorage访问PersistentStorage,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过AppStorage获取和设置属性的。

更多介绍,参见官方文档:PersistentStorage:持久化存储UI状态

其他资源

@ohos.data.preferences (用户首选项)

文档中心--Codelabs

AppStorage:应用全局的UI状态存储

健康生活应用(ArkTS)_华为开发者HarmonyOS专区小助手-华为开发者联盟HarmonyOS专区

harmonyOS鸿蒙-数据管理-用户首选项(@ohos.data.preferences)_harmonyos datapreferences-CSDN博客

HarmonyOS-AppStorage:应用全局的UI状态存储-CSDN博客  

HarmonyOS 网络请求以及数据持久化-CSDN博客

https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_HarmonyOS-HealthyLife

https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Preferences

HarmonyOS应用开发-首选项与后台通知管理_@ohos.data.preferences-CSDN博客

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

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

相关文章

zabbix监控扩展

目录 一、zabbix自动发现与自动注册 &#xff08;一&#xff09;理论定义 1.自动发现 2.自动注册 &#xff08;二&#xff09;实操部署 1.自动发现 &#xff08;1&#xff09;新增一台客户端命名为zbx-agent02 ① 配置时间同步 ② 在服务端和客户端上配置 hosts 解析 …

自定义注解与拦截器实现不规范sql拦截(拦截器实现篇)

最近考虑myBatis中sql语句使用规范的问题&#xff0c;如果漏下条件或者写一些不规范语句会对程序性能造成很大影响。最好的方法就是利用代码进行限制&#xff0c;通过拦截器进行sql格式的判断在自测环节就能找到问题。写了个简单情景下的demo&#xff0c;并通过idea插件来将myB…

JavaScript进阶:WebAPIs重点知识整理1

目录 1 DOM修改元素内容 2 DOM修改元素常见属性 3 修改元素样式属性 3.1 通过style修改元素样式 3.2 通过类名className修改元素样式 3.3 通过classList修改元素样式 4 操作表单元素属性 5 自定义属性 6 定时器 7 事件监听 7.1 点击事件 click 7.2 鼠mouseenter和移…

【数据结构】 双链表的基本操作 (C语言版)

目录 一、双链表 1、双链表的定义&#xff1a; 2、双链表表的优缺点&#xff1a; 二、双链表的基本操作算法&#xff08;C语言&#xff09; 1、宏定义 2、创建结构体 3、双链表的初始化 4、双链表表插入 5、双链表的查找 6、双链表的取值 7、求双链表长度 8、双链表…

华南理工大学数字信号处理实验实验一(薛y老师版本)matlab源码

一、实验目的 1、加深对离散信号频谱分析的理解&#xff1b; 2、分析不同加窗长度对信号频谱的影响&#xff1b; 3、理解频率分辨率的概念&#xff0c;并分析其对频谱的 影响&#xff1b; 4、窗长和补零对DFT的影响 实验源码&#xff1a; 第一题&#xff1a; % 定义离散信…

C++函数对象-函数包装器-调用空的 std::function 时抛出的异常(std::bad_function_call)

任何定义了函数调用操作符的对象都是函数对象。C 支持创建、操作新的函数对象&#xff0c;同时也提供了许多内置的函数对象。 函数包装器 std::function 提供存储任意类型函数对象的支持。 用空的 std::function 时抛出的异常 std::bad_function_call class bad_function_cal…

【C++】命名空间(namespace)

文章目录 1. 为什么要有命名空间?2. 命名空间介绍3.命名空间三种使用方式4. 注意 1. 为什么要有命名空间? 在C语言中&#xff0c;局部变量和全局变量如果同名&#xff0c;在使用时可能会造成冲突。这并不是想避免就能避免的&#xff0c;在程序中&#xff0c;不仅仅是变量&…

《WebKit 技术内幕》之八(2):硬件加速机制

2 Chromium的硬件加速机制 2.1 GraphicsLayer的支持 GraphicsLayer对象是对一个渲染后端存储中某一层的抽象&#xff0c;同众多其他WebKit所定义的抽象类一样&#xff0c;在WebKit移植中&#xff0c;它还需要具体的实现类来支持该类所要提供的功能。为了完成这一功能&#x…

系统架构设计师教程(十三)层次式架构设计理论与实践

层次式架构设计理论与实践 13.1 层次式体系结构概述13.2 表现层框架设计13.2.1 表现层设计模式13.2.2 使用XML设计表现层&#xff0c;统一Web Form与Windows Form的外观13.2.3表现层中UIP设计思想13.2.4 表现层动态生成设计思想 13.3 中间层架构设计13.3.1 业务逻辑层组件设计1…

【Elasticsearch】索引恢复(recovery)流程梳理之副本分片数据恢复

replica shard重启具体流程 replica shard node &#xff08;generic threadpool&#xff09; 也是因为应用新的集群状态触发recovery&#xff0c;进入index阶段进入translog 阶段。先尝试重放本地的translog到global checkpoint向primary shard发起start recovery的请求&…

JVM对象创建与内存回收机制

对象的创建过程有如下步骤&#xff1a; 1.类加载检查&#xff1a; 虚拟机遇到一个new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载、解析和初始化过&#xff0c;如果没…

【golang】Context超时控制与原理

Context 在Go语言圈子中流行着一句话&#xff1a; Never start a goroutine without knowing how it will stop。 翻译&#xff1a;如果你不知道协程如何退出&#xff0c;就不要使用它。 在创建协程时&#xff0c;我们可能还会再创建一些别的子协程&#xff0c;那么这些协程的…

【书籍】强化学习第二版(英文版电子版下载、github源码)-附copilot翻译的中英文目录...

Python代码&#xff1a;https://github.com/ShangtongZhang/reinforcement-learning-an-introduction 英文原版书籍下载&#xff1a;http://incompleteideas.net/book/the-book-2nd.html 作者&#xff1a; 理查德S萨顿是阿尔伯塔大学计算机科学教授和强化学习与人工智能 AITF 主…

1.6万字全面掌握 BERT:自然语言处理(NLP)从初学到高级的全面指南

BERT&#xff08;双向编码器表示来自Transformer的模型&#xff09;是由Google开发的一种革命性的自然语言处理&#xff08;NLP&#xff09;模型。它改变了语言理解任务的格局&#xff0c;使机器能够理解语言中的上下文和细微差异。 在本博客中&#xff0c;我们将带您从 BERT …

信息安全概述

信息安全&#xff1a;防止任何对数据进行未授权访问的措施&#xff0c;或者防止造成信息有意无意泄漏、破坏、丢失等问题的发生&#xff0c;让数据处于远离危险、免于威胁的状态或特性。 网络安全&#xff1a;计算机网络环境下的信息安全。 信息安全现状及挑战 数字化时代威…

什么是VUE 创建第一个VUE实例

一、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 构建用户界面 的 渐进式 框架 Vue2官网&#xff1a;Vue.js 1.什么是构建用户界面 基于数据渲染出用户可以看到的界面 2.什么是渐进式 所谓渐进式就是循序渐进&#xff0c;不一定非得把Vu…

Docker编译多平台文件、构建多平台镜像并运行

参考官方文档&#xff1a; Multi-stageMulti-platformMulti-platform images 文章目录 方法Buildx 设置使用QEMU仿真编译文件构建镜像 使用交叉编译&#xff08;cross-compilation&#xff09;编译文件构建镜像 在x86_64上运行arm64容器 方法 如果在 x86_64/amd64 的平台上&am…

Docker 操作之数据卷挂载【云原生】

文章目录 1. 镜像1.1 基本命令1.2 案例练习 2. 容器2.1 基本命令2.2 Nginx 案例2.3 Redis 案例 3. 数据卷3.1 基本命令3.2 Nginx 案例3.3 MySQL 案例 1. 镜像 镜像命令一般分两部分组成&#xff1a;repository:tag。比如 mysql:5.7&#xff0c;表示名称加版本。 在没有指定 t…

一文掌握Python所有常见的循环用法

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 循环是编程中的重要概念&#xff0c;它允许我们重复执行一组操作&#xff0c;直到满足某个条件。Python提供了多种类型的循环&#xff0c;以适应不同的需求和场景。本文将详细介绍Python中所有常见的循环用法&am…

MySQL下对[表]的操作数据类型

目录 表的操作 创建表&#xff1a; 查看表结构&#xff1a; 修改表&#xff1a; 删除表&#xff1a; 数据类型 数据类型分类&#xff1a; 数值类型&#xff1a; tinyint类型越界测试&#xff1a; bit类型&#xff1a; 小数类型&#xff1a; 字符串类型&#xff1a; …