提交 dd72ec84 编写于 作者: J jiangminsen

Merge remote-tracking branch 'upstream/master'

......@@ -39,21 +39,47 @@
- [app对象内部结构](quick-start/app-structure.md)
- [deviceConfig内部结构](quick-start/deviceconfig-structure.md)
- [module对象内部结构](quick-start/module-structure.md)
- [资源分类与访问](quick-start/resource-categories-and-access.md)
- [资源分类与访问](quick-start/resource-categories-and-access.md)
- 学习ArkTS语言
- [初识ArkTS语言](quick-start/arkts-get-started.md)
- ArkTS语法(声明式UI)
- [基本UI描述](quick-start/arkts-basic-ui-description.md)
- 状态管理
- [基本概念](quick-start/arkts-state-mgmt-concepts.md)
- [页面级变量的状态管理](quick-start/arkts-state-mgmt-page-level.md)
- [应用级变量的状态管理](quick-start/arkts-state-mgmt-application-level.md)
- [动态构建UI元素](quick-start/arkts-dynamic-ui-elememt-building.md)
- [渲染控制](quick-start/arkts-rendering-control.md)
- [使用限制与扩展](quick-start/arkts-restrictions-and-extensions.md)
- 基本语法
- [基本语法概述](quick-start/arkts-basic-syntax-overview.md)
- [声明式UI描述](quick-start/arkts-declarative-ui-description.md)
- 自定义组件
- [创建自定义组件](quick-start/arkts-create-custom-components.md)
- [页面和自定义组件生命周期](quick-start/arkts-page-custom-components-lifecycle.md)
- [\@Builder:自定义构建函数](quick-start/arkts-builder.md)
- [\@BuilderParam:引用\@Builder函数](quick-start/arkts-builderparam.md)
- [\@Styles:定义组件重用样式](quick-start/arkts-style.md)
- [\@Extend:定义扩展组件样式](quick-start/arkts-extend.md)
- [stateStyles:多态样式](quick-start/arkts-statestyles.md)
- 状态管理
- [状态管理概述](quick-start/arkts-state-management-overview.md)
- 管理组件拥有的状态
- [\@State:组件内状态](quick-start/arkts-state.md)
- [\@Prop:父子单向同步](quick-start/arkts-prop.md)
- [\@Link:父子双向同步](quick-start/arkts-link.md)
- [\@Provide和\@Consume:与后代组件双向同步](quick-start/arkts-provide-and-consume.md)
- [\@Observed和\@ObjectLink:嵌套类对象属性变化](quick-start/arkts-observed-and-objectlink.md)
- 管理应用拥有的状态
- [管理应用拥有的状态概述](quick-start/arkts-application-state-management-overview.md)
- [LocalStorage:页面级UI状态存储](quick-start/arkts-localstorage.md)
- [AppStorage:应用全局的UI状态存储](quick-start/arkts-appstorage.md)
- [PersistentStorage:持久化存储UI状态](quick-start/arkts-persiststorage.md)
- [Environment:设备环境查询](quick-start/arkts-environment.md)
- 其他状态管理
- [其他状态管理概述](quick-start/arkts-other-state-mgmt-functions-overview.md)
- [\@Watch:状态变量更改通知](quick-start/arkts-watch.md)
- [$$语法:内置组件双向同步](quick-start/arkts-two-way-sync.md)
- 渲染控制
- [渲染控制概述](quick-start/arkts-rendering-control-overview.md)
- [if/else:条件渲染](quick-start/arkts-rendering-control-ifelse.md)
- [ForEach:循环渲染](quick-start/arkts-rendering-control-foreach.md)
- [LazyForEach:数据懒加载](quick-start/arkts-rendering-control-lazyforeach.md)
- 开发
- [应用模型](application-models/Readme-CN.md)
- [UI开发](ui/Readme-CN.md)
- [Web](web/Readme-CN.md)
- [通知](notification/Readme-CN.md)
- [窗口管理](windowmanager/Readme-CN.md)
- [WebGL](webgl/Readme-CN.md)
......
......@@ -24,6 +24,8 @@
所有应用都应该在这两个框架的基础之上进行功能的开发。
在此基础上,还提供了如下功能的开发指导:
- [Web](web/web-component-overview.md)
- [通知](notification/Readme-CN.md)
- [窗口管理](windowmanager/Readme-CN.md)
- [WebGL](webgl/Readme-CN.md)
......@@ -32,16 +34,16 @@
- [网络与连接](connectivity/Readme-CN.md)
- [电话服务](telephony/Readme-CN.md)
- [数据管理](database/Readme-CN.md)
- [文件管理](file-management/Readme-CN.md)
- [任务管理](task-management/Readme-CN.md)
- [设备管理](device/Readme-CN.md)
- [设备使用信息统计](device-usage-statistics/Readme-CN.md)
- [DFX](dfx/Readme-CN.md)
- [国际化](internationalization/Readme-CN.md)
- [应用测试](application-test/Readme-CN.md)
- [一次开发,多端部署](key-features/multi-device-app-dev/Readme-CN.md)
- [IDL工具规格及使用说明书](IDL/idl-guidelines.md)
- [Native API的相关指导](napi/Readme-CN.md)
- [文件管理](file-management/medialibrary-overview.md)
- [一次开发,多端部署](key-features/multi-device-app-dev/foreword.md)
### 工具
......
......@@ -24,15 +24,17 @@
所有应用都应该在这两个框架的基础之上进行功能的开发。
在此基础上,还提供了如下功能的开发指导:
- [Web](web/web-component-overview.md)
- [通知](notification/notification-overview.md)
- [窗口管理](windowmanager/window-overview.md)
- [WebGL](webgl/webgl-overview.md)
- [媒体](media/audio-overview.md)
- [媒体](media/media-application-overview.md)
- [安全](security/userauth-overview.md)
- [网络与连接](connectivity/ipc-rpc-overview.md)
- [电话服务](telephony/telephony-overview.md)
- [数据管理](database/database-mdds-overview.md)
- [文件管理](file-management/medialibrary-overview.md)
- [文件管理](file-management/file-management-overview.md)
- [任务管理](task-management/background-task-overview.md)
- [设备管理](device/usb-overview.md)
- [设备使用信息统计](device-usage-statistics/device-usage-statistics-overview.md)
......
......@@ -17,11 +17,11 @@
- ExtensionAbility组件
- [ExtensionAbility组件概述](extensionability-overview.md)
- [ServiceExtensionAbility](serviceextensionability.md)
- [FormExtensionAbility(服务卡片)](widget-development-stage.md)
- [AccessibilityExtensionAbility](accessibilityextensionability.md)
- [EnterpriseAdminExtensionAbility](enterprise-extensionAbility.md)
- [InputMethodExtensionAbility](inputmethodextentionability.md)
- [WindowExtensionAbility](windowextensionability.md)
- [服务卡片开发指导](widget-development-stage.md)
- [AbilityStage组件容器](abilitystage.md)
- [应用上下文Context](application-context-stage.md)
- 信息传递载体Want
......@@ -47,6 +47,7 @@
- [静态订阅公共事件(仅对系统应用开放)](common-event-static-subscription.md)
- [取消动态订阅公共事件](common-event-unsubscription.md)
- [公共事件发布](common-event-publish.md)
- [移除粘性公共事件](common-event-remove-sticky.md)
- [后台服务](background-services.md)
- 线程间通信
- [线程模型](thread-model-stage.md)
......
# 配置卡片的配置文件
卡片相关的配置文件主要包含FormExtensionAbility的配置和卡片的配置两部分:
1. 卡片需要在[module.json5配置文件](../quick-start/module-configuration-file.md)中的extensionAbilities标签下,配置FormExtensionAbility相关信息。FormExtensionAbility需要填写metadata元信息标签,其中键名称为固定字符串“ohos.extension.form”,资源为卡片的具体配置信息的索引。
配置示例如下:
```json
{
"module": {
...
"extensionAbilities": [
{
"name": "EntryFormAbility",
"srcEntry": "./ets/entryformability/EntryFormAbility.ts",
"label": "$string:EntryFormAbility_label",
"description": "$string:EntryFormAbility_desc",
"type": "form",
"metadata": [
{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}
]
}
]
}
}
```
2. 卡片的具体配置信息。在上述FormExtensionAbility的元信息(“metadata”配置项)中,可以指定卡片具体配置信息的资源索引。例如当resource指定为$profile:form_config时,会使用开发视图的resources/base/profile/目录下的form_config.json作为卡片profile配置文件。内部字段结构说明如下表所示。
**表1** 卡片form_config.json配置文件
| 属性名称 | 含义 | 数据类型 | 是否可缺省 |
| -------- | -------- | -------- | -------- |
| name | 表示卡片的类名,字符串最大长度为127字节。 | 字符串 | 否 |
| description | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | 可缺省,缺省为空。 |
| src | 表示卡片对应的UI代码的完整路径。当为ArkTS卡片时,完整路径需要包含卡片文件的后缀,如:"./ets/widget/pages/WidgetCard.ets"。当为JS卡片时,完整路径无需包含卡片文件的后缀,如:"./js/widget/pages/WidgetCard" | 字符串 | 否 |
| uiSyntax | 表示该卡片的类型,当前支持如下两种类型:<br/>-&nbsp;arkts:当前卡片为ArkTS卡片。<br/>-&nbsp;hml:当前卡片为JS卡片。 | 字符串 | 可缺省,缺省值为hml |
| window | 用于定义与显示窗口相关的配置。 | 对象 | 可缺省 |
| isDefault | 表示该卡片是否为默认卡片,每个UIAbility有且只有一个默认卡片。<br/>-&nbsp;true:默认卡片。<br/>-&nbsp;false:非默认卡片。 | 布尔值 | 否 |
| colorMode | 表示卡片的主题样式,取值范围如下:<br/>-&nbsp;auto:自适应。<br/>-&nbsp;dark:深色主题。<br/>-&nbsp;light:浅色主题。 | 字符串 | 可缺省,缺省值为“auto”。 |
| supportDimensions | 表示卡片支持的外观规格,取值范围:<br/>-&nbsp;1&nbsp;\*&nbsp;2:表示1行2列的二宫格。<br/>-&nbsp;2&nbsp;\*&nbsp;2:表示2行2列的四宫格。<br/>-&nbsp;2&nbsp;\*&nbsp;4:表示2行4列的八宫格。<br/>-&nbsp;4&nbsp;\*&nbsp;4:表示4行4列的十六宫格。 | 字符串数组 | 否 |
| defaultDimension | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 | 字符串 | 否 |
| updateEnabled | 表示卡片是否支持周期性刷新(包含定时刷新和定点刷新),取值范围:<br/>-&nbsp;true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,当两者同时配置时,定时刷新优先生效。<br/>-&nbsp;false:表示不支持周期性刷新。 | 布尔类型 | 否 |
| scheduledUpdateTime | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。<br/>&gt;&nbsp;**说明:**<br/>&gt;&nbsp;updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 字符串 | 可缺省,缺省时不进行定点刷新。 |
| updateDuration | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。<br/>当取值为0时,表示该参数不生效。<br/>当取值为正整数N时,表示刷新周期为30\*N分钟。<br/>&gt;&nbsp;**说明:**<br/>&gt;&nbsp;updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 数值 | 可缺省,缺省值为“0”。 |
| formConfigAbility | 表示卡片的配置跳转链接,采用URI格式。 | 字符串 | 可缺省,缺省值为空。 |
| formVisibleNotify | 标识是否允许卡片使用卡片可见性通知。 | 字符串 | 可缺省,缺省值为空。 |
| metadata | 表示卡片的自定义信息,包含customizeData数组标签。 | 对象 | 可缺省,缺省值为空。 |
配置示例如下:
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": [
"2*2"
]
}
]
}
```
# 使用方刷新卡片内容(仅对系统应用开放)
当使用方添加了一些周期性刷新的卡片后,由于周期性刷新的时间间隔限制,可以在使用方中提供按钮主动触发卡片的刷新。这种场景下使用方可以通过调用[requestForm](../reference/apis/js-apis-app-form-formHost.md#requestform)接口请求卡片刷新,系统会调用卡片提供方FormExtensionAbility中的[onUpdateForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#onupdateform)生命周期回调,在回调中,可以使用[updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform)接口刷新卡片内容。onUpdateForm生命周期回调参考[通过FormExtensionAbility刷新卡片内容](arkts-ui-widget-event-formextensionability.md)
```ts
import formHost from '@ohos.app.form.formHost';
@Entry()
@Component
struct WidgetCard {
formId = ...; // 卡片ID
build() {
Button(`刷新卡片`)
.type(ButtonType.Capsule)
.width('50%')
.height(50)
.onClick(() => {
console.info('FormAbility update form click');
// formId需要为实际需要刷新的卡片ID
formHost.requestForm(this.formId.toString()).then(() => {
console.info('Succeeded in requestForming.');
});
})
...
}
}
```
# 创建一个ArkTS卡片
开发者可以使用IDE创建ArkTS卡片,具体步骤请参见[DevEco Studio服务卡片开发指南](https://gitee.com/link?target=https%3A%2F%2Fdeveloper.harmonyos.com%2Fcn%2Fdocs%2Fdocumentation%2Fdoc-guides%2Fohos-development-service-widget-0000001263280425)。使用IDE生成的卡片模板包括三个部分:卡片页面(WidgetCard.ets)、卡片生命周期管理(FormExtensionAbility)和卡片配置文件(form_config.json)。
在选择卡片的开发语言类型(Language)时,需要选择ArkTS选项,如下图所示。
![WidgetCreateProject](figures/WidgetCreateProject.png)
# 通过FormExtensionAbility刷新卡片内容
在卡片页面中可以通过**postCardAction**接口触发message事件至FormExtensionAbility,然后由FormExtensionAbility刷新卡片内容,下面是这种刷新方式的简单示例。
- 在卡片页面通过注册Button的onClick点击事件回调,并在回调中调用**postCardAction**接口触发事件至FormExtensionAbility
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('title') title: string = 'init';
@LocalStorageProp('detail') detail: string = 'init';
build() {
Column() {
Button('刷新')
.onClick(() => {
postCardAction(this, {
'action': 'message',
'params': {
'msgTest': 'messageEvent'
}
});
})
Text(`${this.title}`)
Text(`${this.detail}`)
}
.width('100%')
.height('100%')
}
}
```
- 在FormExtensionAbility的onFormEvent生命周期中调用[updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform)接口刷新卡片
```ts
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
onFormEvent(formId, message) {
// Called when a specified message event defined by the form provider is triggered.
console.info(`FormAbility onEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);
let formData = {
'title': 'Title Update Success.', // 和卡片布局中对应
'detail': 'Detail Update Success.', // 和卡片布局中对应
};
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
}
// ...
}
```
运行效果如下图所示。
![WidgetUpdatePage](figures/WidgetUpdatePage.png)
# 卡片事件能力说明
ArkTS卡片中提供了postCardAction()接口用于卡片内部和提供方应用间的交互,当前支持router、message和call三种类型的事件,仅在卡片中可以调用。
![WidgetPostCardAction](figures/WidgetPostCardAction.png)
接口定义:postCardAction(component: Object, action: Object): void
接口参数说明:
| **参数名** | **参数类型** | **必填** | **参数描述** |
| -------- | -------- | -------- | -------- |
| component | Object | 是 | 当前自定义组件的实例,通常传入this。 |
| action | Object | 是 | action的具体描述,详情见下表。 |
action参数说明:
| **Key** | **Value** | **样例描述** |
| -------- | -------- | -------- |
| "action" | string | action的类型,支持三种预定义的类型:<br/>-&nbsp;"router":应用跳转,触发后会跳转到对应UIAbility,仅允许跳转到当前应用的UIAbility。<br/>-&nbsp;"message":自定义消息,触发后会调用提供方FormExtensionAbility的[onFormEvent()](../reference/apis/js-apis-app-form-formExtensionAbility.md#onformevent)生命周期回调。<br/>-&nbsp;"call":应用非前台启动,触发后会启动对应的UIAbility,但不会调度到前台,call的目标应用需要具备后台运行权限([ohos.permission.KEEP_BACKGROUND_RUNNING](../security/permission-list.md#ohospermissionkeep_background_running))。 |
| "bundleName" | string | "router"&nbsp;/&nbsp;"call"&nbsp;类型时跳转的包名,可选。 |
| "moduleName" | string | "router"&nbsp;/&nbsp;"call"&nbsp;类型时跳转的模块名,可选。 |
| "abilityName" | string | "router"&nbsp;/&nbsp;"call"&nbsp;类型时跳转的UIAbility名,必填。 |
| "params" | Object | 当前action携带的额外参数,内容使用JSON格式的键值对形式。 |
postCardAction()接口示例代码:
```typescript
Button('跳转')
.width('40%')
.height('20%')
.onClick(() => {
postCardAction(this, {
'action': 'router',
'bundleName': 'com.example.myapplication',
'abilityName': 'EntryAbility',
'params': {
'message': 'testForRouter' // 自定义要发送的message
}
});
})
```
以下是卡片开发过程中可以通过卡片事件实现的典型开发场景:
- [通过FormExtensionAbility刷新卡片内容](arkts-ui-widget-event-formextensionability.md)
- [通过UIAbility刷新卡片内容](arkts-ui-widget-event-uiability.md)
- [使用router事件跳转到指定页面](arkts-ui-widget-event-router.md)
# 使用router事件跳转到指定页面
在卡片中使用**postCardAction**接口的router能力,能够快速拉起卡片提供方应用,因此页面较多的应用往往会通过卡片提供不同的跳转按钮,实现一键直达的效果。例如相机卡片,卡片上提供拍照、录像等按钮,点击不同按钮将拉起相机应用的不同页面,从而提高用户的体验。
![WidgerCameraCard](figures/WidgerCameraCard.png)
通常使用按钮控件来实现页面拉起,示例代码如下:
- 在卡片页面中布局两个按钮,点击其中一个按钮时调用postCardAction向指定UIAbility发送router事件,并在事件内定义需要传递的内容。
```ts
@Entry
@Component
struct WidgetCard {
build() {
Column() {
Button('功能A')
.margin('20%')
.onClick(() => {
console.info('Jump to EntryAbility funA');
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // 只能跳转到当前应用下的UIAbility
'params': {
'targetPage': 'funA' // 在EntryAbility中处理这个信息
}
});
})
Button('功能B')
.margin('20%')
.onClick(() => {
console.info('Jump to EntryAbility funB');
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // 只能跳转到当前应用下的UIAbility
'params': {
'targetPage': 'funB' // 在EntryAbility中处理这个信息
}
});
})
}
.width('100%')
.height('100%')
}
}
```
- 在UIAbility中接收router事件并获取参数,根据传递的message不同,选择拉起不同的页面。
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
let selectPage = "";
let currentWindowStage = null;
export default class CameraAbility extends UIAbility {
// 如果UIAbility第一次启动,在收到Router事件后会触发onCreate生命周期回调
onCreate(want, launchParam) {
// 获取router事件中传递的targetPage参数
console.info("onCreate want:" + JSON.stringify(want));
if (want.parameters.params !== undefined) {
let params = JSON.parse(want.parameters.params);
console.info("onCreate router targetPage:" + params.targetPage);
selectPage = params.targetPage;
}
}
// 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调
onNewWant(want, launchParam) {
console.info("onNewWant want:" + JSON.stringify(want));
if (want.parameters.params !== undefined) {
let params = JSON.parse(want.parameters.params);
console.info("onNewWant router targetPage:" + params.targetPage);
selectPage = params.targetPage;
}
if (currentWindowStage != null) {
this.onWindowStageCreate(currentWindowStage);
}
}
onWindowStageCreate(windowStage: window.WindowStage) {
let targetPage;
// 根据传递的targetPage不同,选择拉起不同的页面
switch (selectPage) {
case 'funA':
targetPage = 'pages/FunA';
break;
case 'funB':
targetPage = 'pages/FunB';
break;
default:
targetPage = 'pages/Index';
}
if (currentWindowStage === null) {
currentWindowStage = windowStage;
}
windowStage.loadContent(targetPage, (err, data) => {
if (err && err.code) {
console.info('Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
});
}
};
```
# 通过UIAbility刷新卡片内容
在卡片页面中可以通过**postCardAction**接口触发router事件或者call事件拉起UIAbility,然后由UIAbility刷新卡片内容,下面是这种刷新方式的简单示例。
- 在卡片页面通过注册Button的onClick点击事件回调,并在回调中调用**postCardAction**接口触发事件至FormExtensionAbility。
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('detail') detail: string = 'init';
build() {
Column() {
Button('跳转')
.margin('20%')
.onClick(() => {
console.info('postCardAction to EntryAbility');
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // 只能跳转到当前应用下的UIAbility
'params': {
'detail': 'RouterFromCard'
}
});
})
Text(`${this.detail}`).margin('20%')
}
.width('100%')
.height('100%')
}
}
```
- 在UIAbility的onCreate()或者onNewWant()生命周期中可以通过入参want获取卡片的formID和传递过来的参数信息,然后调用[updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform)接口刷新卡片。
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import formInfo from '@ohos.app.form.formInfo';
export default class EntryAbility extends UIAbility {
// 如果UIAbility第一次启动,在收到Router事件后会触发onCreate生命周期回调
onCreate(want, launchParam) {
console.info('Want:' + JSON.stringify(want));
if (want.parameters[formInfo.FormParam.IDENTITY_KEY] !== undefined) {
let curFormId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
let message = JSON.parse(want.parameters.params).detail;
console.info(`UpdateForm formId: ${curFormId}, message: ${message}`);
let formData = {
"detail": message + ': onCreate UIAbility.', // 和卡片布局中对应
};
let formMsg = formBindingData.createFormBindingData(formData)
formProvider.updateForm(curFormId, formMsg).then((data) => {
console.info('updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('updateForm failed:' + JSON.stringify(error));
})
}
}
// 如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调
onNewWant(want, launchParam) {
console.info('onNewWant Want:' + JSON.stringify(want));
if (want.parameters[formInfo.FormParam.IDENTITY_KEY] !== undefined) {
let curFormId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
let message = JSON.parse(want.parameters.params).detail;
console.info(`UpdateForm formId: ${curFormId}, message: ${message}`);
let formData = {
"detail": message + ': onNewWant UIAbility.', // 和卡片布局中对应
};
let formMsg = formBindingData.createFormBindingData(formData)
formProvider.updateForm(curFormId, formMsg).then((data) => {
console.info('updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('updateForm failed:' + JSON.stringify(error));
})
}
}
...
}
```
# 刷新本地图片和网络图片
在卡片上通常需要展示本地图片或从网络上下载的图片,获取本地图片和网络图片需要通过FormExtensionAbility来实现,如下示例代码介绍了如何在卡片上显示本地图片和网络图片。
1. 下载网络图片需要使用到网络能力,需要申请ohos.permission.INTERNET权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md)
2. 在EntryFormAbility中的onAddForm生命周期回调中实现本地文件的刷新
```ts
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import request from '@ohos.request';
import fs from '@ohos.file.fs';
export default class EntryFormAbility extends FormExtensionAbility {
...
// 在添加卡片时,打开一个本地图片并将图片内容传递给卡片页面显示
onAddForm(want) {
// 假设在当前卡片应用的tmp目录下有一个本地图片:head.PNG
let tempDir = this.context.getApplicationContext().tempDir;
// 打开本地图片并获取其打开后的fd
let file;
try {
file = fs.openSync(tempDir + '/' + 'head.PNG');
} catch (e) {
console.error(`openSync failed: ${JSON.stringify(e)}`);
}
let formData = {
'text': 'Image: Bear',
'imgName': 'imgBear',
'formImages': {
'imgBear': file.fd
},
'loaded': true
}
// 将fd封装在formData中并返回至卡片页面
return formBindingData.createFormBindingData(formData);
}
...
}
```
3. 在EntryFormAbility中的onFormEvent生命周期回调中实现网络文件的刷新
```ts
import formBindingData from '@ohos.app.form.formBindingData';
import formProvider from '@ohos.app.form.formProvider';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import request from '@ohos.request';
import fs from '@ohos.file.fs';
export default class EntryFormAbility extends FormExtensionAbility {
// 在卡片页面触发message事件时,下载一个网络图片,并将网络图片内容传递给卡片页面显示
onFormEvent(formId, message) {
let formInfo = formBindingData.createFormBindingData({
'text': '刷新中...'
})
formProvider.updateForm(formId, formInfo)
// 注意:FormExtensionAbility在触发生命周期回调时被拉起,仅能在后台存在5秒
// 建议下载能快速下载完成的小文件,如在5秒内未下载完成,则此次网络图片无法刷新至卡片页面上
let netFile = 'https://xxxx/xxxx.png'; // 需要在此处使用真实的网络图片下载链接
let tempDir = this.context.getApplicationContext().tempDir;
let tmpFile = tempDir + '/file' + Date.now();
request.downloadFile(this.context, {
url: netFile, filePath: tmpFile
}).then((task) => {
task.on('complete', function callback() {
console.info('ArkTSCard download complete:' + tmpFile);
let file;
try {
file = fs.openSync(tmpFile);
} catch (e) {
console.error(`openSync failed: ${JSON.stringify(e)}`);
}
let formData = {
'text': 'Image: Https',
'imgName': 'imgHttps',
'formImages': {
'imgHttps': file.fd
},
'loaded': true
}
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
})
task.on('fail', function callBack(err) {
console.info('ArkTSCard download task failed. Cause:' + err);
let formInfo = formBindingData.createFormBindingData({
'text': '刷新失败'
})
formProvider.updateForm(formId, formInfo)
});
}).catch((err) => {
console.error('Failed to request the download. Cause: ' + JSON.stringify(err));
});
}
...
};
```
4. 在卡片页面通过Image组件展示EntryFormAbility传递过来的卡片内容。
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('text') text: string = '加载中...';
@LocalStorageProp('loaded') loaded: boolean = false;
@LocalStorageProp('imgName') imgName: string = 'name';
build() {
Column() {
Text(this.text)
.fontSize('12vp')
.textAlign(TextAlign.Center)
.width('100%')
.height('15%')
Row() {
if (this.loaded) {
Image('memory://' + this.imgName)
.width('50%')
.height('50%')
.margin('5%')
} else {
Image('common/start.PNG')
.width('50%')
.height('50%')
.margin('5%')
}
}.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Center)
Button('刷新')
.height('15%')
.onClick(() => {
postCardAction(this, {
'action': 'message',
'params': {
'info': 'refreshImage'
}
});
})
}
.width('100%').height('100%')
.alignItems(HorizontalAlign.Center)
.padding('5%')
}
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - Image组件通过入参(**memory://fileName**)中的**memory://**标识来进行远端内存图片显示,其中**fileName**需要和EntryFormAbility传递对象(**'formImages': {key: fd})**中的**key**相对应。
>
> - Image组件通过传入的参数是否有变化来决定是否刷新图片,因此EntryFormAbility每次传递过来的**imgName**都需要不同,连续传递两个相同的**imgName**时,图片不会刷新。
# 卡片数据交互说明
ArkTS卡片框架提供了updateForm()接口和requestForm()接口主动触发卡片的页面刷新。**(介绍下LocalStorageProp在这个过程中起到的作用)**
![WidgetLocalStorageProp](figures/WidgetLocalStorageProp.png)
| 接口 | 是否系统能力 | 约束 |
| -------- | -------- | -------- |
| updateForm | 否 | 1.&nbsp;提供方调用。<br/>2.&nbsp;提供方仅允许刷新自己的卡片,其他提供方的卡片无法刷新。 |
| requestForm | 是 | 1.&nbsp;使用方调用。<br/>2.&nbsp;仅允许刷新添加到当前使用方的卡片,添加到其他使用方的卡片无法刷新。 |
下面介绍卡片页面刷新的典型场景。
- [定时刷新和定点刷新](arkts-ui-widget-update-by-time.md)
- [刷新本地图片和网络图片](arkts-ui-widget-image-update.md)
- [根据卡片状态刷新不同内容](arkts-ui-widget-update-by-status.md)
- [使用方刷新卡片内容(仅对系统应用开放)](arkts-ui-widget-content-update.md)
\ No newline at end of file
# 卡片生命周期管理
创建ArkTS卡片,需实现[FormExtensionAbility](../reference/apis/js-apis-app-form-formExtensionAbility.md)生命周期接口。
1. 在EntryFormAbility.ts中,导入相关模块。
```ts
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
```
2. 在EntryFormAbility.ts中,实现[FormExtensionAbility](../reference/apis/js-apis-app-form-formExtensionAbility.md)生命周期接口,其中在onAddForm的入参want中可以通过[FormParam](../reference/apis/js-apis-app-form-formInfo.md#formparam)取出卡片的相关信息。
```typescript
import formInfo from '@ohos.app.form.formInfo';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want) {
console.info('[EntryFormAbility] onAddForm');
// 在入参want中可以取出卡片的唯一标识:formId
let formId: string = want.parameters[formInfo.FormParam.IDENTITY_KEY];
// 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
let obj = {
'title': 'titleOnAddForm',
'detail': 'detailOnAddForm'
};
let formData = formBindingData.createFormBindingData(obj);
return formData;
}
onCastToNormalForm(formId) {
// Called when the form provider is notified that a temporary form is successfully
// converted to a normal form.
// 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
console.info(`[EntryFormAbility] onCastToNormalForm, formId: ${formId}`);
}
onUpdateForm(formId) {
// 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
console.info('[EntryFormAbility] onUpdateForm');
let obj = {
'title': 'titleOnUpdateForm',
'detail': 'detailOnUpdateForm'
};
let formData = formBindingData.createFormBindingData(obj);
formProvider.updateForm(formId, formData).catch((err) => {
if (err) {
// 异常分支打印
console.error(`[EntryFormAbility] Failed to updateForm. Code: ${err.code}, message: ${err.message}`);
return;
}
});
}
onChangeFormVisibility(newStatus) {
// Called when the form provider receives form events from the system.
// 需要配置formVisibleNotify为true,且为系统应用才会回调
console.info('[EntryFormAbility] onChangeFormVisibility');
}
onFormEvent(formId, message) {
// Called when a specified message event defined by the form provider is triggered.
// 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
console.info('[EntryFormAbility] onFormEvent');
}
onRemoveForm(formId) {
// Called to notify the form provider that a specified form has been destroyed.
// 当对应的卡片删除时触发的回调,入参是被删除的卡片ID
console.info('[EntryFormAbility] onRemoveForm');
}
onConfigurationUpdate(config) {
// 当系统配置信息置更新时触发的回调
console.info('[EntryFormAbility] configurationUpdate:' + JSON.stringify(config));
}
onAcquireFormState(want) {
// Called to return a {@link FormState} object.
// 卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态。
return formInfo.FormState.READY;
}
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> FormExtensionAbility进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务,在生命周期调度完成后会继续存在5秒,如5秒内没有新的生命周期回调触发则进程自动退出。针对可能需要5秒以上才能完成的业务逻辑,建议[拉起主应用](arkts-ui-widget-event-uiability.md)进行处理,处理完成后使用[updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform)通知卡片进行刷新。
# ArkTS卡片相关模块
**图1** ArkTS卡片相关模块  
![WidgetModules](figures/WidgetModules.png)
- [FormExtensionAbility](../reference/apis/js-apis-app-form-formExtensionAbility.md):卡片扩展模块,提供卡片创建、销毁、刷新等生命周期回调。
- [FormExtensionContext](../reference/apis/js-apis-inner-application-formExtensionContext.md):FormExtensionAbility的上下文环境,提供FormExtensionAbility具有的接口和能力。
- [formProvider](../reference/apis/js-apis-app-form-formProvider.md):提供卡片提供方相关的接口能力,可通过该模块提供接口实现更新卡片、设置卡片更新时间、获取卡片信息、请求发布卡片等。
- [formInfo](../reference/apis/js-apis-app-form-formInfo.md):提供了卡片信息和状态等相关类型和枚举。
- [formBindingData](../reference/apis/js-apis-app-form-formBindingData.md):提供卡片数据绑定的能力,包括FormBindingData对象的创建、相关信息的描述。
- [页面布局(Card.ets)](arkts-ui-widget-page-overview.md):提供声明式范式的UI接口能力。
- [ArkTS卡片特有能力](arkts-ui-widget-event-overview.md):postCardAction用于卡片内部和提供方应用间的交互,仅在卡片中可以调用。
- [ArkTS卡片能力列表](arkts-ui-widget-page-overview.md#arkts卡片支持的页面能力):列举了能在ArkTS卡片中使用的API、组件、事件、属性和生命周期调度。
- [卡片配置](arkts-ui-widget-configuration.md):包含FormExtensionAbility的配置和卡片的配置
-[module.json5配置文件](../quick-start/module-configuration-file.md)中的extensionAbilities标签下,配置FormExtensionAbility相关信息。
- 在resources/base/profile/目录下的[form_config.json配置文件](arkts-ui-widget-configuration.md)中,配置卡片(WidgetCard.ets)相关信息。
# 卡片使用动效能力
ArkTS卡片开放了使用动画效果的能力,支持[显式动画](../reference/arkui-ts/ts-explicit-animation.md)[属性动画](../reference/arkui-ts/ts-animatorproperty.md)[组件内转场](../reference/arkui-ts/ts-transition-animation-component.md)能力。需要注意的是,ArkTS卡片使用动画效果时具有以下限制:
**表1** 动效参数限制
| 名称 | 参数说明 | 限制描述 |
| -------- | -------- | -------- |
| duration | 动画播放时长 | 限制最长的动效播放时长为1秒,当设置大于1秒的时间时,动效时长仍为1秒。 |
| tempo | 动画播放速度 | 卡片中禁止设置此参数,使用默认值1。 |
| delay | 动画延迟执行的时长 | 卡片中禁止设置此参数,使用默认值0。 |
| iterations | 动画播放次数 | 卡片中禁止设置此参数,使用默认值1。 |
以下示例代码实现了按钮旋转的动画效果:
![WidgetAnimation](figures/WidgetAnimation.gif)
```ts
@Entry
@Component
struct AttrAnimationExample {
@State rotateAngle: number = 0;
build() {
Column() {
Button('change rotate angle')
.onClick(() => {
this.rotateAngle = 90;
})
.margin(50)
.rotate({ angle: this.rotateAngle })
.animation({
curve: Curve.EaseOut,
playMode: PlayMode.AlternateReverse
})
}.width('100%').margin({ top: 20 })
}
}
```
# 卡片使用自定义绘制能力
ArkTS卡片开放了自定义绘制的能力,在卡片上可以通过[Canvas](../reference/arkui-ts/ts-components-canvas-canvas.md)组件创建一块画布,然后通过[CanvasRenderingContext2D](../reference/arkui-ts/ts-canvasrenderingcontext2d.md)对象在画布上进行自定义图形的绘制,如下示例代码实现了在画布的中心绘制了一个笑脸。
```typescript
@Entry
@Component
struct Card {
private canvasWidth: number = 0;
private canvasHeight: number = 0;
// 初始化CanvasRenderingContext2D和RenderingContextSettings
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Column() {
Row() {
Canvas(this.context)
.margin('5%')
.width('90%')
.height('90%')
.onReady(() => {
console.info('[ArkTSCard] onReady for canvas draw content');
// 在onReady回调中获取画布的实际宽和高
this.canvasWidth = this.context.width;
this.canvasHeight = this.context.height;
// 绘制画布的背景
this.context.fillStyle = 'rgba(203, 154, 126, 1.00)';
this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
// 在画布的中心绘制一个红色的圆
this.context.beginPath();
let radius = this.context.width / 3
let circleX = this.context.width / 2
let circleY = this.context.height / 2
this.context.moveTo(circleX - radius, circleY);
this.context.arc(circleX, circleY, radius, 2 * Math.PI, 0, true);
this.context.closePath();
this.context.fillStyle = 'red';
this.context.fill();
// 绘制笑脸的左眼
let leftR = radius / 4
let leftX = circleX - (radius / 2)
let leftY = circleY - (radius / 3.5)
this.context.beginPath();
this.context.arc(leftX, leftY, leftR, 0, Math.PI, true);
this.context.strokeStyle = '#ffff00'
this.context.lineWidth = 10
this.context.stroke()
// 绘制笑脸的右眼
let rightR = radius / 4
let rightX = circleX + (radius / 2)
let rightY = circleY - (radius / 3.5)
this.context.beginPath();
this.context.arc(rightX, rightY, rightR, 0, Math.PI, true);
this.context.strokeStyle = '#ffff00'
this.context.lineWidth = 10
this.context.stroke()
// 绘制笑脸的嘴巴
let mouthR = radius / 2.5
let mouthX = circleX
let mouthY = circleY + (radius / 3)
this.context.beginPath();
this.context.arc(mouthX, mouthY, mouthR, Math.PI, 0, true);
this.context.strokeStyle = '#ffff00'
this.context.lineWidth = 10
this.context.stroke()
})
}
}.height('100%').width('100%')
}
}
```
运行效果如下图所示。
![WidgetCanvasDemo](figures/WidgetCanvasDemo.jpeg)
# 卡片页面能力说明
开发者可以使用声明式范式开发ArkTS卡片页面。如下卡片页面由DevEco Studio模板自动生成,开发者可以根据自身的业务场景进行调整。
![WidgetPreviewPage](figures/WidgetPreviewPage.png)
ArkTS卡片具备JS卡片的全量能力,并且新增了动效能力和自定义绘制的能力,支持[声明式范式](../reference/arkui-ts/Readme-CN.md)的部分组件、事件、动效、数据管理、状态管理能力,详见“[ArkTS卡片支持的页面能力](#arkts卡片支持的页面能力)”。
## ArkTS卡片支持的页面能力
ArkTS卡片支持的页面能力如下,详细介绍请参见[ArkTS声明式开发范式API参考](../reference/arkui-ts/Readme-CN.md)
在这些能力中,只有标识“支持在ArkTS卡片中使用”的具体能力可用于ArkTS卡片,同时请留意卡片场景下的能力差异说明。
- 属性动画
- 显式动画
- 组件内转场
- 像素单位
- Blank组件
- Button组件
- Checkbox组件
- CheckboxGroup组件
- DataPanel组件
- Divider组件
- Gauge组件
- Image组件
- LoadingProgress组件
- Marquee组件
- Progress组件
- Qrcode组件
- Radio组件
- Rating组件
- Slider组件
- Span组件
- Text组件
- Toggle组件
- Canvas绘制上下文对象
- Canvas组件
- 渐变对象
- ImageBitmap对象
- ImageData对象
- Path2D对象
- ForEach组件
- Badge容器组件
- Column容器组件
- Counter容器组件
- Flex容器组件
- GridCol容器组件
- GridRow容器组件
- List容器组件
- ListItem容器组件
- RelativeContainer容器组件
- Row容器组件
- Stack容器组件
- Circle绘制组件
- Ellipse绘制组件
- Line绘制组件
- Path绘制组件
- Polygon绘制组件
- Polyline绘制组件
- Rect绘制组件
- Shape绘制组件
- Background通用属性
- BackgroundBlurStyle通用属性
- BorderImage通用属性
- Border通用属性
- ComponentId通用属性
- Enable通用属性
- FlexLayout通用属性
- GradientColor通用属性
- ImageEffect通用属性
- LayoutConstraints通用属性
- Location通用属性
- Opacity通用属性
- Overlay通用属性
- PolymorphicStyle通用属性
- SharpClipping通用属性
- Size通用属性
- Touch-target通用属性
- Transformation通用属性
- Visibility通用属性
- ZOrder通用属性
- 点击事件
- 挂载卸载事件
- 组件生命周期
- 状态管理
# 根据卡片状态刷新不同内容
相同的卡片可以添加到桌面上实现不同的功能,比如添加两张桌面的卡片,一张显示杭州的天气,一张显示北京的天气,设置每天早上7点触发定时刷新,卡片需要感知当前的配置是杭州还是北京,然后将对应城市的天气信息刷新到卡片上,以下示例介绍了如何根据卡片的状态动态选择需要刷新的内容。
- 卡片配置文件:配置每天早上7点触发定时刷新
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,"scheduledUpdateTime": "07:00",
"updateDuration": 0,
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
```
- 卡片页面:卡片具备不同的状态选择,在不同的状态下需要刷新不同的内容,因此在状态发生变化时通过postCardAction通知EntryFormAbility。
```ts
let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
@LocalStorageProp('textA') textA: string = '待刷新...';
@LocalStorageProp('textB') textB: string = '待刷新...';
@State selectA: boolean = false;
@State selectB: boolean = false;
build() {
Column() {
Row() {
Checkbox({ name: 'checkbox1', group: 'checkboxGroup' })
.select(false)
.onChange((value: boolean) => {
this.selectA = value;
postCardAction(this, {
'action': 'message',
'params': {
'selectA': JSON.stringify(value)
}
});
})
Text('状态A')
}
Row() {
Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
.select(false)
.onChange((value: boolean) => {
this.selectB = value;
postCardAction(this, {
'action': 'message',
'params': {
'selectB': JSON.stringify(value)
}
});
})
Text('状态B')
}
Row() { // 选中状态A才会进行刷新的内容
Text('状态A: ')
Text(this.textA)
}
Row() { // 选中状态B才会进行刷新的内容
Text('状态B: ')
Text(this.textB)
}
}.padding('10%')
}
}
```
- EntryFormAbility:将卡片的状态存储在本地数据库中,在刷新事件回调触发时,通过formId获取当前卡片的状态,然后根据卡片的状态选择不同的刷新内容。
```ts
import formInfo from '@ohos.app.form.formInfo'
import formProvider from '@ohos.app.form.formProvider';
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import dataStorage from '@ohos.data.storage'
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want) {
let formId = want.parameters[formInfo.FormParam.IDENTITY_KEY];
let isTempCard: boolean = want.parameters[formInfo.FormParam.TEMPORARY_KEY];
if (isTempCard === false) { // 如果为常态卡片,直接进行信息持久化
console.info('Not temp card, init db for:' + formId);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
storeDB.putSync('A' + formId, 'false');
storeDB.putSync('B' + formId, 'false');
storeDB.flushSync();
}
let formData = {};
return formBindingData.createFormBindingData(formData);
}
onRemoveForm(formId) {
console.info('onRemoveForm, formId:' + formId);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
storeDB.deleteSync('A' + formId);
storeDB.deleteSync('B' + formId);
}
// 如果在添加时为临时卡片,则建议转为常态卡片时进行信息持久化
onCastToNormalForm(formId) {
console.info('onCastToNormalForm, formId:' + formId);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
storeDB.putSync('A' + formId, 'false');
storeDB.putSync('B' + formId, 'false');
storeDB.flushSync();
}
onUpdateForm(formId) {
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
let stateA = storeDB.getSync('A' + formId, 'false').toString()
let stateB = storeDB.getSync('B' + formId, 'false').toString()
// A状态选中则更新textA
if (stateA === 'true') {
let formInfo = formBindingData.createFormBindingData({
'textA': 'AAA'
})
formProvider.updateForm(formId, formInfo)
}
// B状态选中则更新textB
if (stateB === 'true') {
let formInfo = formBindingData.createFormBindingData({
'textB': 'BBB'
})
formProvider.updateForm(formId, formInfo)
}
}
onFormEvent(formId, message) {
// 存放卡片状态
console.info('onFormEvent formId:' + formId + 'msg:' + message);
let storeDB = dataStorage.getStorageSync(this.context.filesDir + 'myStore')
let msg = JSON.parse(message)
if (msg.selectA != undefined) {
console.info('onFormEvent selectA info:' + msg.selectA);
storeDB.putSync('A' + formId, msg.selectA);
}
if (msg.selectB != undefined) {
console.info('onFormEvent selectB info:' + msg.selectB);
storeDB.putSync('B' + formId, msg.selectB);
}
storeDB.flushSync();
}
};
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 通过本地数据库进行卡片信息的持久化时,建议先在[**onAddForm**](../reference/apis/js-apis-app-form-formExtensionAbility.md#onaddform)生命周期中通过[**TEMPORARY_KEY**](../reference/apis/js-apis-app-form-formInfo.md#formparam)判断当前添加的卡片是否为常态卡片:如果是常态卡片,则直接进行卡片信息持久化;如果为临时卡片,则可以在卡片转为常态卡片(**[onCastToNormalForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#oncasttonormalform)**)时进行持久化;同时需要在卡片销毁(**[onRemoveForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#onremoveform)**)时删除当前卡片存储的持久化信息,避免反复添加删除卡片导致数据库文件持续变大。
# 定时刷新和定点刷新
当前卡片框架提供了如下几种按时间刷新卡片的方式:
- 定时刷新:表示每隔一段时间刷新卡片内容,在form_config.json文件中配置,详见[updateDuration](arkts-ui-widget-configuration.md)字段。例如,每小时刷新一次卡片内容。注意:updateDuration(定时刷新)优先级比scheduledUpdateTime(定点刷新)高,配置定时刷新后,定点刷新将失效。
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true, // 使能刷新功能
"scheduledUpdateTime": "10:30",
"updateDuration": 2, // 设置卡片定时刷新的更新周期(单位为30分钟,取值为自然数)
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
```
- 定点刷新:表示每天在某个时间点刷新,在form_config.json文件中配置,详见[scheduledUpdateTime](arkts-ui-widget-configuration.md)字段。例如,每天在10:30更新卡片内容。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 当同时配置了定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)时,定时刷新的优先级更高。如果想要配置定点刷新,则需要将updateDuration配置为0。
```json
{
"forms": [
{
"name": "widget",
"description": "This is a service widget.",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true, // 使能刷新功能
"scheduledUpdateTime": "10:30", // 设置卡片的定点刷新的时刻
"updateDuration": 0,
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
```
- 下次刷新:通过[setFormNextRefreshTime](../reference/apis/js-apis-app-form-formProvider.md#setformnextrefreshtime)接口指定卡片的下一次刷新时间(最短时间5分钟),例如,在接口调用的5分钟后刷新卡片内容。
```ts
import formProvider from '@ohos.app.form.formProvider';
let formId = '123456789'; // 实际业务场景需要使用正确的formId
try {
// 设置过5分钟后更新卡片内容
formProvider.setFormNextRefreshTime(formId, 5, (err, data) => {
if (err) {
console.error(`Failed to setFormNextRefreshTime. Code: ${err.code}, message: ${err.message}`);
return;
} else {
console.info('Succeeded in setFormNextRefreshTimeing.');
}
});
} catch (err) {
console.error(`Failed to setFormNextRefreshTime. Code: ${err.code}, message: ${err.message}`);
}
```
在触发定时、定点或主动刷新后,系统会调用FormExtensionAbility的[onUpdateForm](../reference/apis/js-apis-app-form-formExtensionAbility.md#onupdateform)生命周期回调,在回调中,可以使用[updateForm](../reference/apis/js-apis-app-form-formProvider.md#updateform)进行提供方刷新卡片。onUpdateForm生命周期回调参考[通过FormExtensionAbility刷新卡片内容](arkts-ui-widget-event-formextensionability.md)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 1. 定时刷新有配额限制,每张卡片每天最多通过定时方式触发刷新50次,定时刷新包含[卡片配置项updateDuration](arkts-ui-widget-configuration.md)和调用[setFormNextRefreshTime](../reference/apis/js-apis-app-form-formProvider.md#setformnextrefreshtime)两种,当达到50次配额后,无法通过定时方式再次触发刷新,刷新次数会在每天的0点重置。
>
> 2. 当前定时刷新使用同一个计时器进行计时,因此卡片定时刷新的第一次刷新会有最多30分钟的偏差。比如第一张卡片A(每隔半小时刷新一次)在3点20分添加成功,定时器启动并每隔半小时触发一次事件,第二张卡片B(每隔半小时刷新一次)在3点40分添加成功,在3点50分定时器事件触发时,卡片A触发定时刷新,卡片B会在下次事件(4点20分)中才会触发。
>
> 3. 定时刷新和定点刷新仅在屏幕亮屏情况下才会触发,在灭屏场景下仅会将记录刷新动作,待亮屏时统一进行刷新。
# ArkTS卡片运行机制
## 实现原理
**图1** ArkTS卡片实现原理  
![WidgetPrinciple](figures/WidgetPrinciple.png)
- 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置,当前仅系统应用可以作为卡片使用方。
- 卡片提供方:提供卡片显示内容的应用,控制卡片的显示内容、控件布局以及控件点击事件。
- 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,提供[formProvider](../reference/apis/js-apis-app-form-formProvider.md)[formHost](../reference/apis/js-apis-app-form-formHost.md)的接口能力,同时提供卡片对象的管理与使用以及卡片周期性刷新等能力。
- 卡片渲染服务:用于管理卡片渲染实例,渲染实例与卡片使用方上的[卡片组件](../reference/arkui-ts/ts-basic-components-formcomponent.md)一一绑定。卡片渲染服务运行卡片页面代码widgets.abc进行渲染,并将渲染后的数据发送至卡片使用方对应的[卡片组件](../reference/arkui-ts/ts-basic-components-formcomponent.md)
**图2** ArkTS卡片渲染服务运行原理  
![WidgetRender](figures/WidgetRender.png)
与JS卡片相比,ArkTS卡片支持在卡片中运行逻辑代码,为确保ArkTS卡片发生问题后不影响卡片使用方应用的使用,ArkTS卡片新增了卡片渲染服务用于运行卡片页面代码widgets.abc,卡片渲染服务由卡片管理服务管理。卡片使用方的每个卡片组件都对应了卡片渲染服务里的一个渲染实例,同一应用提供方的渲染实例运行在同一个虚拟机运行环境中,不同应用提供方的渲染实例运行在不同的虚拟机运行环境中,通过虚拟机运行环境隔离不同应用提供方卡片之间的资源与状态。开发过程中需要注意的是[globalThis](uiability-data-sync-with-ui.md#使用globalthis进行数据同步)对象的使用,相同应用提供方的卡片globalThis对象是同一个,不同应用提供方的卡片globalThis对象是不同的。
## ArkTS卡片的优势
卡片作为应用的一个快捷入口,ArkTS卡片相较于JS卡片具备如下几点优势:
- 统一开发范式,提升开发体验和开发效率。
OpenHarmony在2022年发布了声明式范式的UI开发框架,而卡片还延续了css/hml/json三段式类Web范式的开发方式,提高了开发者的学习成本,提供ArkTS卡片能力后,统一了卡片和页面的开发范式,页面的布局可以直接复用到卡片布局中,提升开发体验和开发效率。
**图3** 卡片工程结构对比  
![WidgetProject](figures/WidgetProject.png)
- 增强了卡片的能力,使卡片更加万能。
- 新增了动效的能力:ArkTS卡片开放了[属性动画](../reference/arkui-ts/ts-animatorproperty.md)[显式动画](../reference/arkui-ts/ts-explicit-animation.md)的能力,使卡片的交互更加友好。
- 新增了自定义绘制的能力:ArkTS卡片开放了[Canvas](../reference/arkui-ts/ts-components-canvas-canvas.md)画布组件,卡片可以使用自定义绘制的能力构建更多样的显示和交互效果。
- 允许卡片中运行逻辑代码:开放逻辑代码运行后很多业务逻辑可以在卡片内部自闭环,拓宽了卡片的业务适用场景。
## ArkTS卡片的约束
ArkTS卡片相较于JS卡片具备了更加丰富的能力,但也增加了使用卡片进行恶意行为的风险。由于ArkTS卡片显示在使用方应用中,使用方应用一般为桌面应用,为确保桌面的使用体验以及功耗相关考虑,对ArkTS卡片的能力做了以下约束:
- 不支持加载so。
- 不支持使用native语言开发。
- 仅支持声明式范式的[部分](arkts-ui-widget-page-overview.md)组件、事件、动效、数据管理、状态管理和API能力。
- 卡片的事件处理和使用方的事件处理是独立的,建议在使用方支持左右滑动的场景下卡片内容不要使用左右滑动功能的组件,以防手势冲突影响交互体验。
当前ArkTS卡片的能力会持续加强,如下能力会在后续版本支持:
- 不支持断点调试能力,会在后续版本支持。
- 不支持import,会在后续版本支持。
- 不支持极速预览,会在后续版本支持。
# 移除粘性公共事件
## 场景介绍
已发出的粘性公共事件后来订阅者也可以接收到,如果这个事件不再转发,需要事件发布者进行移除。OpenHarmony提供了粘性公共事件移除接口。
## 接口说明
参考[接口文档](../reference/apis/js-apis-commonEventManager.md)
| 接口名 | 接口描述 |
| -------- | -------- |
| removeStickyCommonEvent(event: string, callback: AsyncCallback<void>): void | 移除粘性公共事件 |
## 开发步骤
1. 导入模块。
```ts
import commonEventManager from '@ohos.commonEventManager';
```
2. 移除的粘性公共事件,必须是本应用之前已发布的粘性公共事件,发布粘性公共事件参考[公共事件发布](common-event-publish.md)章节。
```ts
CommonEventManager.removeStickyCommonEvent("sticky_event", (err) => { // sticky_event粘性公共事件名
if (err) {
console.info(`Remove sticky event AsyncCallback failed, errCode: ${err.code}, errMes: ${err.message}`);
return;
}
console.info(`Remove sticky event AsyncCallback success`);
}
});
```
# Stage模型服务卡片相关实例
针对Stage模型卡片提供方的开发,有以下相关实例可供参考:
- [基于Stage模型的JS卡片(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/Widget/FormExtAbility)
- [基于Stage模型的JS卡片(成语接龙小游戏)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/FormGame)
- [基于Stage模型的ArkTS卡片(Canvas绘制实现的五子棋游戏卡片)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/ArkTSCard/CanvasGame)
- [基于Stage模型的ArkTS卡片(逻辑代码执行实现的计算器卡片)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/ability/ArkTSFormCalc)
# 服务卡片概述
服务卡片(以下简称“卡片”)是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。卡片常用于嵌入到其他应用(当前卡片使用方只支持系统应用,如桌面)中作为其界面显示的一部分,并支持拉起页面、发送消息等基础的交互功能。
## 服务卡片架构
**图1** 服务卡片架构  
![WidgetArchitecture](figures/WidgetArchitecture.png)
卡片的基本概念:
- 卡片使用方:如上图中的桌面,显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
- 应用图标:应用入口图标,点击后可拉起应用进程,图标内容不支持交互。
- 卡片:具备不同规格大小的界面展示,卡片的内容可以进行交互,如实现按钮进行[界面的刷新](arkts-ui-widget-event-formextensionability.md)[应用的跳转](arkts-ui-widget-event-router.md)等。
- 卡片提供方:包含卡片的应用,提供卡片的显示内容、控件布局以及控件点击处理逻辑。
- FormExtensionAbility:卡片业务逻辑模块,提供卡片创建、销毁、刷新等生命周期回调。
- 卡片页面:卡片UI模块,包含页面控件、布局、事件等显示和交互信息。
卡片的常见使用步骤如下。
**图2** 卡片常见使用步骤  
![WidgetUse](figures/WidgetUse.png)
1. 长按“桌面图标”,弹出操作菜单。
2. 点击“服务卡片”选项,进入卡片预览界面。
3. 点击“添加到桌面”按钮,即可在桌面上看到新添加的卡片。
## 服务卡片UI页面开发方式
在Stage模型下,服务卡片的UI页面支持通过[ArkTS](arkts-ui-widget-working-principles.md)[JS](js-ui-widget-development.md)两种语言进行开发:
- 基于声明式范式ArkTS UI开发的卡片,简称ArkTS卡片。
- 基于类Web范式JS UI开发的卡片,简称JS卡片。
ArkTS卡片与JS卡片具备不同的实现原理及特征,在场景能力上的差异如下表所示。
| 类别 | JS卡片 | ArkTS卡片 |
| -------- | -------- | -------- |
| 开发范式 | 类Web范式 | 声明式范式 |
| 组件能力 | 支持 | 支持 |
| 布局能力 | 支持 | 支持 |
| 事件能力 | 支持 | 支持 |
| 自定义动效 | 不支持 | 支持 |
| 自定义绘制 | 不支持 | 支持 |
| 逻辑代码执行(不包含import能力) | 不支持 | 支持 |
相比于JS卡片,ArkTS卡片在能力和场景方面更加丰富,因此无论开发何种用途的卡片,都推荐使用ArkTS卡片,因为它可以提高开发效率并实现动态化。但如果只需要做静态页面展示的卡片,可以考虑使用JS卡片。
## 限制
为了降低FormExtensionAbility能力被三方应用滥用的风险,在FormExtensionAbility中限制以下接口的调用
- @ohos.ability.particleAbility.d.ts
- @ohos.backgroundTaskManager.d.ts
- @ohos.resourceschedule.backgroundTaskManager.d.ts
- @ohos.multimedia.camera.d.ts
- @ohos.multimedia.audio.d.ts
- @ohos.multimedia.media.d.ts
\ No newline at end of file
......@@ -361,7 +361,7 @@ onUpdate(formId) {
![widget-development-fa](figures/widget-development-fa.png)
> **说明:**
> 当前仅支持JS扩展的类Web开发范式来实现卡片的UI界面。
> FA模型当前仅支持JS扩展的类Web开发范式来实现卡片的UI界面。
- HML:使用类Web范式的组件描述卡片的页面信息。
......
# 数据管理
- 分布式数据服务
- [分布式数据服务概述](database-mdds-overview.md)
- [分布式数据服务开发指导](database-mdds-guidelines.md)
- 关系型数据库
- [关系型数据库概述](database-relational-overview.md)
- [关系型数据库开发指导](database-relational-guidelines.md)
- 首选项
- [首选项概述](database-preference-overview.md)
- [首选项开发指导](database-preference-guidelines.md)
- 分布式数据对象
- [分布式数据对象概述](database-distributedobject-overview.md)
- [分布式数据对象开发指导](database-distributedobject-guidelines.md)
- 数据共享
- [数据共享概述](database-datashare-overview.md)
- [数据共享开发指导](database-datashare-guidelines.md)
- [数据管理概述](data-mgmt-overview.md)
- 应用数据持久化
- [应用数据持久化概述](app-data-persistence-overview.md)
- [通过用户首选项实现数据持久化](data-persistence-by-preferences.md)
- [通过键值型数据库实现数据持久化](data-persistence-by-kv-store.md)
- [通过关系型数据库实现数据持久化](data-persistence-by-rdb-store.md)
- 同应用跨设备数据同步(分布式)
- [同应用跨设备数据同步概述](sync-app-data-across-devices-overview.md)
- [键值型数据库跨设备数据同步](data-sync-of-kv-store.md)
- [关系型数据库跨设备数据同步](data-sync-of-rdb-store.md)
- [分布式数据对象跨设备数据同步](data-sync-of-distributed-data-object.md)
- 数据可靠性与安全性
- [数据可靠性与安全性概述](data-reliability-security-overview.md)
- [数据库备份与恢复](data-backup-and-restore.md)
- [数据库加密](data-encryption.md)
- [基于设备分类和数据分级的访问控制](access-control-by-device-and-data-level.md)
- 同设备跨应用数据共享(仅对系统应用开放)
- [同设备跨应用数据共享概述](share-device-data-across-apps-overview.md)
- [通过DataShareExtensionAbility实现数据共享](share-data-by-datashareextensionability.md)
- [通过静默数据访问实现数据共享](share-data-by-silent-access.md)
# 基于设备分类和数据分级的访问控制
## 基本概念
分布式数据管理对数据实施分类分级保护,提供基于数据安全标签以及设备安全等级的访问控制机制。
数据安全标签和设备安全等级越高,加密措施和访问控制措施越严格,数据安全性越高。
### 数据安全标签
按照数据分类分级规范要求,可将数据分为S1、S2、S3、S4四个安全等级。
| 风险等级 | 风险标准 | 定义 | 样例 |
| -------- | -------- | -------- | -------- |
| 严重 | S4 | 业界法律法规定义的特殊数据类型,涉及个人的最私密领域的信息或一旦泄露、篡改、破坏、销毁可能会给个人或组织造成重大的不利影响的数据。 | 政治观点、宗教和哲学信仰、工会成员资格、基因数据、生物信息、健康和性生活状况,性取向等或设备认证鉴权、个人信用卡等财物信息等。 |
| 高 | S3 | 数据的泄露、篡改、破坏、销毁可能会给个人或组织导致严峻的不利影响 | 个人实时精确定位信息、运动轨迹等。 |
| 中 | S2 | 数据的泄露、篡改、破坏、销毁可能会给个人或组织导致严重的不利影响 | 个人的详细通信地址、姓名昵称等。 |
| 低 | S1 | 数据的泄露、篡改、破坏、销毁可能会给个人或组织导致有限的不利影响 | 性别、国籍、用户申请记录等。 |
### 设备安全等级
根据设备安全能力,比如是否有TEE、是否有安全存储芯片等,将设备安全等级分为SL1、SL2、SL3、SL4、SL5五个等级。例如,开发板rk3568、hi3516为低安全的SL1设备,平板通常为高安全的SL4设备。
在设备组网时可以通过`hidumper -s 3511`查看设备安全等级,例如,rk3568设备的安全等级查询如下:
![zh-cn_image_0000001542496993](figures/zh-cn_image_0000001542496993.png)
## 跨设备同步访问控制机制
数据跨设备同步时,数据管理基于数据安全标签和设备安全等级进行访问控制。规则为,在本设备的数据安全标签不高于对端设备的设备安全等级时,数据才能从本设备同步到对端设备,否则不能同步。具体访问控制矩阵如下:
|设备安全级别|可同步的数据安全标签|
|---|---|
|SL1|S1|
|SL2|S1~S2|
|SL3|S1~S3|
|SL4|S1~S4|
|SL5|S1~S4|
例如,对于类似rk3568、hi3516的开发板设备,设备安全等级为SL1。若创建数据安全标签为S1的数据库,则此数据库数据可以在这些设备间同步;若创建的数据库标签为S2-S4,则不能在这些设备间同步。
## 场景介绍
分布式数据库的访问控制机制确保了数据存储和同步时的安全能力。在创建数据库时,应当基于数据分类分级规范合理地设置数据库的安全标签,确保数据库内容和数据标签的一致性。
## 使用键值型数据库实现数据分级
键值型数据库,通过securityLevel参数设置数据库的安全等级。此处以创建安全等级为S1的数据库为例。
具体接口及功能,可见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
let kvManager;
let context = getContext(this);
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
}
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
let kvStore;
try {
const options = {
createIfMissing: true,
encrypt: true,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
kvManager.getKVStore('storeId', options, (err, store) => {
if (err) {
console.error(`Failed to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
## 使用关系型数据库实现数据分级
关系型数据库,通过securityLevel参数设置数据库的安全等级。此处以创建安全等级为S1的数据库为例。
具体接口及功能,可见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
```js
import relationalStore from '@ohos.data.relationalStore';
let store;
const STORE_CONFIG = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1
};
let promise = relationalStore.getRdbStore(this.context, STORE_CONFIG);
promise.then(async (rdbStore) => {
store = rdbStore;
console.info('Succeeded in getting RdbStore.')
}).catch((err) => {
console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`);
})
```
# 应用数据持久化概述
应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。
OpenHarmony标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。
开发者可以根据如下功能介绍,选择合适的数据形态以满足自己应用数据的持久化需要。
- **用户首选项(Preferences)**:通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
- **键值型数据库(KV-Store)**:一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。
- **关系型数据库(RelationalStore)**:一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自己定义的SQL语句来满足复杂业务场景的需要。
# 数据库备份与恢复
## 场景介绍
当应用在处理一项重要的操作,显然是不能被打断的。例如:写入多个表关联的事务。此时,每个表的写入都是单独的,但是表与表之间的事务关联性不能被分割。
如果操作的过程中出现问题,开发者可以使用恢复功能,将数据库恢复到之前的状态,重新对数据库进行操作。
在数据库被篡改、删除、或者设备断电场景下,数据库可能会因为数据丢失、数据损坏、脏数据等而不可用,可以通过数据库的备份恢复能力将数据库恢复至可用状态。
键值型数据库和关系型数据库均支持对数据库的备份和恢复。另外,键值型数据库还支持删除数据库备份,以释放本地存储空间。
## 键值型数据库备份、恢复与删除
键值型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复,通过deletebackup接口删除数据库备份。具体接口及功能,可见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
1. 创建数据库。
(1) 创建kvManager。
(2) 配置数据库参数。
(3) 创建kvStore。
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
let kvManager;
let context = getContext(this);
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
}
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
let kvStore;
try {
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S2
};
kvManager.getKVStore('storeId', options, (err, store) => {
if (err) {
console.error(`Fail to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
2. 使用put()方法插入数据。
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Fail to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
3. 使用backup()方法备份数据。
```js
let file = 'BK001';
try {
kvStore.backup(file, (err) => {
if (err) {
console.error(`Fail to backup data.code:${err.code},message:${err.message}`);
} else {
console.info('Succeeded in backupping data.');
}
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
4. 使用delete()方法删除数据(模拟意外删除、篡改场景)。
```js
try {
kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Fail to delete data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in deleting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
5. 使用restore()方法恢复数据。
```js
let file = 'BK001';
try {
kvStore.restore(file, (err) => {
if (err) {
console.error(`Fail to restore data. Code:${err.code},message:${err.message}`);
} else {
console.info('Succeeded in restoring data.');
}
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
6. 当本地设备存储空间有限或需要重新备份时,还可使用deleteBackup()方法删除备份,释放存储空间。
```js
let kvStore;
let files = ['BK001'];
try {
kvStore.deleteBackup(files).then((data) => {
console.info(`Succeed in deleting Backup. Data:filename is ${data[0]},result is ${data[1]}.`);
}).catch((err) => {
console.error(`Fail to delete Backup. Code:${err.code},message:${err.message}`);
})
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
## 关系型数据库备份与恢复
关系型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复。具体接口及功能,可见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
1. 使用getRdbStore()方法创建关系型数据库。
```js
import relationalStore from '@ohos.data.relationalStore';
let store;
let context = getContext(this);
const STORE_CONFIG = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1
};
relationalStore.getRdbStore(context, STORE_CONFIG, (err, rdbStore) => {
store = rdbStore;
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`);
return;
}
store.executeSql("CREATE TABLE IF NOT EXISTS EMPLOYEE (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, salary INTEGER, codes Uint8Array);", null);
console.info('Succeeded in getting RdbStore.');
})
```
2. 使用insert()方法插入数据。
```js
const valueBucket = {
'NAME': 'Lisa',
'AGE': 18,
'SALARY': 100.5,
'CODES': new Uint8Array([1, 2, 3, 4, 5])
};
store.insert('EMPLOYEE', valueBucket, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE, (err, rowId) => {
if (err) {
console.error(`Failed to insert data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in inserting data. rowId:${rowId}`);
})
```
3. 使用backup()方法备份数据。
```js
store.backup('dbBackup.db', (err) => {
if (err) {
console.error(`Failed to backup data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in backuping data.`);
})
```
4. 使用delete()方法删除数据(模拟意外删除、篡改场景)。
```js
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Lisa');
let promise = store.delete(predicates);
promise.then((rows) => {
console.info(`Delete rows: ${rows}`);
}).catch((err) => {
console.error(`Failed to delete data. Code:${err.code},message:${err.message}`);
})
```
5. 使用restore()方法恢复数据。
```js
store.restore('dbBackup.db', (err) => {
if (err) {
console.error(`Failed to restore data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in restoring data.`);
})
```
# 数据库加密
## 场景介绍
为了增强数据库的安全性,数据库提供了一个安全适用的数据库加密能力,从而对数据库存储的内容实施有效保护。通过数据库加密等安全方法实现了数据库数据存储的保密性和完整性要求,使得数据库以密文方式存储并在密态方式下工作,确保了数据安全。
加密后的数据库只能通过接口进行访问,无法通过其它方式打开数据库文件。数据库的加密属性在创建数据库时确认,无法变更。
键值型数据库和关系型数据库均支持数据库加密操作。
## 键值型数据库加密
键值型数据库,通过options中encrypt参数来设置是否加密,默认为false,表示不加密。encrypt参数为true时表示加密。
具体接口及功能,可见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
let kvManager;
let context = getContext(this);
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest',
}
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
let kvStore;
try {
const options = {
createIfMissing: true,
// 设置数据库加密
encrypt: true,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S2
};
kvManager.getKVStore('storeId', options, (err, store) => {
if (err) {
console.error(`Fail to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
## 关系型数据库加密
关系型数据库,通过StoreConfig中encrypt属性来设置是否加密,默认为false,表示不加密。encrypt参数为true时表示加密。
具体接口及功能,可见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
```js
import relationalStore from '@ohos.data.relationalStore';
let store;
let context = getContext(this);
const STORE_CONFIG = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1,
encrypt: true
};
relationalStore.getRdbStore(context, STORE_CONFIG, (err, rdbStore) => {
store = rdbStore;
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in getting RdbStore.`);
})
```
# 数据管理概述
## 功能介绍
数据管理为开发者提供数据存储、数据管理和数据同步能力,比如联系人应用数据可以保存到数据库中,提供数据库的安全、可靠以及共享访问等管理机制,也支持与手表同步联系人信息。
- 数据存储:提供通用数据持久化能力,根据数据特点,分为用户首选项、键值型数据库和关系型数据库。
- 数据管理:提供高效的数据管理能力,包括权限管理、数据备份恢复、数据共享框架等。
- 数据同步:提供跨设备数据同步能力,比如分布式对象支持内存对象跨设备共享能力,分布式数据库支持跨设备数据库访问能力。
应用创建的数据库,都保存到应用沙盒,当应用卸载时,数据库也会自动删除。
## 运作机制
数据管理模块包括用户首选项、键值型数据管理、关系型数据管理、分布式数据对象和跨应用数据管理。Interface接口层提供标准JS API接口,定义这些部件接口描述,供开发者参考。Frameworks&amp;System service层负责实现部件数据存储、同步功能,还有一些SQLite和其他子系统的依赖。
**图1** 数据管理架构图  
![dataManagement](figures/dataManagement.jpg)
- 用户首选项(Preferences):提供了轻量级配置数据的持久化能力,并支持订阅数据变化的通知能力。不支持分布式同步,常用于保存应用配置信息、用户偏好设置等。
- 键值型数据管理(KV-Store):提供了键值型数据库的读写、加密、手动备份以及订阅通知能力。应用需要使用键值型数据库的分布式能力时,KV-Store会将同步请求发送给DatamgrService由其完成跨设备数据同步。
- 关系型数据管理(RelationalStore):提供了关系型数据库的增删改查、加密、手动备份以及订阅通知能力。应用需要使用关系型数据库的分布式能力时,RelationalStore部件会将同步请求发送给DatamgrService由其完成跨设备数据同步。
- 分布式数据对象(DataObject):独立提供对象型结构数据的分布式能力。如果应用需要重启后仍获取之前的对象数据(包含跨设备应用和本设备应用),则使用数据管理服务(DatamgrService)的对象持久化能力,做暂时保存。
- 跨应用数据管理(DataShare):提供了数据提供者provider、数据消费者consumer以及同设备跨应用数据交互的增、删、改、查以及订阅通知等能力。DataShare不与任何数据库绑定,可以对接关系型数据库、键值型数据库。如果开发C/C++应用甚至可以自行封装数据库。在提供标准的provider-consumer模式基础上,同时提供了静默数据访问能力,即不再拉起provider而是直接通过DatamgrService代理访问provider的数据(目前仅关系型数据库支持静默数据访问方式)。
- 数据管理服务(DatamgrService):提供其它部件的同步及跨应用共享能力,包括RelationalStore和KV-Store跨设备同步,DataShare静默访问provider数据,暂存DataObject同步对象数据等。
# 通过键值型数据库实现数据持久化
## 场景介绍
键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。
## 约束限制
- 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度&lt;4 MB。
- 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度&lt;4 MB。
- 每个应用程序最多支持同时打开16个分布式数据库。
- 键值型数据库事件回调方法中不允许进行阻塞操作,例如修改UI组件。
## 接口说明
以下是键值型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| createKVManager(config: KVManagerConfig): KVManager | 创建一个KVManager对象实例,用于管理数据库对象。 |
| getKVStore&lt;T&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void | 指定Options和storeId,创建并得到指定类型的KVStore数据库。 |
| put(key: string, value: Uint8Array\|string\|number\|boolean, callback: AsyncCallback&lt;void&gt;): void | 添加指定类型的键值对到数据库。 |
| get(key: string, callback: AsyncCallback&lt;Uint8Array\|string\|boolean\|number&gt;): void | 获取指定键的值。 |
| delete(key: string, callback: AsyncCallback&lt;void&gt;): void | 从数据库中删除指定键值的数据。 |
## 开发步骤
1. 若要使用键值型数据库,首先要获取一个KVManager实例,用于管理数据库对象。示例代码如下所示:
Stage模型示例:
```js
// 导入模块
import distributedKVStore from '@ohos.data.distributedKVStore';
// Stage模型
import UIAbility from '@ohos.app.ability.UIAbility';
let kvManager;
export default class EntryAbility extends UIAbility {
onCreate() {
let context = this.context;
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
};
try {
// 创建KVManager实例
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 继续创建获取数据库
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
}
}
```
FA模型示例:
```js
// 导入模块
import distributedKVStore from '@ohos.data.distributedKVStore';
// FA模型
import featureAbility from '@ohos.ability.featureAbility';
let kvManager;
let context = featureAbility.getContext(); // 获取context
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
};
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 继续创建获取数据库
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
```
2. 创建并获取键值数据库。示例代码如下所示:
```js
try {
const options = {
createIfMissing: true, // 当数据库文件不存在时是否创建数据库,默认创建
encrypt: false, // 当数据库文件不存在时是否创建数据库,默认创建
backup: false, // 设置数据库文件是否备份,默认备份
autoSync: true, // 设置数据库文件是否自动同步。默认为false,即手动同步;设置为true时,表示自动同步
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, // 设置要创建的数据库类型,默认为多设备协同数据库
securityLevel: distributedKVStore.SecurityLevel.S2 // 设置数据库安全级别
};
// storeId为数据库唯一标识符
kvManager.getKVStore('storeId', options, (err, kvStore) => {
if (err) {
console.error(`Failed to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
// 进行相关数据操作
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
3. 调用put()方法向键值数据库中插入数据。示例代码如下所示:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
> **说明:**
>
> 当Key值存在时,put()方法会修改其值,否则新增一条数据。
4. 调用get()方法获取指定键的值。示例代码如下所示:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
kvStore.get(KEY_TEST_STRING_ELEMENT, (err, data) => {
if (err !== undefined) {
console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in getting data. data:${data}`);
});
});
} catch (e) {
console.error(`Failed to get data. Code:${e.code},message:${e.message}`);
}
```
5. 调用delete()方法删除指定键值的数据。示例代码如下所示:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to delete data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in deleting data.');
});
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
# 通过用户首选项实现数据持久化
## 场景介绍
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。当用户希望有一个全局唯一存储的地方,可以采用用户首选项来进行存储。Preferences会将该数据缓存在内存中,当用户读取的时候,能够快速从内存中获取数据。Preferences会随着存放的数据量越多而导致应用占用的内存越大,因此,Preferences不适合存放过多的数据,适用的场景一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等。
## 运作机制
如图所示,用户程序通过JS接口调用用户首选项读写对应的数据文件。开发者可以将用户首选项持久化文件的内容加载到Preferences实例,每个文件唯一对应到一个Preferences实例,系统会通过静态容器将该实例存储在内存中,直到主动从内存中移除该实例或者删除该文件。
**图1** 用户首选项运作机制  
![preferences](figures/preferences.jpg)
## 约束限制
- Key键为string类型,要求非空且长度不超过80个字节。
- 如果Value值为string类型,可以为空,不为空时长度不超过8192个字节。
- 内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。
## 接口说明
以下是用户首选项持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[用户首选项](../reference/apis/js-apis-data-preferences.md)
| 接口名称 | 描述 |
| -------- | -------- |
| getPreferences(context: Context, name: string, callback: AsyncCallback&lt;Preferences&gt;): void | 获取Preferences实例。 |
| put(key: string, value: ValueType, callback: AsyncCallback&lt;void&gt;): void | 将数据写入Preferences实例,可通过flush将Preferences实例持久化。 |
| has(key: string, callback: AsyncCallback&lt;boolean&gt;): void | 检查Preferences实例是否包含名为给定Key的存储键值对。给定的Key值不能为空。 |
| get(key: string, defValue: ValueType, callback: AsyncCallback&lt;ValueType&gt;): void | 获取键对应的值,如果值为null或者非默认值类型,返回默认数据defValue。 |
| delete(key: string, callback: AsyncCallback&lt;void&gt;): void | 从Preferences实例中删除名为给定Key的存储键值对。 |
| flush(callback: AsyncCallback&lt;void&gt;): void | 将当前Preferences实例的数据异步存储到用户首选项持久化文件中。 |
| on(type: 'change', callback: Callback&lt;{ key : string }&gt;): void | 订阅数据变更,订阅的Key的值发生变更后,在执行flush方法后,触发callback回调。 |
| off(type: 'change', callback?: Callback&lt;{ key : string }&gt;): void | 取消订阅数据变更。 |
| deletePreferences(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void | 从内存中移除指定的Preferences实例。若Preferences实例有对应的持久化文件,则同时删除其持久化文件。 |
## 开发步骤
1. 导入`@ohos.data.preferences`模块。
```js
import dataPreferences from '@ohos.data.preferences';
```
2. 要通过用户首选项实现数据持久化,首先要获取Preferences实例。读取指定文件,将数据加载到Preferences实例,用于数据操作。
Stage模型示例:
```js
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
dataPreferences.getPreferences(this.context, 'mystore', (err, preferences) => {
if (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting preferences.');
// 进行相关数据操作
})
} catch (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
}
}
}
```
FA模型示例:
```js
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
try {
dataPreferences.getPreferences(context, 'mystore', (err, preferences) => {
if (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting preferences.');
// 进行相关数据操作
})
} catch (err) {
console.error(`Failed to get preferences. Code is ${err.code},message:${err.message}`);
}
```
3. 写入数据。
使用put()方法保存数据到缓存的Preferences实例中。在写入数据后,如有需要,可使用flush()方法将Preferences实例的数据存储到持久化文件。
> **说明:**
>
> 当对应的键已经存在时,put()方法会修改其值。如果仅需要在键值对不存在时新增键值对,而不修改已有键值对,需使用has()方法检查是否存在对应键值对;如果不关心是否会修改已有键值对,则直接使用put()方法即可。
示例代码如下所示:
```js
try {
preferences.has('startup', function (err, val) {
if (err) {
console.error(`Failed to check the key 'startup'. Code:${err.code}, message:${err.message}`);
return;
}
if (val) {
console.info("The key 'startup' is contained.");
} else {
console.info("The key 'startup' does not contain.");
// 此处以此键值对不存在时写入数据为例
try {
preferences.put('startup', 'auto', (err) => {
if (err) {
console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
})
} catch (err) {
console.error(`Failed to put data. Code: ${err.code},message:${err.message}`);
}
}
})
} catch (err) {
console.error(`Failed to check the key 'startup'. Code:${err.code}, message:${err.message}`);
}
```
4. 读取数据。
使用get()方法获取数据,即指定键对应的值。如果值为null或者非默认值类型,则返回默认数据。示例代码如下所示:
```js
try {
preferences.get('startup', 'default', (err, val) => {
if (err) {
console.error(`Failed to get value of 'startup'. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in getting value of 'startup'. val: ${val}.`);
})
} catch (err) {
console.error(`Failed to get value of 'startup'. Code:${err.code}, message:${err.message}`);
}
```
5. 删除数据。
使用delete()方法删除指定键值对,示例代码如下所示:
```js
try {
preferences.delete('startup', (err) => {
if (err) {
console.error(`Failed to delete the key 'startup'. Code:${err.code}, message:${err.message}`);
return;
}
console.info("Succeeded in deleting the key 'startup'.");
})
} catch (err) {
console.error(`Failed to delete the key 'startup'. Code:${err.code}, message:${err.message}`);
}
```
6. 数据持久化。
应用存入数据到Preferences实例后,可以使用flush()方法实现数据持久化。示例代码如下所示:
```js
try {
preferences.flush((err) => {
if (err) {
console.error(`Failed to flush. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in flushing.');
})
} catch (err) {
console.error(`Failed to flush. Code:${err.code}, message:${err.message}`);
}
```
7. 订阅数据变更。
应用订阅数据变更需要指定observer作为回调方法。订阅的Key值发生变更后,当执行flush()方法时,observer被触发回调。示例代码如下所示:
```js
let observer = function (key) {
console.info('The key' + key + 'changed.');
}
preferences.on('change', observer);
// 数据产生变更,由'auto'变为'manual'
preferences.put('startup', 'manual', (err) => {
if (err) {
console.error(`Failed to put the value of 'startup'. Code:${err.code},message:${err.message}`);
return;
}
console.info("Succeeded in putting the value of 'startup'.");
preferences.flush((err) => {
if (err) {
console.error(`Failed to flush. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in flushing.');
})
})
```
8. 删除指定文件。
使用deletePreferences()方法从内存中移除指定文件对应的Preferences实例,包括内存中的数据。若该Preference存在对应的持久化文件,则同时删除该持久化文件,包括指定文件及其备份文件、损坏文件。
> **说明:**
>
> - 调用该接口后,应用不允许再使用该Preferences实例进行数据操作,否则会出现数据一致性问题。
>
> - 成功删除后,数据及文件将不可恢复。
示例代码如下所示:
```js
try {
dataPreferences.deletePreferences(this.context, 'mystore', (err, val) => {
if (err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting preferences.');
})
} catch (err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
}
```
## 相关实例
针对用户首选项开发,有以下相关实例可供参考:
- [`Preferences`:首选项(ArkTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/Preferences)
- [首选项(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/Data/Preferences)
\ No newline at end of file
# 通过关系型数据库实现数据持久化
## 场景介绍
关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。
## 基本概念
- **谓词**:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
- **结果集**:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便地拿到用户想要的数据。
## 运作机制
关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
**图1** 关系型数据库运作机制
![relationStore_local](figures/relationStore_local.jpg)
## 约束限制
- 系统默认日志方式是WAL(Write Ahead Log)模式,系统默认落盘方式是FULL模式。
- 数据库中连接池的最大数量是4个,用以管理用户的读操作。
- 为保证数据的准确性,数据库同一时间只能支持一个写操作。
- 当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除。
## 接口说明
以下是关系型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback&lt;RdbStore&gt;): void | 获得一个相关的RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作。 |
| executeSql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;void&gt;):void | 执行包含指定参数但不返回值的SQL语句。 |
| insert(table: string, values: ValuesBucket, callback: AsyncCallback&lt;number&gt;):void | 向目标表中插入一行数据。 |
| update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | 根据RdbPredicates的指定实例对象更新数据库中的数据。 |
| delete(predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | 根据RdbPredicates的指定实例对象从数据库中删除数据。 |
| query(predicates: RdbPredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void | 根据指定条件查询数据库中的数据。 |
| deleteRdbStore(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void | 删除数据库。 |
## 开发指导
1. 使用关系型数据库实现数据持久化,需要获取一个RdbStore。示例代码如下所示:
Stage模型示例:
```js
import relationalStore from '@ohos.data.relationalStore'; // 导入模块
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
const STORE_CONFIG = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
};
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)'; // 建表Sql语句
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.`);
store.executeSql(SQL_CREATE_TABLE); // 创建数据表
// 这里执行数据库的增、删、改、查等操作
});
}
}
```
FA模型示例:
```js
import relationalStore from '@ohos.data.relationalStore'; // 导入模块
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
const STORE_CONFIG = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
};
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)'; // 建表Sql语句
relationalStore.getRdbStore(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.`);
store.executeSql(SQL_CREATE_TABLE); // 创建数据表
// 这里执行数据库的增、删、改、查等操作
});
```
> **说明:**
>
> - 应用创建的数据库与其上下文(Context)有关,即使使用同样的数据库名称,但不同的应用上下文,会产生多个数据库,例如每个UIAbility都有各自的上下文。
>
> - 当应用首次获取数据库(调用getRdbStore)后,在应用沙箱内会产生对应的数据库文件。使用数据库的过程中,数据库文件相同的目录下,可以会产生以-wal和-shm结尾的临时文件,此时开发者希望移到数据库文件到其它地方使用可查看,需要同时移动这些临时文件。当应用被卸载完成后,其在设备上产生的数据库文件及临时文件也会被移除。
2. 获取到RdbStore后,调用insert()接口插入数据。示例代码如下所示:
```js
const valueBucket = {
'NAME': 'Lisa',
'AGE': 18,
'SALARY': 100.5,
'CODES': new Uint8Array([1, 2, 3, 4, 5])
};
store.insert('EMPLOYEE', valueBucket, (err, rowId) => {
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()方法删除数据。示例代码如下所示:
```js
// 修改数据
const valueBucket = {
'NAME': 'Rose',
'AGE': 22,
'SALARY': 200.5,
'CODES': new Uint8Array([1, 2, 3, 4, 5])
};
let predicates = new relationalStore.RdbPredicates('EMPLOYEE'); // 创建表'EMPLOYEE'的predicates
predicates.equalTo('NAME', 'Lisa'); // 匹配表'EMPLOYEE'中'NAME'为'Lisa'的字段
store.update(valueBucket, predicates, (err, rows) => {
if (err) {
console.error(`Failed to update data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in updating data. row count: ${rows}`);
})
// 删除数据
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Lisa');
store.delete(predicates, (err, rows) => {
if (err) {
console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Delete rows: ${rows}`);
})
```
4. 根据谓词指定的查询条件查找数据。
调用query()方法查找数据,返回一个ResultSet结果集。示例代码如下所示:
```js
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Rose');
store.query(predicates, ['ID', 'NAME', 'AGE', 'SALARY', 'CODES'], (err, resultSet) => {
if (err) {
console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}`);
console.info(`ResultSet column count: ${resultSet.columnCount}`);
})
```
> **说明:**
>
> 当应用完成查询数据操作,不再使用结果集(ResultSet)时,请及时调用close方法关闭结果集,释放系统为其分配的内存。
5. 删除数据库。
调用deleteRdbStore()方法,删除数据库及数据库相关文件。示例代码如下:
Stage模型示例:
```js
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
}
}
```
FA模型示例:
```js
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
relationalStore.deleteRdbStore(context, 'RdbTest.db', (err) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
```
# 数据可靠性与安全性概述
## 功能场景
在系统运行中,存储损坏、存储空间不足、文件系统权限、系统掉电等都可能导致数据库发生故障。比如联系人应用的数据库损坏,导致用户的联系人丢失;日历应用的数据库损坏,导致丢失日历提醒等。为此数据管理提供了数据可靠性与安全性相关的解决方案和能力保障。
- 备份、恢复功能:重要业务应用(如银行)数据丢失,出现严重异常场景,可以通过备份恢复数据库,保证关键数据不丢失。
- 数据库加密功能:当数据库中存储如认证凭据、财务数据等高敏感信息时,可对数据库进行加密,提高数据库安全性。
- 数据库分类分级:数据跨设备同步时,数据管理基于数据安全标签和设备安全等级进行访问控制,保证数据安全。
另外,备份数据库存储在应用的沙箱内,当存储空间不足时,可以选择删除本地的数据库备份,释放空间。
## 基本概念
在进行数据可靠性与安全性相关功能的开发前,请先了解以下相关概念。
### 数据库备份与恢复
- **数据库备份**:指对当前数据库的数据库文件进行完整备份。OpenHarmony数据库备份针对数据库全量文件进行完整的备份。
在进行数据库备份的时候,无需关闭数据库,直接调用对应的数据库备份接口就能完成对数据库文件的备份。
- **数据库恢复**:从指定的备份文件恢复到当前数据库文件。恢复完成时,当前数据库数据恢复到和指定备份文件一致。
### 数据库加密
数据库加密是对整个数据库文件的加密,可以增强数据库的安全性,有效保护数据库内容。
### 数据库分类分级
分布式数据管理对数据实施分类分级保护,提供基于数据安全标签以及设备安全等级的访问控制机制。
数据安全标签和设备安全等级越高,加密措施和访问控制措施越严格,数据安全性越高。
## 运作机制
### 数据库备份与恢复机制
数据库在备份时,会将当前的数据库备份在指定的文件中,后续对数据库的操作不会影响备份的数据库文件。只有当恢复指定数据库文件时,才会将备份的数据库文件覆盖当前数据库,实现数据的回滚。
- 键值型数据库备份路径:/data/service/el1(el2)/public/database/...{appId}/kvdb/backup/...{storeId}
- 关系型数据库备份路径:/data/app/el1(el2)/100/database/...{bundlename}/rdb
### 数据库加密机制
OpenHarmony数据库加密时,应用开发者无需传入密钥,只需要设置数据库加密的状态即可。系统会自动帮助开发者将数据库加密,使用[huks通用密钥库系统](../reference/apis/js-apis-huks.md),完成数据库密钥的生成及加密保护。
## 约束限制
- 数据库加密的密钥一年自动更换一次。
- 键值型数据库最多可以备份5份。
- 键值型数据库的自动备份需要在熄屏且充电的状态下进行。
# 分布式数据对象跨设备数据同步
## 场景介绍
传统方式下,设备之间的数据同步,需要开发者完成消息处理逻辑,包括:建立通信链接、消息收发处理、错误重试、数据冲突解决等操作,工作量非常大。而且设备越多,调试复杂度也将同步增加。
其实设备之间的状态、消息发送进度、发送的数据等都是“变量”。如果这些变量支持“全局”访问,那么开发者跨设备访问这些变量就能像操作本地变量一样,从而能够自动高效、便捷地实现数据多端同步。
分布式数据对象即实现了对“变量”的“全局”访问。向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力,同时具备分布式能力。为开发者在分布式应用场景下提供简单易用的JS接口,轻松实现多设备间同应用的数据协同,同时设备间可以监听对象的状态和数据变更。满足超级终端场景下,相同应用多设备间的数据对象协同需求。与传统方式相比,分布式数据对象大大减少了开发者的工作量。
## 基本概念
- **分布式内存数据库**
分布式内存数据库将数据缓存在内存中,以便应用获得更快的数据存取速度,不会将数据进行持久化。若数据库关闭,则数据不会保留。
- **分布式数据对象**
分布式数据对象是一个JS对象型的封装。每一个分布式数据对象实例会创建一个内存数据库中的数据表,每个应用程序创建的内存数据库相互隔离,对分布式数据对象的“读取”或“赋值”会自动映射到对应数据库的get/put操作。
分布式数据对象的生命周期包括以下状态:
- 未初始化:未实例化,或已被销毁。
- 本地数据对象:已创建对应的数据表,但是还无法进行数据同步。
- 分布式数据对象:已创建对应的数据表,设备在线且组网内设置同样sessionId的对象数&gt;=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。
## 运作机制
**图1** 分布式数据对象运作机制 
 
![distributedObject](figures/distributedObject.jpg)
分布式数据对象生长在分布式内存数据库之上,在分布式内存数据库上进行了JS对象型的封装,能像操作本地变量一样操作分布式数据对象,数据的跨设备同步由系统自动完成。
### JS对象型存储与封装机制
- 为每个分布式数据对象实例创建一个内存数据库,通过SessionId标识,每个应用程序创建的内存数据库相互隔离。
- 在分布式数据对象实例化的时候,(递归)遍历对象所有属性,使用“Object.defineProperty”定义所有属性的set和get方法,set和get中分别对应数据库一条记录的put和get操作,Key对应属性名,Value对应属性值。
- 在开发者对分布式数据对象进行“读取”或者“赋值”的时候,都会自动调用到set和get方法,映射到对应数据库的操作。
**表1** 分布式数据对象和分布式数据库的对应关系
| 分布式对象实例 | 对象实例 | 属性名称 | 属性值 |
| -------- | -------- | -------- | -------- |
| 分布式内存数据库 | 一个数据库(sessionID标识) | 一条数据库记录的key | 一条数据库记录的value |
### 跨设备同步和数据变更通知机制
分布式数据对象,最重要的功能就是对象之间的数据同步。可信组网内的设备可以在本地创建分布式数据对象,并设置sessionID。不同设备上的分布式数据对象,通过设置相同的sessionID,建立对象之间的同步关系。
如下图所示,设备A和设备B上的“分布式数据对象1”,其sessionID均为session1,这两个对象建立了session1的同步关系。
**图2** 对象的同步关系  
![distributedObject_sync](figures/distributedObject_sync.jpg)
一个同步关系中,一个设备只能有一个对象加入。比如上图中,设备A的“分布式数据对象1”已经加入了session1的同步关系,所以设备A的“分布式数据对象2”就加入失败了。
建立同步关系后,每个Session有一份共享对象数据。加入了同一个Session的对象,支持以下操作:
(1)读取/修改Session中的数据。
(2)监听数据变更,感知其他设备对共享对象数据的修改。
(3)监听状态变更,感知其他设备的加入和退出。
### 同步的最小单位
关于分布式数据对象的数据同步,值得注意的是,同步的最小单位是“属性”。比如,下图中对象1包含三个属性:name、age和parents。当其中一个属性变更时,则数据同步时只需同步此变更的属性。
**图3** 数据同步视图 
![distributedObject_syncView](figures/distributedObject_syncView.jpg)
### 对象持久化缓存机制
分布式对象主要运行在应用程序的进程空间。当调用分布式对象持久化接口时,通过分布式数据库对对象进行持久化和同步,进程退出后数据也不会丢失。
该场景是分布式对象的扩展场景,主要用于以下情况:
- 在设备上创建持久化对象后APP退出,重新打开APP,创建持久化对象,加入同一个Session,数据可以恢复到APP退出前的数据。
- 在设备A上创建持久化对象并同步后持久化到设备B后,A设备的APP退出,设备B打开APP,创建持久化对象,加入同一个Session,数据可以恢复到A设备退出前的数据。
## 约束限制
- 不同设备间只有相同bundleName的应用才能直接同步。
- 分布式数据对象的数据同步发生在同一个应用程序下,且同sessionID之间。
- 不建议创建过多的分布式数据对象,每个分布式数据对象将占用100-150KB内存。
- 每个分布式数据对象大小不超过500KB。
- 设备A修改1KB数据,设备B收到变更通知,50ms内完成。
- 单个应用程序最多只能创建16个分布式数据对象实例。
- 考虑到性能和用户体验,最多不超过3个设备进行数据协同。
- 如对复杂类型的数据进行修改,仅支持修改根属性,暂不支持下级属性修改。
- 支持JS接口间的互通,与其他语言不互通。
## 接口说明
以下是分布式对象跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式数据对象](../reference/apis/js-apis-data-distributedobject.md)
| 接口名称 | 描述 |
| -------- | -------- |
| create(context: Context, source: object): DataObject | 创建并得到一个分布式数据对象实例。 |
| genSessionId(): string | 创建一个sessionId,可作为分布式数据对象的sessionId。 |
| setSessionId(sessionId: string, callback: AsyncCallback&lt;void&gt;): void | 设置同步的sessionId,当可信组网中有多个设备时,多个设备间的对象如果设置为同一个sessionId,就能自动同步。 |
| setSessionId(callback: AsyncCallback&lt;void&gt;): void | 退出所有已加入的session。 |
| on(type: 'change', callback: Callback&lt;{ sessionId: string, fields: Array&lt;string&gt; }&gt;): void | 监听分布式数据对象的数据变更。 |
| on(type: 'status', callback: Callback&lt;{ sessionId: string, networkId: string, status: 'online' \| 'offline' }&gt;): void | 监听分布式数据对象的上下线。 |
| save(deviceId: string, callback: AsyncCallback&lt;SaveSuccessResponse&gt;): void | 保存分布式数据对象。 |
| revokeSave(callback: AsyncCallback&lt;RevokeSaveSuccessResponse&gt;): void | 撤回保存的分布式数据对象。 |
## 开发步骤
以一次分布式数据对象同步为例,说明开发步骤。
1. 导入`@ohos.data.distributedDataObject`模块。
```js
import distributedDataObject from '@ohos.data.distributedDataObject';
```
2. 请求权限。
1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
3. 创建并得到一个分布式数据对象实例。
Stage模型示例:
```js
// 导入模块
import distributedDataObject from '@ohos.data.distributedDataObject';
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
// 创建对象,该对象包含4个属性类型:string、number、boolean和Object
let localObject = distributedDataObject.create(this.context, {
name: 'jack',
age: 18,
isVis: false,
parent: { mother: 'jack mom', father: 'jack Dad' },
list: [{ mother: 'jack mom' }, { father: 'jack Dad' }]
});
}
}
```
FA模型示例:
```js
// 导入模块
import distributedDataObject from '@ohos.data.distributedDataObject';
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
// 创建对象,该对象包含4个属性类型:string、number、boolean和Object
let localObject = distributedDataObject.create(context, {
name: 'jack',
age: 18,
isVis: false,
parent: { mother: 'jack mom', father: 'jack Dad' },
list: [{ mother: 'jack mom' }, { father: 'jack Dad' }]
});
```
4. 加入同步组网。同步组网中的数据对象分为发起方和被拉起方。
```js
// 设备1加入sessionId
let sessionId = '123456';
localObject.setSessionId(sessionId);
// 和设备1协同的设备2加入同一个session
// 创建对象,该对象包含4个属性类型:string、number、boolean和Object
let remoteObject = distributedDataObject.create(this.context, {
name: undefined,
age: undefined, // undefined表示数据来自对端
isVis: true,
parent: undefined,
list: undefined
});
// 收到status上线后remoteObject同步数据,即name变成jack,age是18
remoteObject.setSessionId(sessionId);
```
5. 监听对象数据变更。可监听对端数据的变更,以callback作为变更回调实例。
```js
function changeCallback(sessionId, changeData) {
console.info(`change: ${sessionId}`);
if (changeData !== null && changeData !== undefined) {
changeData.forEach(element => {
console.info(`The element ${localObject[element]} changed.`);
});
}
}
// 发起方要在changeCallback里刷新界面,则需要将正确的this绑定给changeCallback
localObject.on("change", this.changeCallback.bind(this));
```
6. 修改对象属性,对象属性支持基本类型(数字类型、布尔类型、字符串类型)以及复杂类型(数组、基本类型嵌套等)。
```js
localObject.name = 'jack1';
localObject.age = 19;
localObject.isVis = false;
localObject.parent = { mother: 'jack1 mom', father: 'jack1 Dad' };
localObject.list = [{ mother: 'jack1 mom' }, { father: 'jack1 Dad' }];
```
> **说明:**
>
> 针对复杂类型的数据修改,目前仅支持对根属性的修改,暂不支持对下级属性的修改。
```js
// 支持的修改方式
localObject.parent = { mother: 'mom', father: 'dad' };
// 不支持的修改方式
localObject.parent.mother = 'mom';
```
7. 访问对象。可以通过直接获取的方式访问到分布式数据对象的属性,且该数据为组网内的最新数据。
```js
console.info(`name:${localObject['name']}`);
```
8. 删除监听数据变更。可以指定删除监听的数据变更回调;也可以不指定,这将会删除该分布式数据对象的所有数据变更回调。
```js
// 删除变更回调changeCallback
localObject.off('change', this.changeCallback);
// 删除所有的变更回调
localObject.off('change');
```
9. 监听分布式数据对象的上下线。可以监听对端分布式数据对象的上下线。
```js
function statusCallback(sessionId, networkId, status) {
// 业务处理
}
localObject.on('status', this.statusCallback);
```
10. 保存和撤回已保存的数据对象。
```js
// 保存数据对象,如果应用退出后组网内设备需要恢复对象数据时调用
localObject.save('local').then((result) => {
console.info(`Succeeded in saving. SessionId:${result.sessionId},version:${result.version},deviceId:${result.deviceId}`);
}).catch((err) => {
console.error(`Failed to save. Code:${err.code},message:${err.message}`);
});
// 撤回保存的数据对象
localObject.revokeSave().then((result) => {
console.info(`Succeeded in revokeSaving. Session:${result.sessionId}`);
}).catch((err) => {
console.error(`Failed to revokeSave. Code:${err.code},message:${err.message}`);
});
```
11. 删除监听分布式数据对象的上下线。可以指定删除监听的上下线回调;也可以不指定,这将会删除该分布式数据对象的所有上下线回调。
```js
// 删除上下线回调statusCallback
localObject.off('status', this.statusCallback);
// 删除所有的上下线回调
localObject.off('status');
```
12. 退出同步组网。分布式数据对象退出组网后,本地的数据变更对端不会同步。
```js
localObject.setSessionId(() => {
console.info('leave all lession.');
});
```
## 相关实例
针对分布式数据对象,有以下相关实例可供参考:
- [`DistributedNote`:分布式备忘录(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/DistributedNote)
- [`DistributedObjectDms`:分布式跑马灯(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/DistributedObjectDms)
\ No newline at end of file
# 键值型数据库跨设备数据同步
## 场景介绍
键值型数据库适合不涉及过多数据关系和业务关系的业务数据存储,比SQL数据库存储拥有更好的读写性能,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。
## 基本概念
在使用键值型数据库跨设备数据同步前,请先了解以下概念。
### 单版本数据库
单版本是指数据在本地是以单个条目为单位的方式保存,当数据在本地被用户修改时,不管它是否已经被同步出去,均直接在这个条目上进行修改。多个设备全局只保留一份数据,多个设备的相同记录(主码相同)会按时间最新保留一条记录,数据不分设备,设备之间修改相同的key会覆盖。同步也以此为基础,按照它在本地被写入或更改的顺序将当前最新一次修改逐条同步至远端设备,常用于联系人、天气等应用存储场景。
![singleKVStore](figures/singleKVStore.jpg)
### 多设备协同数据库
多设备协同分布式数据库建立在单版本数据库之上,对应用程序存入的键值型数据中的Key前面拼接了本设备的DeviceID标识符,这样能保证每个设备产生的数据严格隔离。数据以设备的维度管理,不存在冲突;支持按照设备的维度查询数据。
底层按照设备的维度管理这些数据,多设备协同数据库支持以设备的维度查询分布式数据,但是不支持修改远端设备同步过来的数据。需要分开查询各设备数据的可以使用设备协同版本数据库。常用于图库缩略图存储场景。
![deviceKVStore](figures/deviceKVStore.jpg)
## 同步方式
数据管理服务提供了两种同步方式:手动同步和自动同步。键值型数据库可选择其中一种方式实现同应用跨设备数据同步。
- **手动同步**:由应用程序调用sync接口来触发,需要指定同步的设备列表和同步模式。同步模式分为PULL_ONLY(将远端数据拉取到本端)、PUSH_ONLY(将本端数据推送到远端)和PUSH_PULL(将本端数据推送到远端同时也将远端数据拉取到本端)。[带有Query参数的同步接口](../reference/apis/js-apis-distributedKVStore.md#sync-1),支持按条件过滤的方法进行同步,将符合条件的数据同步到远端。手动同步功能,仅系统应用可用。
- **自动同步**:由分布式数据库自动将本端数据推送到远端,同时也将远端数据拉取到本端来完成数据同步,同步时机包括设备上线、应用程序更新数据等,应用不需要主动调用sync接口。
## 运作机制
底层通信组件完成设备发现和认证,会通知上层应用程序设备上线。收到设备上线的消息后数据管理服务可以在两个设备之间建立加密的数据传输通道,利用该通道在两个设备之间进行数据同步。
### 数据跨设备同步机制
![kvStore](figures/kvStore.jpg)
如图所示,通过put、delete接口触发自动同步,将分布式数据通过通信适配层发送给对端设备,实现分布式数据的自动同步;
手动同步则是手动调用sync接口触发同步,将分布式数据通过通信适配层发送给对端设备。
### 数据变化通知机制
增、删、改数据库时,会给订阅者发送数据变化的通知。主要分为本地数据变化通知和分布式数据变化通知。
- **本地数据变化通知**:本地设备的应用内订阅数据变化通知,数据库增删改数据时,会收到通知。
- **分布式数据变化通知**:同一应用订阅组网内其他设备数据变化的通知,其他设备增删改数据时,本设备会收到通知。数据变化通知可以让用户及时感知到两端数据的不同,并进行同步操作,保证分布式数据库的一致性。
## 约束限制
- 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度&lt;4 MB。
- 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度&lt;4 MB。
- 键值型数据库不支持应用程序自定义冲突解决策略。
- 每个应用程序最多支持同时打开16个分布式数据库。
- 允许同时订阅数据变化通知的数据库最大数量为8个。
- 手动同步功能,仅系统应用可用。
## 接口说明
以下是单版本键值型分布式数据库跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| createKVManager(config: KVManagerConfig): KVManager | 创建一个KVManager对象实例,用于管理数据库对象。 |
| getKVStore&lt;T&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void | 指定Options和storeId,创建并得到指定类型的KVStore数据库。 |
| put(key: string, value: Uint8Array\|string\|number\|boolean, callback: AsyncCallback&lt;void&gt;): void | 插入和更新数据。 |
| on(event: 'dataChange', type: SubscribeType, listener: Callback&lt;ChangeNotification&gt;): void | 订阅数据库中数据的变化。 |
| get(key: string, callback: AsyncCallback&lt;boolean \| string \| number \| Uint8Array&gt;): void | 查询指定Key键的值。 |
| sync(deviceIds: string[], mode: SyncMode, delayMs?: number): void | 在手动模式下,触发数据库同步。 |
## 开发步骤
此处以单版本键值型数据库跨设备数据同步的开发为例。以下是具体的开发流程和开发步骤。
![kvStore_development_process](figures/kvStore_development_process.png)
> **说明:**
>
> 数据只允许向数据安全标签不高于对端设备安全等级的设备同步数据,具体规则可见[跨设备同步访问控制机制](sync-app-data-across-devices-overview.md#跨设备同步访问控制机制)。
1. 导入模块。
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
```
2. 请求权限。
1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
3. 根据配置构造分布式数据库管理类实例。
1. 根据应用上下文创建kvManagerConfig对象。
2. 创建分布式数据库管理器实例。
```js
// Stage模型获取context
import UIAbility from '@ohos.app.ability.UIAbility';
let kvManager;
let context = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
context = this.context;
}
}
// FA模型获取context
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
// 获取context之后,构造分布式数据库管理类实例
try {
const kvManagerConfig = {
bundleName: 'com.example.datamanagertest',
context: context
}
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 继续创建获取数据库
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
```
4. 获取并得到指定类型的键值型数据库。
1. 声明需要创建的分布式数据库ID描述。
2. 创建分布式数据库,建议关闭自动同步功能(autoSync:false),方便后续对同步功能进行验证,需要同步时主动调用sync接口。
```js
try {
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
// 设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
kvManager.getKVStore('storeId', options, (err, kvStore) => {
if (err) {
console.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
// 进行相关数据操作
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
5. 订阅分布式数据变化。
```js
try {
kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
console.info(`dataChange callback call data: ${data}`);
});
} catch (e) {
console.error(`An unexpected error occured. code:${e.code},message:${e.message}`);
}
```
6. 将数据写入分布式数据库。
1. 构造需要写入分布式数据库的Key(键)和Value(值)。
2. 将键值数据写入分布式数据库。
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
7. 查询分布式数据库数据。
1. 构造需要从单版本分布式数据库中查询的Key(键)。
2. 从单版本分布式数据库中获取数据。
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
kvStore.get(KEY_TEST_STRING_ELEMENT, (err, data) => {
if (err != undefined) {
console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in getting data. Data:${data}`);
});
});
} catch (e) {
console.error(`Failed to get data. Code:${e.code},message:${e.message}`);
}
```
8. 同步数据到其他设备。
选择同一组网环境下的设备以及同步模式(需用户在应用首次启动的弹窗中确认选择同步模式),进行数据同步。
> **说明:**
>
> 在手动同步的方式下,其中的deviceIds通过调用[devManager.getTrustedDeviceListSync](../reference/apis/js-apis-device-manager.md#gettrusteddevicelistsync)方法得到,deviceManager模块的接口均为系统接口,仅系统应用可用。
```js
import deviceManager from '@ohos.distributedHardware.deviceManager';
let devManager;
// create deviceManager
deviceManager.createDeviceManager('bundleName', (err, value) => {
if (!err) {
devManager = value;
// deviceIds由deviceManager调用getTrustedDeviceListSync方法得到
let deviceIds = [];
if (devManager !== null) {
//需要权限:ohos.permission.ACCESS_SERVICE_DM,仅系统应用可以获取
let devices = devManager.getTrustedDeviceListSync();
for (let i = 0; i < devices.length; i++) {
deviceIds[i] = devices[i].deviceId;
}
}
try {
// 1000表示最大延迟时间为1000ms
kvStore.sync(deviceIds, distributedKVStore.SyncMode.PUSH_ONLY, 1000);
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
}
});
```
# 关系型数据库跨设备数据同步
## 场景介绍
当应用程序本地存储的关系型数据存在跨设备同步的需求时,可以将需求同步的表数据迁移到新的支持跨设备的表中,当然也可以在刚完成表创建时设置其支持跨设备。
## 基本概念
关系型数据库跨设备数据同步,支持应用在多设备间同步存储的关系型数据。
- 分布式列表,应用在数据库中新创建表后,可以设置其为分布式表。在查询远程设备数据库时,根据本地表名可以获取指定远程设备的分布式表名。
- 设备之间同步数据,数据同步有两种方式,将数据从本地设备推送到远程设备或将数据从远程设备拉至本地设备。
## 运作机制
底层通信组件完成设备发现和认证,会通知上层应用程序设备上线。收到设备上线的消息后数据管理服务可以在两个设备之间建立加密的数据传输通道,利用该通道在两个设备之间进行数据同步。
### 数据跨设备同步机制
![relationalStore_sync](figures/relationalStore_sync.jpg)
业务将数据写入关系型数据库或键值型数据库后,向数据管理服务发起同步请求。
数据管理服务从应用沙箱内读取待同步数据,根据对端设备的deviceId将数据发送到其他设备的数据管理服务。再由数据管理服务将数据写入同应用的数据库内。
### 数据变化通知机制
增、删、改数据库时,会给订阅者发送数据变化的通知。主要分为本地数据变化通知和分布式数据变化通知。
- **本地数据变化通知**:本地设备的应用内订阅数据变化通知,数据库增删改数据时,会收到通知。
- **分布式数据变化通知**:同一应用订阅组网内其他设备数据变化的通知,其他设备增删改数据时,本设备会收到通知。数据变化通知可以让用户及时感知到两端数据的不同,并进行同步操作,保证分布式数据库的一致性。
## 约束限制
- 每个应用程序最多支持同时打开16个分布式数据库。
- 允许同时订阅数据变化通知的数据库最大数量为8个。
- 不支持非系统应用调用需要指定设备的分布式能力接口。
## 接口说明
以下是关系型设备协同分布式数据库跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| setDistributedTables(tables: Array&lt;string&gt;, callback: AsyncCallback&lt;void&gt;): void | 设置分布式同步表。 |
| sync(mode: SyncMode, predicates: RdbPredicates, callback: AsyncCallback&lt;Array&lt;[string, number]&gt;&gt;): void | 分布式数据同步。 |
| on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;): void | 订阅分布式数据变化。 |
| off(event:'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;): void | 取消订阅分布式数据变化。 |
| obtainDistributedTableName(device: string, table: string, callback: AsyncCallback&lt;string&gt;): void; | 根据本地数据库表名获取指定设备上的表名。 |
| remoteQuery(device: string, table: string, predicates: RdbPredicates, columns: Array&lt;string&gt; , callback: AsyncCallback&lt;ResultSet&gt;): void | 根据指定条件查询远程设备数据库中的数据。 |
## 开发步骤
> **说明:**
>
> 数据只允许向数据安全标签不高于对端设备安全等级的设备同步数据,具体规则可见[跨设备同步访问控制机制](sync-app-data-across-devices-overview.md#跨设备同步访问控制机制)。
1. 导入模块。
```js
import relationalStore from '@ohos.data.relationalStore';
```
2. 请求权限。
1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
3. 创建关系型数据库,设置将需要进行分布式同步的表。
```js
const STORE_CONFIG = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
};
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)', null, (err) => {
// 设置分布式同步表。
store.setDistributedTables(['EMPLOYEE']);
// 进行数据的相关操作
})
})
```
4. 分布式数据同步。使用SYNC_MODE_PUSH触发同步后,数据将从本设备向组网内的其它所有设备同步。
```js
// 构造用于同步分布式表的谓词对象
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
// 调用同步数据的接口
store.sync(relationalStore.SyncMode.SYNC_MODE_PUSH, predicates, (err, result) => {
// 判断数据同步是否成功
if (err) {
console.error(`Failed to sync data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in syncing data.');
for (let i = 0; i < result.length; i++) {
console.info(`device:${result[i][0]},status:${result[i][1]}`);
}
})
```
5. 分布式数据订阅。数据同步变化将触发订阅回调方法执行,回调方法的入参为发生变化的设备ID。
```js
let observer = function storeObserver(devices) {
for (let i = 0; i < devices.length; i++) {
console.info(`The data of device:${devices[i]} has been changed.`);
}
}
try {
// 调用分布式数据订阅接口,注册数据库的观察者
// 当分布式数据库中的数据发生更改时,将调用回调
store.on('dataChange', relationalStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, observer);
} catch (err) {
console.error('Failed to register observer. Code:${err.code},message:${err.message}');
}
// 当前不需要订阅数据变化时,可以将其取消。
try {
store.off('dataChange', relationalStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, observer);
} catch (err) {
console.error('Failed to register observer. Code:${err.code},message:${err.message}');
}
```
6. 跨设备查询。如果数据未完成同步,或未触发数据同步,应用可以使用此接口从指定的设备上查询数据。
> **说明:**
>
> deviceIds通过调用[devManager.getTrustedDeviceListSync](../reference/apis/js-apis-device-manager.md#gettrusteddevicelistsync)方法得到,deviceManager模块的接口均为系统接口,仅系统应用可用。
```js
// 获取deviceIds
import deviceManager from '@ohos.distributedHardware.deviceManager';
deviceManager.createDeviceManager("com.example.appdatamgrverify", (err, manager) => {
if (err) {
console.info(`Failed to create device manager. Code:${err.code},message:${err.message}`);
return;
}
let devices = manager.getTrustedDeviceListSync();
let deviceId = devices[0].deviceId;
// 构造用于查询分布式表的谓词对象
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
// 调用跨设备查询接口,并返回查询结果
store.remoteQuery(deviceId, 'EMPLOYEE', predicates, ['ID', 'NAME', 'AGE', 'SALARY', 'CODES'],
function (err, resultSet) {
if (err) {
console.error(`Failed to remoteQuery data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
}
)
})
```
# 数据共享概述
## 数据共享简介
DataShare即数据共享模块,用于应用管理其自身数据,也提供了向其他应用共享以及管理其数据的方法。目前仅支持同个设备上应用之间的数据共享。
DataShare需要与[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md)配合使用。
在许多应用场景中都需要用到数据共享,比如将电话簿、短信、媒体库中的数据共享给其他应用等。当然,不是所有的数据都允许其他应用访问,比如帐号、密码等;有些数据也只允许其他应用查询而不允许其删改,比如短信等。所以对于各种数据共享场景,DataShare这样一个安全、便捷的可以跨应用的数据共享机制是十分必需的。
对于数据提供者来说,无需进行繁琐的封装,可直接使用DataShare框架实现向其他应用共享数据;对于数据访问方来说,因DataShare的访问方式不会因数据提供的方式而不同,所以只需要学习和使用一套接口即可,大大减少了学习时间和开发难度。
## 基本概念
在进行应用的开发前,开发者应了解以下基本概念:
- **数据提供方**
DataShareExtensionAbility,基于Stage模型,选择性实现对数据的增、删、改、查以及文件打开等功能,并对外共享这些数据。实现跨应用数据共享的相关业务。
- **数据访问方**
DataShareHelper,由[createDataShareHelper()](../reference/apis/js-apis-data-dataShare.md#datasharecreatedatasharehelper)方法所创建的工具类,数据访问方利用工具类,便可访问数据提供方提供的数据。
- **数据集**
用户要插入的数据集合,可以是一条或多条数据。数据集以键值对的形式存在,键为字符串类型,值支持数字、字符串、布尔值、无符号整型数组等多种数据类型。
- **结果集**
用户查询之后的结果集合,其提供了灵活的数据访问方式,以便用户获取各项数据。
- **谓词**
用户访问数据库中的数据所使用的筛选条件,经常被应用在更新数据、删除数据和查询数据等场景。
## 运作机制
**图 1** DataShare运作机制<a name="fig3330103712254"></a>
![](figures/zh-cn_DataShare.png)
- DataShareExtensionAbility模块为数据提供方,实现跨应用数据共享的相关业务。
- DataShareHelper模块为数据访问方,提供各种访问数据的接口,包括增删改查等。
- 数据访问方与提供方通过IPC进行通信,数据提供方可以通过数据库实现,也可以通过其他数据存储方式实现。
- ResultSet模块通过共享内存实现,用于存储查询数据得到的结果集,并提供了遍历结果集的方法。
## 约束与限制
- DataShare受到数据提供方所使用数据库的一些限制。例如支持的数据模型、Key的长度、Value的长度、每个应用程序支持同时打开数据库的最大数量等,都会受到使用的数据库的限制。
- 因DataShare内部实现依赖于IPC通信,所以数据集、谓词、结果集等的载荷受到IPC通信的约束与限制。
# 首选项概述
首选项Preferences,适用于对`Key-Value`结构的数据进行存取和持久化操作。
应用获取某个`Preferences`对象后,该存储对象中的数据将会被缓存在内存中,以便应用获得更快的数据存取速度。
应用也可以将缓存的数据再次写回文本文件中进行持久化存储,由于文件读写将产生不可避免的系统资源开销,建议应用降低对持久化文件的读写频率。
关于数据库锁机制,开发者无需关注其具体实现。
## 基本概念
- **Key-Value数据结构**
一种键值型的数据结构。`Key`是不重复的关键字,`Value`是数据值。
- **非关系型数据库**
区别于关系数据库,不保证遵循ACID(Atomic、Consistency、Isolation及Durability)特性,不采用关系模型来组织数据,数据之间无关系。比如,以`Key-Value`数据结构组成的数据库。
## 运作机制
1. 应用通过指定首选项持久化文件将其中的数据加载到`Preferences`实例,系统会通过静态容器将该实例存储在内存中,同一应用或进程中每个文件仅存在一个`Preferences`实例,直到应用主动从内存中移除该实例或者删除该首选项持久化文件。
2. 应用获取到首选项持久化文件对应的实例后,可以从`Preferences`实例中读取数据,或者将数据存入`Preferences`实例中。通过调用flush方法可以将`Preferences`实例中的数据回写到文件里。
**图1** 首选项运作机制
![zh-cn_image_0000001199139454](figures/zh-cn_image_0000001199139454.png)
## 约束与限制
-`Preferences`实例会加载到内存中,建议存储的数据不超过一万条,并注意及时清理不再使用的实例,以便减少非内存开销。
- 数据中的`Key``string`类型,要求非空且字符长度不超过80个字节。
- 当数据中的`Value``string`类型时,允许为空,字符长度不超过8192个字节。
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册