持久化存储(sqlite,preferences)实践

持久化存储-preferences

官方文档:

[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-preferences-V5](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-preferences-V5)

【新知识-用户首选项】- 学习流程

![](https://i-blog.csdnimg.cn/img_convert/e88ca523014bad5052bc0d29881597e4.png)

✔【用户首选项】- 数据持久化基本概念

**课程目标**
  • 认识首选项是什么?有什么作用?
  • 认识首选项运行机制、限制约束

**什么是数据持久化?**将内存数据保存到磁盘或者数据库的过程,我们称为数据持久化

**持久化的作用:**可以保证数据永久留存,直到我们主动删除为止

用户首选项是什么?有什么用?

**用户首选项:**用户首选项支持持久化轻量级数据,并提供相关方法对其新增、修改、查询、删除

用户首选项运行机制:

限制约束:

  • Key键为string类型,要求非空且长度不超过80个字节。
  • 如果Value值为string类型,可以为空,不为空时长度不超过8192个字节,大约为8K大小
  • 内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。

✔【用户首选项】-新增/修改数据

![](https://i-blog.csdnimg.cn/img_convert/ad859b5f29328cb3c84c56e94807be81.png)

课程目标

  • 能利用getPreferencesSync + putSync + flush 向用户首选项文件store中新增/修改一条字符串数组数据:["鸿蒙","HTML5"],key为keyword

新增/修改:如果首选项中没有数据则是【新增】、如果有数据则会被新值覆盖,则为【修改】

步骤:

  1. 在pages中创建一个pre_test.ets页面,在pre_test.ets页面上调用首选项api完成数据新增和修改需求
// 首选项api测试
import { preferences } from '@kit.ArkData'

@Entry
@Component
struct Pre_test {

  build() {
    Row() {
      Column() {
      // 1.0 利用首选项api对store这个文件做一个新增数据:["鸿蒙","HTML5"]
        Button('新增/修改数据').onClick(() => {
        //   1.0 获取store文件的操作对象
          const pre = preferences.getPreferencesSync(getContext(),{name:'store'})

        //   2.0 调用操作对象上的putSync方法完成数据的新增 (这是将数据保存到内存中)
          pre.putSync('keyword',["鸿蒙Next","HTML5"])
          pre.flush() //将内存数据写入到磁盘

          AlertDialog.show({message:'首选项数据写入成功'})
        })

      }
      .width('100%')
    }
    .height('100%')
  }
}
  1. 修改EntryAbility.ets中的启动路径
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/pre_test', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}
  1. 找到首选项磁盘目录查看数据

day6✔【用户首选项】-查询数据

![](https://i-blog.csdnimg.cn/img_convert/9216c3ababafa03e6ac20fd8dc138fa1.png)

课程目标

  • 能利用getPreferencesSync + getSync查询指定key为keyword的数据

✔【用户首选项】-删除数据

![](https://i-blog.csdnimg.cn/img_convert/fe33583a05199381c8be68de8b33989e.png)

课程目标

  • 能利用getPreferencesSync + deleteSync + flush删除用户首选项文件中指定key为keyword的数据

【用户首选项】-综合案例

![](https://i-blog.csdnimg.cn/img_convert/c6e0e9df8c41e96896138437b34c9f1f.png)

课程目标

  • 使用用户首选项增,删,查 三套API实现如下需求:
  • 在文本框输入关键字 回车保存到用户首选项中
  • 文本框回车保存的同时查询用户首选项中的数据显示在页面列表中
  • 点击 关键字后面的删除按钮实现关键字的删除
  • 点击全部删除按钮,实现删除所有关键字

提示:可以使用 Array数组形式来保存关键字

✔【综合案例】- 静态结构搭建

```arkts import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct SearchPage {
@State keyword: string = ‘’
@StorageProp(‘topHeight’) topHeight: number = 0
@State isdel: boolean = false

aboutToAppear(): void {

}

@Builder
itemBuilder(text: string) {
Row({ space: 20 }) {
Text(text)
if (this.isdel) {
Text(‘x’)
.height(30)
.onClick(() => {
AlertDialog.show({ message: ‘删除’ + text })
})
}
}
.margin({ right: 10, top: 10 })
.padding({
left: 15,
right: 15,
top: 5,
bottom: 5
})
.backgroundColor(‘rgba(0,0,0,0.05)’)
.borderRadius(20)
}

build() {
Navigation() {
Column({ space: 15 }) {
// 1. 搜索关键字
TextInput({ placeholder: ‘输入回车保存数据’, text: $$this.keyword })
.onSubmit(() => {
AlertDialog.show({ message: this.keyword })
})

    // 2. 关键字列表
    Row() {
      Text('搜索记录').fontSize(20).fontWeight(800)

      Row() {
        if (this.isdel) {
          Text('全部删除')
            .onClick(()=>{
              AlertDialog.show({ message: '补上全部删除逻辑' })
            })
          Text(' | ')
          Text('取消删除')
            .onClick(()=>{
              this.isdel = false
            })
        } else {
          Image($r('app.media.ic_common_delete'))
            .height(28)
            .onClick(()=>{
              this.isdel = true
            })
        }
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)

    //   3. 关键字列表
    Flex({ wrap: FlexWrap.Wrap }) {
      ForEach([1, 2, 3, 4], (item: number) => {
        this.itemBuilder('文本' + item)
      })
    }
  }
  .padding(15)
}
.padding({ top: this.topHeight })
.titleMode(NavigationTitleMode.Mini)
.title('搜索页面')

}
}




<h3 id="BtdQj">✔【综合案例】-保存关键字数组</h3>
**课程目标**

+ 利用用户首选项getPreferencesSync+putSync+flush**追加保存**用户输入的关键字
+ 相同关键字不重复保存

注意:在调用putSync方法之前先调用getSync获取老数据数组,再使用push方法追加新数据后再保存回首选项

```arkts
import { promptAction } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'

@Entry
@Component
struct SerachPage {
  @State keyword: string = ''
  @StorageProp('topHeight') topHeight: number = 0

  aboutToAppear(): void {

  }

  @Builder
  itemBuilder(text: string) {
    Row({ space: 20 }) {
      Text(text)
      Text('x')
        .height(30)
        .onClick(() => {
          AlertDialog.show({ message: '删除' + text })
        })
    }
    .margin({ right: 10, top: 10 })
    .padding({ left: 15, right: 15, top: 5, bottom: 5 })
    .backgroundColor('rgba(0,0,0,0.05)')
    .borderRadius(20)
  }

  // 1.0 新增方法
  async saveData(text: string) {
    // 非空验证
    if(!text){
      return
    }
    // 1.0 获取首选项对象实例
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    // 2.0 调用putsync方法保存数组
    // 2.0.1 先从首选项中获取老数据
    let dataArr = pre.getSync('keyword', []) as string[]
    // 判断如果首选项中已经有了该关键词,不再保存
    if(dataArr.includes(text)){
      // 该关键字已经存在了,不保存
      return
    }

    dataArr.push(text)
    // 2.0.2 存数据
    pre.putSync('keyword', dataArr)

    // 3.0 调用flush写入到磁盘
    await pre.flush()
  }

  build() {
    Column({ space: 15 }) {
      // 1. 搜索关键字
      TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
        .onSubmit(() => {
          // AlertDialog.show({ message: this.keyword })
          //   将关键词数据保存到首选项中
          this.saveData(this.keyword)
        })

      // 2. 关键字列表
      Row() {
        Text('搜索记录').fontSize(20).fontWeight(800)

        Row() {
          Text('全部删除')
          Text(' | ')
          Text('取消删除')

          Image($r('app.media.ic_common_delete'))
            .height(28)
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      //   3. 关键字列表
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach([1, 2, 3, 4], (item: number) => {
          this.itemBuilder('文本' + item)
        })
      }
    }
    .padding(15)
  }
}

✔【综合案例】-查询保存的关键字数组

**课程目标**
  • 利用用户首选项getPreferencesSync+getSync从首选项中获取string[]类型的数据

**注意:**使用await调用,否则可能出现获取不到最新保存的关键字数据

import { promptAction } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'

@Entry
@Component
struct SearchPage {
  @State keyword: string = ''
  @StorageProp('topHeight') topHeight: number = 0
  @State keywords: string[] = []

  aboutToAppear() {
    this.getData()
  }

  @Builder
  itemBuilder(text: string) {
    Row({ space: 20 }) {
      Text(text)
      Text('x')
        .height(30)
        .onClick(() => {
          AlertDialog.show({ message: '删除' + text })
        })
    }
    .margin({ right: 10, top: 10 })
    .padding({ left: 15, right: 15, top: 5, bottom: 5 })
    .backgroundColor('rgba(0,0,0,0.05)')
    .borderRadius(20)
  }

  // 1.0 新增方法
  async saveData(text: string) {
    // 非空验证
    if (!text) {
      return
    }

    // 1.0 获取首选项对象实例
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    // 2.0 调用putsync方法保存数组
    // 2.0.1 先从首选项中获取老数据
    let dataArr = pre.getSync('keyword', []) as string[]
    // 判断如果首选项中已经有了该关键词,不再保存
    if (dataArr.includes(text)) {
      // 该关键字已经存在了,不保存
      return
    }

    dataArr.push(text)
    // 2.0.2 存数据
    pre.putSync('keyword', dataArr)

    // 3.0 调用flush写入到磁盘
    await pre.flush()
  }

  // 2.0 获取首选项数据
  async getData() {
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    this.keywords = pre.getSync('keyword', []) as string[]
  }

  build() {
    Column({ space: 15 }) {
      // 1. 搜索关键字
      TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
        .onSubmit(async () => {
          // AlertDialog.show({ message: this.keyword })
          //   将关键词数据保存到首选项中
          // 注意使用await调用,否则可能出现获取不到最新保存的关键字数据
          await this.saveData(this.keyword)
          await this.getData() 
        })

      // 2. 关键字列表
      Row() {
        Text('搜索记录').fontSize(20).fontWeight(800)

        Row() {
          Text('全部删除')
          Text(' | ')
          Text('取消删除')

          Image($r('app.media.ic_common_delete'))
            .height(28)
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      //   3. 关键字列表
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.keywords, (item:string) => {
          this.itemBuilder(item)
        })
      }
    }
    .padding(15)
  }
}

✔【综合案例】-删除指定关键字

**课程目标**
  • 利用用户首选项getPreferencesSync+putSync+flush删除指定关键字
  • 利用数组方法findIndex查找关键字在数组中的索引和使用splice根据索引从数组中删除一个数据
import { promptAction } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'

@Entry
@Component
struct SearchPage {
  @State keyword: string = ''
  @StorageProp('topHeight') topHeight: number = 0
  @State keywords: string[] = []

  aboutToAppear() {
    this.getData()
  }

  @Builder
  itemBuilder(text: string) {
    Row({ space: 20 }) {
      Text(text)
      Text('x')
        .height(30)
        .onClick(async () => {
          //  删除指定关键字
          await this.delData(text)
          await this.getData()
        })
    }
    .margin({ right: 10, top: 10 })
    .padding({ left: 15, right: 15, top: 5, bottom: 5 })
    .backgroundColor('rgba(0,0,0,0.05)')
    .borderRadius(20)
  }

  // 1.0 新增方法
  async saveData(text: string) {
    // 非空验证
    if (!text) {
      return
    }

    // 1.0 获取首选项对象实例
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    // 2.0 调用putsync方法保存数组
    // 2.0.1 先从首选项中获取老数据
    let dataArr = pre.getSync('keyword', []) as string[]
    // 判断如果首选项中已经有了该关键词,不再保存
    if (dataArr.includes(text)) {
      // 该关键字已经存在了,不保存
      return
    }

    dataArr.push(text)
    // 2.0.2 存数据
    pre.putSync('keyword', dataArr)

    // 3.0 调用flush写入到磁盘
    await pre.flush()
  }

  // 2.0 获取首选项数据
  async getData() {
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    this.keywords = pre.getSync('keyword', []) as string[]
  }

  // 3.0 删除指定关键字和全部关键字
  async delData(text?: string) {
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    //   1.0 删除全部关键
    if (!text) {
      pre.deleteSync('keyword')
    } else {
      // 2.0 删除指定关键字  获取 -> 删除内存数组的关键字 -> 写回
      let datas = pre.getSync('keyword', []) as string[]
      let cindex = datas.findIndex(item => item === text)
      // 当关键字索引为-1的时候,表示没有找到
      if (cindex < 0) {
        return
      }
      // 如果有删除
      datas.splice(cindex, 1) // 返回值表示删除的元素
      //   保存回去
      pre.putSync('keyword', datas)
    }
    // 写回磁盘
    pre.flush()
  }

  build() {
    Column({ space: 15 }) {
      // 1. 搜索关键字
      TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
        .onSubmit(async () => {
          // AlertDialog.show({ message: this.keyword })
          //   将关键词数据保存到首选项中
          await this.saveData(this.keyword)
          await this.getData()
        })

      // 2. 关键字列表
      Row() {
        Text('搜索记录').fontSize(20).fontWeight(800)

        Row() {
          Text('全部删除')
            .onClick(async () => {
              // 删除全局数据
              await this.delData()
              await this.getData()
            })
          Text(' | ')
          Text('取消删除')

          Image($r('app.media.ic_common_delete'))
            .height(28)
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      //   3. 关键字列表
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.keywords, (item: string) => {
          this.itemBuilder(item)
        })
      }
    }
    .padding(15)
  }
}

✔【综合案例】-删除全部关键字

**课程目标**
  • 利用用户首选项getPreferencesSync+deleteSync+flush从删除首选项中全部数据
import { promptAction } from '@kit.ArkUI'
import { preferences } from '@kit.ArkData'

@Entry
@Component
struct SearchPage {
  @State keyword: string = ''
  @StorageProp('topHeight') topHeight: number = 0
  @State keywords: string[] = []

  aboutToAppear() {
    this.getData()
  }

  @Builder
  itemBuilder(text: string) {
    Row({ space: 20 }) {
      Text(text)
      Text('x')
        .height(30)
        .onClick(async () => {
          //  删除指定关键字
          await this.delData(text)
          await this.getData()
        })
    }
    .margin({ right: 10, top: 10 })
    .padding({ left: 15, right: 15, top: 5, bottom: 5 })
    .backgroundColor('rgba(0,0,0,0.05)')
    .borderRadius(20)
  }

  // 1.0 新增方法
  async saveData(text: string) {
    // 非空验证
    if (!text) {
      return
    }

    // 1.0 获取首选项对象实例
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    // 2.0 调用putsync方法保存数组
    // 2.0.1 先从首选项中获取老数据
    let dataArr = pre.getSync('keyword', []) as string[]
    // 判断如果首选项中已经有了该关键词,不再保存
    if (dataArr.includes(text)) {
      // 该关键字已经存在了,不保存
      return
    }

    dataArr.push(text)
    // 2.0.2 存数据
    pre.putSync('keyword', dataArr)

    // 3.0 调用flush写入到磁盘
    await pre.flush()
  }

  // 2.0 获取首选项数据
  async getData() {
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    this.keywords = pre.getSync('keyword', []) as string[]
  }

  // 3.0 删除指定关键字和全部关键字
  async delData(text?: string) {
    const pre = preferences.getPreferencesSync(getContext(), { name: 'store1' })
    //   1.0 删除全部关键
    if (!text) {
      pre.deleteSync('keyword')
    } else {
      // 2.0 删除指定关键字  获取 -> 删除内存数组的关键字 -> 写回
      let datas = pre.getSync('keyword', []) as string[]
      let cindex = datas.findIndex(item => item === text)
      // 当关键字索引为-1的时候,表示没有找到
      if (cindex < 0) {
        return
      }
      // 如果有删除
      datas.splice(cindex, 1) // 返回值表示删除的元素
      //   保存回去
      pre.putSync('keyword', datas)
    }
    // 写回磁盘
    pre.flush()
  }

  build() {
    Column({ space: 15 }) {
      // 1. 搜索关键字
      TextInput({ placeholder: '输入回车保存数据', text: $$this.keyword })
        .onSubmit(async () => {
          // AlertDialog.show({ message: this.keyword })
          //   将关键词数据保存到首选项中
          await this.saveData(this.keyword)
          await this.getData()
        })

      // 2. 关键字列表
      Row() {
        Text('搜索记录').fontSize(20).fontWeight(800)

        Row() {
          Text('全部删除')
            .onClick(async () => {
              // 删除全局数据
              await this.delData()
              await this.getData()
            })
          Text(' | ')
          Text('取消删除')

          Image($r('app.media.ic_common_delete'))
            .height(28)
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      //   3. 关键字列表
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.keywords, (item: string) => {
          this.itemBuilder(item)
        })
      }
    }
    .padding(15)
  }
}

其他:

示例代码:

[https://gitee.com/harmonyos_samples/preferences.git](https://gitee.com/harmonyos_samples/preferences.git)

1.点击顶部titleBar的右侧切换按钮,弹出主题菜单,选择任意主题则切换相应的主题界面;

2.退出应用再重新进入,显示上一次退出前的主题界面。

封装的单例:

```typescript import { preferences } from "@kit.ArkData"

export default class PreferencesDemo {
private static Instance: PreferencesDemo | null = null
pref: preferences.Preferences | null = null

private constructor() {
}

static getInstance() {
if (!PreferencesDemo.Instance) {
PreferencesDemo.Instance = new PreferencesDemo()
}
return PreferencesDemo.Instance
}

async loadPreferences(context: Context, name: string) {
let pre = await preferences.getPreferences(context, name)
// todo 获取用户首选项对象
this.pref = pre
}

// todo 获取键值对
async getPreferences(key: string, value: preferences.ValueType): Promise<preferences.ValueType> {
return new Promise<preferences.ValueType>(async (resolve, reject) => {
if (!this.pref) {
console.log(‘当前不存在用户首选项’)
reject()
} else {
let ret = await this.pref.get(key, value)
resolve(ret)
}
})
}

// todo 添加键值对
async pushPreferences(key: string, value: preferences.ValueType) {
if (!this.pref) {
console.log(‘当前不存在用户首选项’)
} else {
await this.pref.put(key, value)
await this.pref.flush()
// 立即刷新写入文件中
}
}

// todo 删除键值对
async deletePreferences(key: string) {
if (!this.pref) {
console.log(‘当前不存在用户首选项’)
} else {
await this.pref.delete(key)
}
}

// todo 查询所有键值对
getAllPreferences(): Promise {
return new Promise(async (resolve, reject) => {
if (!this.pref) {
console.log(‘当前不存在用户首选项’)
reject()
} else {
let ret = await this.pref.getAll()
resolve(ret)
}
})
}

// todo 清空数据
async clear() {
try {
let value = await this.pref?.clear()
console.log(‘清空Preferences成功’)
return value
} catch (e) {
console.log(‘清空Preferences失败’, JSON.stringify(e))
}
}
}


<h4 id="FMYsn">封装单例使用:</h4>
```typescript
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) 中添加代码:
PreferencesDemo.getInstance().loadPreferences(this.context, 'xy')
import { promptAction, router } from '@kit.ArkUI';
import PreferencesDemo from '../day09/PreferencesDemo';
import { preferences } from '@kit.ArkData';
import { JSON } from '@kit.ArkTS';

@Entry
@Component
struct Day03_demo4Page {
  @State message: string = 'Hello World';
  @State appName: string = '美团';
  @State ft_color: Color = Color.Gray
  @State ifAgree: boolean = false
  @State phone: string = ''

  async aboutToAppear() {
    // 1.0 获取首选项对象实例
    const pre = await preferences.getPreferencesSync(getContext(), { name: 'xy' })
    // 2.0 调用putsync方法保存数组
    // 2.0.1 先从首选项中获取老数据
    let dataArr = await pre.getSync('phone', '') as string
    // promptAction.showToast({ message: dataArr })
    // const pre2 = await preferences.getPreferencesSync(getContext(), { name: 'xy' })
    // // 2.0 调用putsync方法保存数组
    // // 2.0.1 先从首选项中获取老数据
    // let dataArr2 = await pre2.getSync('keyword', []) as string[]
    // promptAction.showToast({ message: 'arr2' + JSON.stringify(dataArr2) })
    let phone2 = (await PreferencesDemo.getInstance().getPreferences('phone', '')).toString()
    promptAction.showToast({ message: phone2 })
    if (phone2.length) {
      this.phone = phone2
    }
  }

  build() {
    Column() {
      Stack({ alignContent: Alignment.BottomEnd }) {
        Column() {
          Text(`欢迎登陆${this.appName}`).fontSize(30).width('100%')

          Row() {
            Text('+86')
            Image($r('app.media.arrow_down')).width(20).height(20)
            TextInput({ placeholder: '请输入手机号', text: $$this.phone })
              .type(InputType.Number)
              .backgroundColor(Color.Transparent)
              .fontColor(Color.Black)
              .width('80%')
          }.width('100%').margin({ top: 40 })

          Row().width('100%').height(1).backgroundColor(Color.Black)

          TextInput({ placeholder: '请输入密码' })
            .type(InputType.Password)
            .width('100%')
            .backgroundColor(Color.Transparent)
            .margin({ top: 20 })

          Row().width('100%').height(1).backgroundColor(Color.Black)

          Flex({
            direction: FlexDirection.Row,
            wrap: FlexWrap.Wrap,
            alignItems: ItemAlign.Center
          }) {
            Toggle({ type: ToggleType.Checkbox, isOn: false })
              .onChange(() => {
                this.ifAgree = !this.ifAgree
              })
            Text('我已阅读并同意').fontColor(this.ft_color)
            Text(`${this.appName}用户协议》`).fontColor(Color.Blue)
            Text('和').fontColor(this.ft_color)
            Text('《隐私政策》').fontColor(Color.Blue)
          }.width('100%').margin({ top: 20 })

          Button('登陆')
            .width('100%')
            .backgroundColor('#fff6b9')
            .fontColor(Color.Black)
            .margin({ top: 20 })
            .onClick(async () => {
              promptAction.showToast({ message: this.phone + '保存数据到本地' })
              await PreferencesDemo.getInstance().pushPreferences('phone', this.phone)
            })
          Button('新增/修改数据')
            .width('100%')
            .backgroundColor('#fff6b9')
            .fontColor(Color.Black)
            .margin({ top: 20 })
            .onClick((event: ClickEvent) => {
              //   1.0 获取store文件的操作对象
              const pre = preferences.getPreferencesSync(getContext(), { name: 'store' })

              //   2.0 调用操作对象上的putSync方法完成数据的新增 (这是将数据保存到内存中)
              pre.putSync('keyword', ["鸿蒙Next", "HTML5"])
              pre.flush() //将内存数据写入到磁盘

              AlertDialog.show({ message: '首选项数据写入成功' })
            })

          Row() {
            Text('短信验证码登陆').fontColor('#3A8BFF').onClick(() => {
              router.pushUrl({ url: 'pages/day03/day03_demo5Page' })
            })
            Text('遇到问题').fontColor('#3A8BFF')
          }.width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ top: 20 })

        }.height('100%')
        .width('100%')

        Row() {
          Image($r("app.media.dianxin")).width(40).height(40)
          Image($r("app.media.wechat")).width(40).height(40)
          Image($r("app.media.QQ")).width(40).height(40)
          Image($r("app.media.apple")).width(40).height(40)
        }.justifyContent(FlexAlign.SpaceBetween)
        .width('100%')
        .padding(20)
      }

    }.padding({ left: 30, right: 30 })
    .height('100%')
    .width('100%')
  }
}
**再put后沙盒才会有数据**
![](https://i-blog.csdnimg.cn/img_convert/23f067ae998ffe108392d652ce0c1c9a.png)![](https://i-blog.csdnimg.cn/img_convert/ae8588679d85c52206fbcdab5849d3f5.png)

sqlite数据库

官方文档:

[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-rdb-store-V5](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-persistence-by-rdb-store-V5)

开发步骤:

1.使用关系型数据库实现数据持久化,需要获取一个RdbStore,
其中包括建库、建表、升降级等操作。示例代码如下所示:
import { relationalStore } from '@kit.ArkData'; // 导入模块
import { UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { window } from '@kit.ArkUI';

// 此处示例在Ability中实现,使用者也可以在其他合理场景中使用
class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage) {
    const STORE_CONFIG :relationalStore.StoreConfig= {
      name: 'RdbTest.db', // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全级别
      encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
      customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
      isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
    };

    // 判断数据库版本,如果不匹配则需进行升降级操作
    // 假设当前数据库版本为3,表结构:EMPLOYEE (NAME, AGE, SALARY, CODES, IDENTITY)
    const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB, IDENTITY UNLIMITED INT)'; // 建表Sql语句, IDENTITY为bigint类型,sql中指定类型为UNLIMITED INT

    relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
      if (err) {
        console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
        return;
      }
      console.info('Succeeded in getting RdbStore.');

      // 当数据库创建时,数据库默认版本为0
      if (store.version === 0) {
        store.executeSql(SQL_CREATE_TABLE); // 创建数据表
        // 设置数据库的版本,入参为大于0的整数
        store.version = 3;
      }

      // 如果数据库版本不为0且和当前数据库版本不匹配,需要进行升降级操作
      // 当数据库存在并假定版本为1时,例应用从某一版本升级到当前版本,数据库需要从1版本升级到2版本
      if (store.version === 1) {
        // version = 1:表结构:EMPLOYEE (NAME, SALARY, CODES, ADDRESS) => version = 2:表结构:EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS)
        (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER');
        store.version = 2;
      }

      // 当数据库存在并假定版本为2时,例应用从某一版本升级到当前版本,数据库需要从2版本升级到3版本
      if (store.version === 2) {
        // version = 2:表结构:EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS) => version = 3:表结构:EMPLOYEE (NAME, AGE, SALARY, CODES)
        (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT');
        store.version = 3;
      }
    });

    // 请确保获取到RdbStore实例后,再进行数据库的增、删、改、查等操作
  }
}

说明

  • 应用创建的数据库与其上下文(Context)有关,即使使用同样的数据库名称,但不同的应用上下文,会产生多个数据库,例如每个UIAbility都有各自的上下文。
  • 当应用首次获取数据库(调用getRdbStore)后,在应用沙箱内会产生对应的数据库文件。使用数据库的过程中,在与数据库文件相同的目录下可能会产生以-wal和-shm结尾的临时文件。此时若开发者希望移动数据库文件到其它地方使用查看,则需要同时移动这些临时文件,当应用被卸载完成后,其在设备上产生的数据库文件及临时文件也会被移除。
  • 错误码的详细介绍请参见通用错误码关系型数据库错误码
2.获取到RdbStore后,调用insert()接口插入数据。
示例代码如下所示:
let store: relationalStore.RdbStore | undefined = undefined;

let value1 = 'Lisa';
let value2 = 18;
let value3 = 100.5;
let value4 = new Uint8Array([1, 2, 3, 4, 5]);
let value5 = BigInt('15822401018187971961171');
// 以下三种方式可用
const valueBucket1: relationalStore.ValuesBucket = {
  'NAME': value1,
  'AGE': value2,
  'SALARY': value3,
  'CODES': value4,
  'IDENTITY': value5,
};
const valueBucket2: relationalStore.ValuesBucket = {
  NAME: value1,
  AGE: value2,
  SALARY: value3,
  CODES: value4,
  IDENTITY: value5,
};
const valueBucket3: relationalStore.ValuesBucket = {
  "NAME": value1,
  "AGE": value2,
  "SALARY": value3,
  "CODES": value4,
  "IDENTITY": value5,
};

if (store !== undefined) {
  (store as relationalStore.RdbStore).insert('EMPLOYEE', valueBucket1, (err: BusinessError, rowId: number) => {
    if (err) {
      console.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
      return;
    }
    console.info(`Succeeded in inserting data. rowId:${rowId}`);
  })
}

说明

关系型数据库没有显式的flush操作实现持久化,数据插入即保存在持久化文件。

3.根据谓词指定的实例对象,对数据进行修改或删除。
调用update()方法修改数据,调用delete()方法删除数据。示例代码如下所示:
let value6 = 'Rose';
let value7 = 22;
let value8 = 200.5;
let value9 = new Uint8Array([1, 2, 3, 4, 5]);
let value10 = BigInt('15822401018187971967863');
// 以下三种方式可用
const valueBucket4: relationalStore.ValuesBucket = {
  'NAME': value6,
  'AGE': value7,
  'SALARY': value8,
  'CODES': value9,
  'IDENTITY': value10,
};
const valueBucket5: relationalStore.ValuesBucket = {
  NAME: value6,
  AGE: value7,
  SALARY: value8,
  CODES: value9,
  IDENTITY: value10,
};
const valueBucket6: relationalStore.ValuesBucket = {
  "NAME": value6,
  "AGE": value7,
  "SALARY": value8,
  "CODES": value9,
  "IDENTITY": value10,
};

// 修改数据
let predicates1 = new relationalStore.RdbPredicates('EMPLOYEE'); // 创建表'EMPLOYEE'的predicates
predicates1.equalTo('NAME', 'Lisa'); // 匹配表'EMPLOYEE'中'NAME'为'Lisa'的字段
if (store !== undefined) {
  (store as relationalStore.RdbStore).update(valueBucket4, predicates1, (err: BusinessError, rows: number) => {
    if (err) {
      console.error(`Failed to update data. Code:${err.code}, message:${err.message}`);
     return;
   }
   console.info(`Succeeded in updating data. row count: ${rows}`);
  })
}

// 删除数据
predicates1 = new relationalStore.RdbPredicates('EMPLOYEE');
predicates1.equalTo('NAME', 'Lisa');
if (store !== undefined) {
  (store as relationalStore.RdbStore).delete(predicates1, (err: BusinessError, rows: number) => {
    if (err) {
      console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
      return;
    }
    console.info(`Delete rows: ${rows}`);
  })
}
4.根据谓词指定的查询条件查找数据。
调用query()方法查找数据,返回一个ResultSet结果集。示例代码如下所示:
let predicates2 = new relationalStore.RdbPredicates('EMPLOYEE');
predicates2.equalTo('NAME', 'Rose');
if (store !== undefined) {
  (store as relationalStore.RdbStore).query(predicates2, ['ID', 'NAME', 'AGE', 'SALARY', 'IDENTITY'], (err: BusinessError, resultSet) => {
    if (err) {
      console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
      return;
    }
    console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
    // resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。
    while (resultSet.goToNextRow()) {
      const id = resultSet.getLong(resultSet.getColumnIndex('ID'));
      const name = resultSet.getString(resultSet.getColumnIndex('NAME'));
      const age = resultSet.getLong(resultSet.getColumnIndex('AGE'));
      const salary = resultSet.getDouble(resultSet.getColumnIndex('SALARY'));
      const identity = resultSet.getValue(resultSet.getColumnIndex('IDENTITY'));
      console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}, identity=${identity}`);
    }
    // 释放数据集的内存
    resultSet.close();
  })
}

说明

当应用完成查询数据操作,不再使用结果集(ResultSet)时,请及时调用close方法关闭结果集,释放系统为其分配的内存。

5.在同路径下备份数据库。
关系型数据库支持两种手动备份和自动备份(仅系统应用可用)两种方式,具体可见[关系型数据库备份](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-backup-and-restore-V5#关系型数据库备份)。

此处以手动备份为例:

if (store !== undefined) {
  // "Backup.db"为备份数据库文件名,默认在RdbStore同路径下备份。也可指定路径:customDir + "backup.db"
  (store as relationalStore.RdbStore).backup("Backup.db", (err: BusinessError) => {
    if (err) {
      console.error(`Failed to backup RdbStore. Code:${err.code}, message:${err.message}`);
      return;
    }
    console.info(`Succeeded in backing up RdbStore.`);
  })
}
6.从备份数据库中恢复数据。
关系型数据库支持两种方式:恢复手动备份数据和恢复自动备份数据(仅系统应用可用),具体可见[关系型数据库数据恢复](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/data-backup-and-restore-V5#关系型数据库数据恢复)。

此处以调用restore接口恢复手动备份数据为例:

if (store !== undefined) {
  (store as relationalStore.RdbStore).restore("Backup.db", (err: BusinessError) => {
    if (err) {
      console.error(`Failed to restore RdbStore. Code:${err.code}, message:${err.message}`);
      return;
    }
    console.info(`Succeeded in restoring RdbStore.`);
  })
}
7.删除数据库。
调用deleteRdbStore()方法,删除数据库及数据库相关文件。示例代码如下:
relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err: BusinessError) => {
 if (err) {
    console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
    return;
  }
  console.info('Succeeded in deleting RdbStore.');
});

具体案例:

![](https://i-blog.csdnimg.cn/img_convert/207b5b2e14264d87b86780a7875ce14c.png)

export default class EntryAbility extends UIAbility {
  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
    //this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    //hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    //PreferencesDemo.getInstance().loadPreferences(this.context, 'xy')

    await DbUtilsDemo.initDB(this.context)
    // await dbUtil.createTable(`create table IF NOT EXISTS happy(name varchar(16),pd INTEGER,id INTEGER PRIMARY KEY AUTOINCREMENT)`)
    await DbUtilsDemo.createTable(`CREATE TABLE IF NOT EXISTS water(
      nickname VARCHAR(45) NULL,
      avatar VARCHAR(200) NULL,
      cover VARCHAR(200) NULL,
      likedCount VARCHAR(15) NULL,
      displayTitle VARCHAR(45) NULL,
      id INTEGER PRIMARY KEY AUTOINCREMENT)`)
  }
WaterFlowItem.ets
```typescript export interface WaterFlowItem { nickName: string, avatar: string, cover: string, likedCount: string, displayTitle: string } ```

History.ets
```typescript export interface History { name: string pd: string } ```

DbUtilsDemo.ets
```typescript import { relationalStore } from '@kit.ArkData' import { common } from '@kit.AbilityKit' import { JSON } from '@kit.ArkTS' import { History } from '../common/History'

const config: relationalStore.StoreConfig = {
name: ‘xy’, //数据库名称
securityLevel: relationalStore.SecurityLevel.S1 //数据库安全等级
}

class DbUtilsDemo {
rdbStore: relationalStore.RdbStore | null = null

// 创建sqlite对象
// userId用于决定不同用户使用不同的数据库
initDB(context: common.UIAbilityContext | common.Context, userId?: string): Promise {
return new Promise((resolve, reject) => {
// 获取数据库对象
relationalStore.getRdbStore(context, config)
.then(rdbStore => {
this.rdbStore = rdbStore
// 赋值给数据库对象
console.log(‘rdbStore 初始化完成!’, JSON.stringify(rdbStore))
resolve()
})
.catch((reason: Error) => {
console.log(‘rdbStore 初始化异常’, JSON.stringify(reason))
reject(reason)
})
})
}

createTable(createSQL: string): Promise {
return new Promise((resolve, reject) => {
this.rdbStore?.executeSql(createSQL)
.then(() => {
console.log(‘创建表成功’, createSQL)
resolve()
})
.catch((err: Error) => {
console.log(‘创建表失败,’ + err.message, JSON.stringify(err))
reject(err)
})
})
}
}

let dbUtil: DbUtilsDemo = new DbUtilsDemo();

export default dbUtil as DbUtilsDemo




<h4 id="nXJxc">HistoryRecordModel.ets</h4>
```typescript
// import { History } from "./History"
import { relationalStore, ValuesBucket } from "@kit.ArkData"
import DbUtilsDemo from "../utils/DbUtilsDemo"
import dbUtil from "../utils/DbUtilsDemo"
import { Type } from "@kit.ArkUI"
import { WaterFlowItem } from '../pages/day08/das'

type History = WaterFlowItem

export class HistoryRecordModel {
  private static Instance: HistoryRecordModel | null = null
  TABNAME: string = 'water'

  static getInstance() {
    if (!HistoryRecordModel.Instance) {
      HistoryRecordModel.Instance = new HistoryRecordModel()
    }
    return HistoryRecordModel.Instance
  }

  //   todo 应为insert操作 需要修改ValuesBucket类型
  valueBucket(value: History): ValuesBucket {
    let obj: ValuesBucket = {}
    if (!value) {
      return {}
    } else {
      obj.nickName = value.nickName
      obj.avatar = value.avatar
      obj.cover = value.cover
      obj.likedCount = value.likedCount
      obj.displayTitle = value.displayTitle
      return obj
    }
  }

  async insert(record: History): Promise<number> {
    return new Promise((resolve, reject) => {
      // 获取数据库对象进行判断
      if (DbUtilsDemo.rdbStore) {
        let valueRecord: ValuesBucket = this.valueBucket(record)
        console.log("listItem的值", JSON.stringify(valueRecord))
        DbUtilsDemo.rdbStore?.insert(this.TABNAME, valueRecord, (err, rowId: number) => {
          if (err) {
            console.error(`sql失败插入:${err.message}, message:${err.name}`);
            reject(err)
            // reject(err)
          } else {
            console.info(`sql成功插入:${rowId}`);
            resolve(rowId)
          }
        })
      }
    })
  }

  //   todo 删除操作

  deleteById(name: string): Promise<number> {
    return new Promise((resolve, reject) => {
      if (DbUtilsDemo.rdbStore) {
        let predicates = new relationalStore.RdbPredicates(this.TABNAME)
        //   选择表操作
        predicates.equalTo('name', name)
        //   todo 进行等值比较
        DbUtilsDemo.rdbStore?.delete(predicates, (err, rows: number) => {
          if (err) {
            console.log(`sql删除失败.Code:${err.code},message:${err.message}`)
            reject(err)
          } else {
            console.log(`sql删除成功:${rows}`)
            resolve(rows)
          }
        })
      }
    })
  }


  //
  // /**
  //  * 查询函数
  //  * 参数不填则全局查询默认100条,按照更新时间降序
  //  *
  //  *
  //  * date为日期 格式 '2024-03-12 15:30:12'
  //  *
  //  *
  //  *
  //  *
  //  *
  //  * @param limit
  //  * @returns
  //  *
  //  * */
  query(limit = 100): Promise<History[]> {
    let historys: Array<History> = []
    return new Promise((resolve, reject) => {
      // predicates.orderByDesc("UPDATE_TIME")
      if (DbUtilsDemo.rdbStore) {
        let predicates = new relationalStore.RdbPredicates(this.TABNAME);
        predicates.limitAs(limit)
        // 单次查询最多100条数据
        // predicates.orderByDesc("update_time")
        // 表示以那个属性进行倒序
        predicates.distinct()
        // 表示去重
        DbUtilsDemo.rdbStore.query(predicates, (err, resultSet) => {
          if (err) {
            console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
            reject(err)
          } else {
            console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
            // resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。
            while (resultSet.goToNextRow()) {
              const id = resultSet.getLong(resultSet.getColumnIndex('id'));
              const nickName = resultSet.getString(resultSet.getColumnIndex('nickname'));
              const avatar = resultSet.getString(resultSet.getColumnIndex('avatar'));
              const cover = resultSet.getString(resultSet.getColumnIndex('cover'));
              const likedCount = resultSet.getString(resultSet.getColumnIndex('likedCount'));
              const displayTitle = resultSet.getString(resultSet.getColumnIndex('displayTitle'));
              historys.push({
                nickName: nickName,
                avatar: avatar,
                cover: cover,
                likedCount: likedCount,
                displayTitle: displayTitle
              })
            }
            // 释放数据集的内存
            console.log("history记录信息", JSON.stringify(historys))
            resultSet.close();
            resolve(historys)
          }
        })

      }
      // resolve(historys)
    })
  }

  // /**
  //  * record参数类型为
  //  * 根据id来更新数据
  //  * @param record
  //  * @returns
  //  **/
  update(record: History, name: string): Promise<number> {
    return new Promise((resolve, reject) => {

      if (DbUtilsDemo.rdbStore) {
        let valueRecord = this.valueBucket(record)
        let predicates = new relationalStore.RdbPredicates(this.TABNAME); // 创建表的predicates
        predicates.equalTo('name', name); // 匹配表'name'的字段
        DbUtilsDemo.rdbStore?.update(valueRecord, predicates, (err, rows: number) => {
          if (err) {
            console.error(`更新失败 data. Code:${err.code}, message:${err.message}`);
            reject(err)
          } else {
            console.info(`更新成功 in updating data. row count: ${rows}`);
            resolve(rows)
          }

        })
      }
    })
  }
}
SQLPage.ets
```typescript import { History } from '../../common/History'; import { HistoryRecordModel } from '../../common/HistoryRecordModel'; import { it } from '../day05/item_cmp'; import { WaterFlowItem } from '../day08/das';

@Entry
@Component
struct SQLPage {
@State message: string = ‘Hello World’;
@State historys: Array = []

aboutToAppear(): void {

}

build() {
Column() {
Text()
Button(‘添加sqlite数据’)
.onClick(async () => {
await HistoryRecordModel.getInstance().insert({ name: ‘小黑子’, pd: ‘123456’ })
})
Button(‘删除sqlite数据’)
.onClick(async () => {
await HistoryRecordModel.getInstance().deleteById(‘小黑子’)
})
Button(‘查询sqlite数据’)
.onClick(async (event: ClickEvent) => {
let items = await HistoryRecordModel.getInstance().query(10)
items.forEach((value: WaterFlowItem) => {
this.historys.push({ name: value.nickName, pd: value.cover })
})
// this.historys = await HistoryRecordModel.getInstance().query(10)
})

  ForEach(this.historys, (item: History) => {
    Text(`id:${item.name} pd: ${item.pd}`)
  })
}
.height('100%')
.width('100%')

}
}

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cf233ebd253f4a548b3d697457a379be.jpeg)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐