提交 fe2f3136 编写于 作者: L l00613276

Merge branch 'master' of https://gitee.com/HelloCrease/docs

...@@ -16,7 +16,11 @@ ...@@ -16,7 +16,11 @@
- [Database Backup and Restoration](data-backup-and-restore.md) - [Database Backup and Restoration](data-backup-and-restore.md)
- [Database Encryption](data-encryption.md) - [Database Encryption](data-encryption.md)
- [Access Control by Device and Data Level](access-control-by-device-and-data-level.md) - [Access Control by Device and Data Level](access-control-by-device-and-data-level.md)
- Cross-Application Data Sharing (for System Applications Only) - Cross-Application Data Sharing
- [Cross-Application Data Sharing Overview](share-device-data-across-apps-overview.md) - [Data Sharing Overview](data-share-overview.md)
- [Unified Data Definition](unified-data-definition.md)
- One-to-Many Data Sharing (Only for System Applications)
- [Sharing Data Using DataShareExtensionAbility](share-data-by-datashareextensionability.md) - [Sharing Data Using DataShareExtensionAbility](share-data-by-datashareextensionability.md)
- [Sharing Data in Silent Access](share-data-by-silent-access.md) - [Silent Access via the DatamgrService](share-data-by-silent-access.md)
- Many-to-Many Data Sharing
- [Sharing Data Using Unified Data Channels](unified-data-channels.md)
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
## Function ## Function
Data management provides data storage, management, and synchronization capabilities. For example, you can store the Contacts application data in database for secure management and shared access, and synchronize the contacts information with a smart watch. Data management provides data storage, management, and synchronization capabilities. For example, you can store the Contacts application data in database for secure management and shared access, and synchronize the Contacts information with a smart watch.
- Data storage: provides data persistence capabilities, which can be classified into user preferences, key-value (KV) stores, and relational database (RDB) stores by data characteristics. - Data storage: provides data persistence capabilities, which can be classified into user preferences, key-value (KV) stores, and relational database (RDB) stores by data characteristics.
...@@ -16,9 +16,9 @@ The database stores created by an application are saved to the application sandb ...@@ -16,9 +16,9 @@ The database stores created by an application are saved to the application sandb
## Working Principles ## Working Principles
The data management module includes user preferences (**Preferences**), KV data management (**KV-Store**), RDB data management (**RelationalStore**), distributed data object (**DataObject**), and cross-application data management (**DataShare**). The interface layer provides standard JavaScript APIs for application development. The Frameworks&System service layer implements storage and synchronization of component data, and provides dependencies for SQLite and other subsystems. The data management module includes preferences, KV data management (KV-Store), relational data management (RelatoinalStore), distributed data object (DataObject), cross-application data management (DataShare), and unified data management framework (UDMF). The interface layer provides standard JavaScript APIs for application development. The Frameworks&System service layer implements storage and synchronization of component data, and provides dependencies for SQLite and other subsystems.
**Figure 1** Data management architecture **Figure 1** Data management architecture
![dataManagement](figures/dataManagement.jpg) ![dataManagement](figures/dataManagement.jpg)
...@@ -33,4 +33,7 @@ The data management module includes user preferences (**Preferences**), KV data ...@@ -33,4 +33,7 @@ The data management module includes user preferences (**Preferences**), KV data
- **DataShare**: provides the data provider-consumer mode to implement addition, deletion, modification, and query of cross-application data on a device, and notification subscription. **DataShare** is not bound to any database and can interact with RDB and KV stores. You can also encapsulate your own databases for C/C++ applications.<br> In addition to the provider-consumer mode, **DataShare** provides silent access, which allows direct access to the provider's data via the DatamgrService proxy instead of starting the provider. Currently, only the RDB stores support silent access. - **DataShare**: provides the data provider-consumer mode to implement addition, deletion, modification, and query of cross-application data on a device, and notification subscription. **DataShare** is not bound to any database and can interact with RDB and KV stores. You can also encapsulate your own databases for C/C++ applications.<br> In addition to the provider-consumer mode, **DataShare** provides silent access, which allows direct access to the provider's data via the DatamgrService proxy instead of starting the provider. Currently, only the RDB stores support silent access.
- **UDMF**: defines the data language and standards for cross-application and cross-device data interaction, improving data interaction efficiency. The UDMF provides secure and standard data transmission channels and supports different levels of data access permissions and lifecycle management policies. It helps implement efficient data sharing across applications and devices.
- **DatamgrService**: implements synchronization and cross-application sharing for other components, including cross-device synchronization of **RelationalStore** and **KV-Store**, silent access to provider data of **DataShare**, and temporary storage of **DataObject** synchronization object data. - **DatamgrService**: implements synchronization and cross-application sharing for other components, including cross-device synchronization of **RelationalStore** and **KV-Store**, silent access to provider data of **DataShare**, and temporary storage of **DataObject** synchronization object data.
...@@ -18,7 +18,7 @@ A relational database (RDB) store is used to store data in complex relational mo ...@@ -18,7 +18,7 @@ A relational database (RDB) store is used to store data in complex relational mo
**RelationalStore** provides APIs for applications to perform data operations. With SQLite as the underlying persistent storage engine, **RelationalStore** provides SQLite database features, including transactions, indexes, views, triggers, foreign keys, parameterized queries, prepared SQL statements, and more. **RelationalStore** provides APIs for applications to perform data operations. With SQLite as the underlying persistent storage engine, **RelationalStore** provides SQLite database features, including transactions, indexes, views, triggers, foreign keys, parameterized queries, prepared SQL statements, and more.
**Figure 1** Working mechanism **Figure 1** Working mechanism
![relationStore_local](figures/relationStore_local.jpg) ![relationStore_local](figures/relationStore_local.jpg)
...@@ -37,15 +37,15 @@ A relational database (RDB) store is used to store data in complex relational mo ...@@ -37,15 +37,15 @@ A relational database (RDB) store is used to store data in complex relational mo
The following table lists the APIs used for RDB data persistence. Most of the APIs are executed asynchronously, using a callback or promise to return the result. The following table uses the callback-based APIs as an example. For more information about the APIs, see [RDB Store](../reference/apis/js-apis-data-relationalStore.md). The following table lists the APIs used for RDB data persistence. Most of the APIs are executed asynchronously, using a callback or promise to return the result. The following table uses the callback-based APIs as an example. For more information about the APIs, see [RDB Store](../reference/apis/js-apis-data-relationalStore.md).
| API| Description| | API| Description|
| -------- | -------- | | -------- | -------- |
| getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback&lt;RdbStore&gt;): void | Obtains a **RdbStore** instance to implement RDB store operations. You can set **RdbStore** parameters based on actual requirements and use **RdbStore** APIs to perform data operations.| | getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback&lt;RdbStore&gt;): void | Obtains a **RdbStore** instance to implement RDB store operations. You can set **RdbStore** parameters based on actual requirements and use **RdbStore** APIs to perform data operations.|
| executeSql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;void&gt;):void | Executes an SQL statement that contains specified arguments but returns no value.| | executeSql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;void&gt;):void | Executes an SQL statement that contains specified arguments but returns no value.|
| insert(table: string, values: ValuesBucket, callback: AsyncCallback&lt;number&gt;):void | Inserts a row of data into a table.| | insert(table: string, values: ValuesBucket, callback: AsyncCallback&lt;number&gt;):void | Inserts a row of data into a table.|
| update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | Updates data in the RDB store based on the specified **RdbPredicates** instance.| | update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | Updates data in the RDB store based on the specified **RdbPredicates** instance.|
| delete(predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | Deletes data from the RDB store based on the specified **RdbPredicates** instance.| | delete(predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | Deletes data from the RDB store based on the specified **RdbPredicates** instance.|
| query(predicates: RdbPredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void | Queries data in the RDB store based on specified conditions.| | query(predicates: RdbPredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void | Queries data in the RDB store based on specified conditions.|
| deleteRdbStore(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void | Deletes an RDB store.| | deleteRdbStore(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void | Deletes an RDB store.|
## How to Develop ## How to Develop
...@@ -53,7 +53,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -53,7 +53,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
1. Obtain an **RdbStore** instance.<br> Example: 1. Obtain an **RdbStore** instance.<br> Example:
Stage model: Stage model:
```js ```js
import relationalStore from '@ohos.data.relationalStore'; // Import the module. import relationalStore from '@ohos.data.relationalStore'; // Import the module.
import UIAbility from '@ohos.app.ability.UIAbility'; import UIAbility from '@ohos.app.ability.UIAbility';
...@@ -65,7 +65,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -65,7 +65,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
securityLevel: relationalStore.SecurityLevel.S1 // Database security level. securityLevel: relationalStore.SecurityLevel.S1 // Database security level.
}; };
// The current RDB store version is 3, and the table structure is EMPLOYEE (NAME, AGE, SALARY, CODES). // The RDB store version is 3, and the table structure is EMPLOYEE (NAME, AGE, SALARY, CODES).
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 statement for creating a data table. 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 statement for creating a data table.
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => { relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
...@@ -106,7 +106,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -106,7 +106,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
FA model: FA model:
```js ```js
import relationalStore from '@ohos.data.relationalStore'; // Import the module. import relationalStore from '@ohos.data.relationalStore'; // Import the module.
import featureAbility from '@ohos.ability.featureAbility'; import featureAbility from '@ohos.ability.featureAbility';
...@@ -160,10 +160,12 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -160,10 +160,12 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
> >
> - The RDB store created by an application varies with the context. Multiple RDB stores are created for the same database name with different application contexts. For example, each UIAbility has its own context. > - The RDB store created by an application varies with the context. Multiple RDB stores are created for the same database name with different application contexts. For example, each UIAbility has its own context.
> >
> - When an application calls **getRdbStore()** to obtain an RDB store instance for the first time, the corresponding database file is generated in the application sandbox. If you want to move the files of an RDB store to another place for view, you must also move the temporary files with finename extensions **-wal** or **-shm** in the same directory. Once an application is uninstalled, the database files and temporary files generated by the application on the device are also removed. > - When an application calls **getRdbStore()** to obtain an RDB store instance for the first time, the corresponding database file is generated in the application sandbox. When the RDB store is used, temporary files ended with **-wal** and **-shm** may be generated in the same directory as the database file. If you want to move the database files to other places, you must also move these temporary files. After the application is uninstalled, the database files and temporary files generated on the device are also removed.
2. Use **insert()** to insert data to the RDB store. Example: 2. Use **insert()** to insert data to the RDB store.
Example:
```js ```js
const valueBucket = { const valueBucket = {
'NAME': 'Lisa', 'NAME': 'Lisa',
...@@ -177,13 +179,13 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -177,13 +179,13 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
return; return;
} }
console.info(`Succeeded in inserting data. rowId:${rowId}`); console.info(`Succeeded in inserting data. rowId:${rowId}`);
}) })
``` ```
> **NOTE** > **NOTE**
> >
> **RelationalStore** does not provide explicit flush operations for data persistence. Data inserted by **insert()** is stored in files persistently. > **RelationalStore** does not provide explicit flush operations for data persistence. Data inserted by **insert()** is stored in files persistently.
3. Modify or delete data based on the specified **Predicates** instance. 3. Modify or delete data based on the specified **Predicates** instance.
Use **update()** to modify data and **delete()** to delete data. Use **update()** to modify data and **delete()** to delete data.
...@@ -254,13 +256,15 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -254,13 +256,15 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
5. Delete the RDB store. 5. Delete the RDB store.
Use **deleteRdbStore()** to delete the RDB store and related database files. Use **deleteRdbStore()** to delete the RDB store and related database files.
Example: > **NOTE**
>
> After the deletion, you are advised to set the database object to null.
Stage model: Stage model:
```js ```js
import UIAbility from '@ohos.app.ability.UIAbility'; import UIAbility from '@ohos.app.ability.UIAbility';
...@@ -271,6 +275,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -271,6 +275,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`); console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return; return;
} }
store = null;
console.info('Succeeded in deleting RdbStore.'); console.info('Succeeded in deleting RdbStore.');
}); });
} }
...@@ -279,7 +284,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -279,7 +284,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
FA model: FA model:
```js ```js
import featureAbility from '@ohos.ability.featureAbility'; import featureAbility from '@ohos.ability.featureAbility';
...@@ -291,6 +296,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP ...@@ -291,6 +296,7 @@ The following table lists the APIs used for RDB data persistence. Most of the AP
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`); console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return; return;
} }
store = null;
console.info('Succeeded in deleting RdbStore.'); console.info('Succeeded in deleting RdbStore.');
}); });
``` ```
# Cross-Application Data Sharing
## Introduction
OpenHarmony provides APIs for an application to manage its own data and share data with other applications.
Data needs to be shared in a wealth of scenarios. For example, the Contacts, short message service (SMS), and Gallery data always needs to be shared with other applications. However, certain data, such as accounts and passwords, cannot be shared. Some data, such as SMS messages, can be queried but not modified by other applications. Therefore, a secure and efficient cross-application data sharing mechanism for different data sharing scenarios and data privacy protection is very important.
Currently, OpenHarmony supports one-to-many and many-to-many cross-application data sharing, based on the number of the data provider applications involved.
## Basic Concepts
Before you start, understand the following concepts:
- **Data provider**: an application that provides data and implements related services. It is also called the producer or server.
- **Data consumer**: an application that accesses the data or services provided by the data provider. It is also called the client.
- **ValuesBucket**: a set of data to be inserted. It can be one or more data records in key-value (KV) pairs. In each KV pair, the key must be of the string type, and the value can be a number, a string, a Boolean value, or an unsigned integer array.
- **ResultSet**: a set of query results. It provides flexible modes for users to obtain various data.
- **Predicates**: an object that specifies the conditions for updating, deleting, or querying data in a database.
## Unified Data Definition
When data needs to be shared among multiple applications, a large amount of data needs to be converted for data interaction because the data definition and format vary with applications. To reduce application/service data interaction costs, OpenHarmony uses the unified data definition as the unified data language to build cross-application data interaction standards.
The unified data definition defines common data types. Applications can use the APIs provided by the Unified Data Management Framework (UDMF) to create and use these data types. For details, see [Unified Data Definition](unified-data-definition.md).
## One-to-Many Cross-Application Data Sharing
You can use **DataShare** to implement one-to-many data sharing across applications. Two implementation modes are provided, depending on whether the data provider is started in the cross-application data sharing.
### Implementation
The data provider can directly use **DataShare** to share data with other applications without complex encapsulation. The data consumer only needs to use a set of APIs because the **DataShare** access mode does not vary with the data provision mode. This greatly reduces the learning time and development difficulty.
**DataShare** implements cross-application data sharing in either of the following ways:
- [Using DataShareExtensionAbility](share-data-by-datashareextensionability.md)
You need to implement an ExtensionAbility with callbacks in the HAP. When the data consumer calls an API, the ExtensionAbility of the data provider will be automatically started to invoke the registered callback.
You can use **DataShareExtensionAbility** when the cross-application data access involves service operations other than mere addition, deletion, modification, and query of data in databases.
- [Using Silent Access via the DatamgrService](share-data-by-silent-access.md)
You need to configure database access rules in the HAP. When the data consumer calls an API, the system ability automatically obtains the access rules in the HAP and returns data without starting the data provider.
You can use this mode when the cross-application data access involves only database operations (data addition, deletion, modification, and query) or data hosted to the DatamgrService.
If your application is signed with a system signature, you can use both methods. When data is created for the first time, use **DataShareExtensionAbility**. When data is accessed and modified later, use the **DatamgrService** to share data. That is, the data provider is started only when the data is accessed for the first time.
### Restrictions
- **DataShare** is subject to the limitations on the database used by the data provider. For example, the supported data models, length of the keys and values, and maximum number of databases that can be accessed at a time by each application vary with the database in use.
- The payloads of **ValuesBucket**, **Predicates**, and **ResultSet** are restricted by IPC.
- Currently, **dataShare** supports development based on the stage model only.
## Many-to-Many Cross-Application Data Sharing
In one-to-many cross-application data sharing, there is only one data provider. In many-to-many cross-application data sharing, you need to consider data definition, data exchange, and permission management. The UDMF provides a new data sharing and interaction mode to implement many-to-many cross-application data sharing.
### Implementation
[Sharing Data via Unified Data Channels](unified-data-channels.md)
Applications can call the APIs provided by the UDMF to write data that complies with the unified data definition to different data sharing channels of the UDMF. The data in these channels can be read by other applications. The data written into the UDMF is managed based on the permissions of the application, permissions of the data channels, and the permission management logic of the UDMF. Lifecycle management is also performed on the data written into the channels in the same way. In this way, the data scattered in each application is aggregated via different channels of the UDMF, improving the development efficiency and data experience of users.
...@@ -16,8 +16,7 @@ There are two roles in **DataShare**: ...@@ -16,8 +16,7 @@ There are two roles in **DataShare**:
- Data consumer: accesses the data provided by the provider using [createDataShareHelper()](../reference/apis/js-apis-data-dataShare.md#datasharecreatedatasharehelper). - Data consumer: accesses the data provided by the provider using [createDataShareHelper()](../reference/apis/js-apis-data-dataShare.md#datasharecreatedatasharehelper).
**Figure 1** Data sharing mechanism **Figure 1** Data sharing mechanism
![dataShare](figures/dataShare.jpg) ![dataShare](figures/dataShare.jpg)
- The **DataShareExtensionAbility** module, as the data provider, implements services related to data sharing between applications. - The **DataShareExtensionAbility** module, as the data provider, implements services related to data sharing between applications.
...@@ -32,7 +31,7 @@ There are two roles in **DataShare**: ...@@ -32,7 +31,7 @@ There are two roles in **DataShare**:
## How to Develop ## How to Develop
### Data Provider Application Development (Only for System Applications) ### Data Provider Application (Only for System Applications)
[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md) provides the following APIs. You can override these APIs as required. [DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md) provides the following APIs. You can override these APIs as required.
...@@ -146,7 +145,7 @@ override the service implementation as required. For example, if the data provid ...@@ -146,7 +145,7 @@ override the service implementation as required. For example, if the data provid
"icon": "$media:icon", "icon": "$media:icon",
"description": "$string:description_datashareextability", "description": "$string:description_datashareextability",
"type": "dataShare", "type": "dataShare",
"uri": "datashare://com.samples.datasharetest.DataShare", "uri": "datashareproxy://com.samples.datasharetest.DataShare",
"exported": true, "exported": true,
"metadata": [{"name": "ohos.extension.dataShare", "resource": "$profile:data_share_config"}] "metadata": [{"name": "ohos.extension.dataShare", "resource": "$profile:data_share_config"}]
} }
...@@ -155,11 +154,11 @@ override the service implementation as required. For example, if the data provid ...@@ -155,11 +154,11 @@ override the service implementation as required. For example, if the data provid
**Table 2** Fields in the data_share_config.json file **Table 2** Fields in the data_share_config.json file
| Field| Description | Mandatory| | Field | Description | Mandatory |
| ------------ | ------------------------------------------------------------ | --- | | ------------- | ---------------------------------------- | ---- |
| tableConfig | Label configuration.| Yes| | tableConfig | Label configuration. | Yes |
| uri | Range for which the configuration takes effect. The URI supports the following formats in descending order by priority:<br> 1. *****: indicates all databases and tables.<br> 2. **datashare:///{*bundleName*}/{*moduleName*}/{*storeName*}**: specifies a database.<br> 3. **datashare:///{*bundleName*}/{*moduleName*}/{*storeName*}/{*tableName*}**: specifies a table.<br>If URIs of different formats are configured, only the URI with higher priority takes effect. | Yes| | uri | Range for which the configuration takes effect. The URI supports the following formats in descending order by priority:<br> - *****: indicates all databases and tables.<br> - **datashareproxy://{bundleName}/{moduleName}/{storeName}**: specifies a database.<br>- **datashareproxy://{bundleName}/{moduleName}/{storeName}/{tableName}**: specifies a table. | Yes |
| crossUserMode | Whether data is shared by multiple users. The value **1** means to share data between multiple users, and the value **2** means the opposite. | Yes| | crossUserMode | Whether data is shared by multiple users.<br>The value **1** means to share data between multiple users, and the value **2** means the opposite. | Yes |
**data_share_config.json Example** **data_share_config.json Example**
...@@ -170,18 +169,18 @@ override the service implementation as required. For example, if the data provid ...@@ -170,18 +169,18 @@ override the service implementation as required. For example, if the data provid
"crossUserMode": 1 "crossUserMode": 1
}, },
{ {
"uri": "datashare:///com.acts.datasharetest/entry/DB00", "uri": "datashareproxy://com.acts.datasharetest/entry/DB00",
"crossUserMode": 1 "crossUserMode": 1
}, },
{ {
"uri": "datashare:///com.acts.datasharetest/entry/DB00/TBL00", "uri": "datashareproxy://com.acts.datasharetest/entry/DB00/TBL00",
"crossUserMode": 2 "crossUserMode": 2
} }
] ]
``` ```
### Data Consumer Application Development ### Data Consumer Application
1. Import the dependencies. 1. Import the dependencies.
...@@ -195,7 +194,7 @@ override the service implementation as required. For example, if the data provid ...@@ -195,7 +194,7 @@ override the service implementation as required. For example, if the data provid
```js ```js
// Different from the URI defined in the module.json5 file, the URI passed in the parameter has an extra slash (/), because there is a DeviceID parameter between the second and the third slash (/). // Different from the URI defined in the module.json5 file, the URI passed in the parameter has an extra slash (/), because there is a DeviceID parameter between the second and the third slash (/).
let dseUri = ('datashare:///com.samples.datasharetest.DataShare'); let dseUri = ('datashareproxy://com.samples.datasharetest.DataShare');
``` ```
3. Create a **DataShareHelper** instance. 3. Create a **DataShareHelper** instance.
...@@ -239,3 +238,4 @@ override the service implementation as required. For example, if the data provid ...@@ -239,3 +238,4 @@ override the service implementation as required. For example, if the data provid
console.info(`dsHelper delete result:${data}`); console.info(`dsHelper delete result:${data}`);
}); });
``` ```
# Data Sharing Through Silent Access # Silent Access via the DatamgrService
## When to Use ## When to Use
In a typical cross-application data access scenario, an application may be started multiple times. In a typical cross-application data access scenario, the data provider may be started multiple times.
To reduce the number of application startup times and improve the access speed, OpenHarmony provides the silent access feature, which allows direct access to the database without starting the data provider. To reduce the number of startup times of the data provider and improve the access speed, OpenHarmony provides the silent access feature, which allows access to the database without starting the data provider.
Silent access supports only basic database access. If service processing is required, implement service processing in the data consumer. In silent data access, the DatamgrService accesses and modifies data without starting the data provider.
If the service processing is complex, use **DataShareExtensionAbility** to start the data provider. The DatamgrService supports basic database access or data hosting only. If service processing is required, the service processing needs to be encapsulated into APIs for the data consumer to call.
If the service processing is too complex to be processed by the data consumer, use **DataShareExtensionAbility** to start the data provider.
## Working Principles ## Working Principles
The DatamgrService can serve as a proxy to access the following types of data:
- Persistent data
Persistent data belongs to the database of the data provider. It is stored in the sandbox of the data provider and can be shared in declaration mode by the data provider. Persistent data is configured as data tables for access.
- Process data
The process data managed by the **DatamgrService** is stored in the DatamgrService sandbox in JSON or byte format. This type of data is automatically deleted 10 days after no subscription.
| Type | Storage Location | Data Format | Validity Period | Application Scenario |
| ----- | --------- | ----------- | ------------ | --------------------------------- |
| Persistent data| Sandbox of the data provider | Tables in the database | Permanent storage | RDB data applications, such as schedules and conferences. |
| Process data | DatamgrService sandbox| JSON or byte| Automatically deleted 10 days after no subscription| Applications featuring simple and time-sensitive data, such as step count, weather, and heart rate.|
**Figure 1** Silent access **Figure 1** Silent access
![silent_dataShare](figures/silent_dataShare.jpg) ![silent_dataShare](figures/silent_dataShare.jpg)
...@@ -21,26 +42,271 @@ If the service processing is complex, use **DataShareExtensionAbility** to start ...@@ -21,26 +42,271 @@ If the service processing is complex, use **DataShareExtensionAbility** to start
- In silent access, **DatamgrService** obtains the access rules configured by the data provider through directory mapping, performs preprocessing based on rules, and accesses the database. - In silent access, **DatamgrService** obtains the access rules configured by the data provider through directory mapping, performs preprocessing based on rules, and accesses the database.
- To use silent access, the URIs must be in the following format: - To use silent access, the URIs must be in the following format:
datashare:///{bundleName}/{moduleName}/{storeName}/{tableName}?Proxy=true
datashareproxy://{bundleName}/{dataPath}
The **DatamgrService** obtains the data provider application based on **bundleName**, reads the configuration, verifies the permission, and accesses data.
**dataPath** identifies the data. It can be customized and must be unique in the same data provider application.
"Proxy=true" means to access data without starting the data provider. If **Proxy** is not set to **true**, the data provider is started. ## Constraints
The **DatamgrService** obtains the data provider application based on **bundleName**, reads the configuration, verifies the permission, and accesses data. - Currently, only the RDB stores support silent data access.
- The system supports a maximum of 16 concurrent query operations. Excess query requests need to be queued for processing.
- The proxy is not allowed to create a database for persistent data. To create a database, you must start the data provider.
- If the data provider is an application with a normal signature, the data read/write permission must be system_basic or higher.
## Constraints ## Available APIs
- Currently, only RDB stores support silent access. The following table lists the APIs for silent data access. Most of the APIs are executed asynchronously in callback or promise mode. In the following table, callback-based APIs are used as an example. For more information about the APIs, see [Data Sharing](../reference/apis/js-apis-data-dataShare.md).
- The system supports a maximum of 16 concurrent query operations. Excess query requests need to be queued for processing. ### Common API
| API | Description |
| ---------------------------------------- | -------------------- |
| createDataShareHelper(context: Context, uri: string, options: DataShareHelperOptions, callback: AsyncCallback&lt;DataShareHelper&gt;): void | Creates a **DataShareHelper** instance.|
### APIs for Persistent Data
| API | Description |
| ---------------------------------------- | -------------------- |
| insert(uri: string, value: ValuesBucket, callback: AsyncCallback&lt;number&gt;): void | Inserts a row of data into a table. |
| delete(uri: string, predicates: dataSharePredicates.DataSharePredicates, callback: AsyncCallback&lt;number&gt;): void | Deletes one or more data records from the database. |
| query(uri: string, predicates: dataSharePredicates.DataSharePredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;DataShareResultSet&gt;): void | Queries data in the database. |
| update(uri: string, predicates: dataSharePredicates.DataSharePredicates, value: ValuesBucket, callback: AsyncCallback&lt;number&gt;): void | Updates data in the database. |
| addTemplate(uri: string, subscriberId: string, template: Template): void | Adds a data template with the specified subscriber. |
| on(type: 'rdbDataChange', uris: Array&lt;string&gt;, templateId: TemplateId, callback: AsyncCallback&lt;RdbDataChangeNode&gt;): Array&lt;OperationResult | Subscribes to the changes of the data corresponding to the specified URI and template.|
### APIs for Process Data
| API | Description |
| ---------------------------------------- | ------------------ |
| publish(data: Array&lt;PublishedItem&gt;, bundleName: string, version: number, callback: AsyncCallback&lt;Array&lt;OperationResult&gt;&gt;): void | Publish data to the **DatamgrService**.|
| on(type: 'publishedDataChange', uris: Array&lt;string&gt;, subscriberId: string, callback: AsyncCallback&lt;PublishedDataChangeNode&gt;): Array&lt;OperationResult&gt; | Subscribes to changes of the published data. |
## Implementation of the Persistence Data
The following describes how to share an RDB store.
### Data Provider Application
1. In the **module.json5** file, set the ID, read/write permissions, and basic information of the table to be shared under **proxyDatas**.
**Table 1** Fields of proxyDatas in module.json5
| Field | Description | Mandatory |
| ----------------------- | ---------------------------------------- | ---- |
| uri | URI of the data, which is the unique identifier for cross-application data access. | Yes |
| requiredReadPermission | Permission required for reading data from the data proxy. If this parameter is not set, other applications are not allowed to access data. For details about the supported permissions, see [Application Permission List](../security/permission-list.md). | No |
| requiredWritePermission | Permission required for modifying data from the data proxy. If this parameter is not set, other applications are not allowed to modify the data. For details about the supported permissions, see [Application Permission List](../security/permission-list.md). | No |
| metadata | Data source information, including the **name** and **resource** fields.<br> The **name** field identifies the configuration, which has a fixed value of **dataProperties**.<br> The value of **resource** is **$profile:{fileName}**, indicating that the name of the configuration file is **{fileName}.json**.| Yes |
**module.json5 example**
```json
"proxyDatas":[
{
"uri": "datashareproxy://com.acts.ohos.data.datasharetest/test",
"requiredReadPermission": "ohos.permission.GET_BUNDLE_INFO",
"requiredWritePermission": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"metadata": {
"name": "dataProperties",
"resource": "$profile:my_config"
}
}
]
```
**Table 2** Fields in my_config.json
| Field | Description | Mandatory |
| ----- | ---------------------------------------- | ---- |
| path | Data source path, in the **Database_name/Table_name** format. Currently, only RDB stores are supported. | Yes |
| type | Database type. Currently, only **rdb** is supported. | Yes |
| scope | Scope of the database.<br>- **module** indicates that the database is located in this module.<br>- **application** indicates that the database is located in this application.| No |
**my_config.json example**
```json
{
"path": "DB00/TBL00",
"type": "rdb",
"scope": "application"
}
```
### Data Consumer Application
1. Import dependencies.
```js
import dataShare from '@ohos.data.dataShare';
import dataSharePredicates from '@ohos.data.dataSharePredicates';
```
2. Define the URI string for communicating with the data provider.
```js
let dseUri = ('datashareproxy://com.acts.ohos.data.datasharetest/test');
```
3. Create a **DataShareHelper** instance.
```js
let dsHelper;
let abilityContext;
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
abilityContext = this.context;
dataShare.createDataShareHelper(abilityContext, "", {
isProxy: true
}, (err, data) => {
dsHelper = data;
});
}
}
```
4. Use the APIs provided by **DataShareHelper** to access the services provided by the provider, for example, adding, deleting, modifying, and querying data.
```js
// Construct a piece of data.
let valuesBucket = {
'name': 'ZhangSan', 'age': 21, 'isStudent': false, 'Binary': new Uint8Array([1, 2, 3])
};
let updateBucket = {
'name': 'LiSi', 'age': 18, 'isStudent': true, 'Binary': new Uint8Array([1, 2, 3])
};
let predicates = new dataSharePredicates.DataSharePredicates();
let valArray = ['*'];
// Insert a piece of data.
dsHelper.insert(dseUri, valuesBucket, (err, data) => {
console.info(`dsHelper insert result:${data}`);
});
// Update data.
dsHelper.update(dseUri, predicates, updateBucket, (err, data) => {
console.info(`dsHelper update result:${data}`);
});
// Query data.
dsHelper.query(dseUri, predicates, valArray, (err, data) => {
console.info(`dsHelper query result:${data}`);
});
// Delete data.
dsHelper.delete(dseUri, predicates, (err, data) => {
console.info(`dsHelper delete result:${data}`);
});
```
5. Subscribe to the specified data.
```js
function onCallback(err, node: dataShare.RdbDataChangeNode) {
console.info("uri " + JSON.stringify(node.uri));
console.info("templateId " + JSON.stringify(node.templateId));
console.info("data length " + node.data.length);
for (let i = 0; i < node.data.length; i++) {
console.info("data " + node.data[i]);
}
}
let template = {
predicates: {
"p1": "select * from TBL00",
"p2": "select name from TBL00",
},
scheduler: ""
}
dsProxyHelper.addTemplate(dseUri, "111", template);
let templateId: dataShare.TemplateId = {
subscriberId: "111",
bundleNameOfOwner: "com.acts.ohos.data.datasharetestclient"
}
// When the DatamgrService modifies data, onCallback is invoked to return the data queried based on the rules in the template.
let result: Array<dataShare.OperationResult> = dsProxyHelper.on("rdbDataChange", [dseUri], templateId, onCallback);
```
## Implementation of the Process Data
The following describes how to host process data.
### (Optional) Data Provider Application
In the **module.json5** file of the data provider, set the process data ID, read/write permissions, and basic information under **proxyDatas**.
> **NOTE**
>
> - This step is optional.
> - If **proxyDatas** is not configured, the hosted data cannot be accessed by other applications.
> - If **proxyDatas** is not configured, you do not need to use the full data path. For example, you can use **weather** instead of **datashareproxy://com.acts.ohos.data.datasharetest/weather** when publishing, subscribing to, and querying data.
**Table 3** Fields of proxyDatas in module.json5
| Field | Description | Mandatory |
| ----------------------- | ----------------------------- | ---- |
| uri | URI of the data, which is the unique identifier for cross-application data access. | Yes |
| requiredReadPermission | Permission required for reading data from the data proxy. If this parameter is not set, other applications are not allowed to access data. For details about the supported permissions, see [Application Permission List](../security/permission-list.md).| No |
| requiredWritePermission | Permission required for modifying data from the data proxy. If this parameter is not set, other applications are not allowed to access data. For details about the supported permissions, see [Application Permission List](../security/permission-list.md).| No |
**module.json5 example**
```json
"proxyDatas": [
{
"uri": "datashareproxy://com.acts.ohos.data.datasharetest/weather",
"requiredReadPermission": "ohos.permission.GET_BUNDLE_INFO",
"requiredWritePermission": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
]
```
### Data Consumer Application
1. Import dependencies.
```js
import dataShare from '@ohos.data.dataShare';
```
2. Create a **DataShareHelper** instance.
```js
let dsHelper;
let abilityContext;
- A proxy cannot be used to create a database. If a database needs to be created, the data provider must be started. export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
abilityContext = this.context;
dataShare.createDataShareHelper(abilityContext, "", {isProxy : true}, (err, data) => {
dsHelper = data;
});
}
}
```
3. Use the APIs provided by **DataShareHelper** to access the services provided by the provider, for example, adding, deleting, modifying, and querying data.
## How to Develop ```js
// Construct two pieces of data. The first data is not configured with proxyDatas and cannot be accessed by other applications.
let data : Array<dataShare.PublishedItem> = [
{key:"city", subscriberId:"11", data:"xian"},
{key:"datashareproxy://com.acts.ohos.data.datasharetest/weather", subscriberId:"11", data:JSON.stringify("Qing")}];
// Publish data.
let result: Array<dataShare.OperationResult> = await dsProxyHelper.publish(data, "com.acts.ohos.data.datasharetestclient");
```
The URI must be in the following format: 4. Subscribe to the specified data.
datashare:///{bundleName}/{moduleName}/{storeName}/{tableName}?Proxy=true ```js
function onPublishCallback(err, node:dataShare.PublishedDataChangeNode) {
console.info("onPublishCallback");
}
let uris:Array<string> = ["city", "datashareproxy://com.acts.ohos.data.datasharetest/weather"];
let result: Array<dataShare.OperationResult> = dsProxyHelper.on("publishedDataChange", uris, "11", onPublishCallback);
```
For details about the development procedure and implementation, see [Sharing Data Using DataShareExtensionAbility](share-data-by-datashareextensionability.md).
...@@ -7,7 +7,7 @@ The distributed application data synchronization allows the data of an applicati ...@@ -7,7 +7,7 @@ The distributed application data synchronization allows the data of an applicati
For example, when data is added, deleted, or modified for an application on a device, the same application on another device can obtain the updated data. You can use this feature in the distributed Gallery, Notepad, Contacts, and File Manager. For example, when data is added, deleted, or modified for an application on a device, the same application on another device can obtain the updated data. You can use this feature in the distributed Gallery, Notepad, Contacts, and File Manager.
For details about how to subscribe to database change notifications between different applications, see [Sharing Application Data with Other Applications](share-device-data-across-apps-overview.md). For details about how to subscribe to database change notifications between different applications, see [Cross-Application Data Sharing](data-share-overview.md).
The data storage modes vary depending on the lifecycle of data to be synchronized: The data storage modes vary depending on the lifecycle of data to be synchronized:
...@@ -24,7 +24,7 @@ In a distributed scenario, cross-device collaboration demands consistent data be ...@@ -24,7 +24,7 @@ In a distributed scenario, cross-device collaboration demands consistent data be
The data consistency can be classified into the following types: The data consistency can be classified into the following types:
- Strong consistency: When data is inserted, deleted, or modified on a device, other devices in the same network can obtain the updates eventually, but may not immediately. - Strong consistency: When data is inserted, deleted, or modified on a device, other devices in the same network will obtain the latest data immediately. Once data is modified, the devices can read the updated data eventually, but may not read the updated data immediately.
- Weak consistency: When data is added, deleted, or modified on a device, other devices in the same network may or may not obtain the updates. The data on these devices may be inconsistent after a certain period of time. - Weak consistency: When data is added, deleted, or modified on a device, other devices in the same network may or may not obtain the updates. The data on these devices may be inconsistent after a certain period of time.
......
# Sharing Data via Unified Data Channels
## When to Use
In many-to-many data sharing across applications, a data channel needs to be provided to access data of different applications and share the data with other applications.
The Unified Data Management Framework (UDMF) provides unified data channels and standard data access interfaces for different service scenarios of many-to-many cross-application data sharing.
## Definition and Implementation of Unified Data Channels
The unified data channel provides cross-application data access for various service scenarios. It can temporarily store the unified data objects to be shared by an application, and manage the access permissions and lifecycle of the data according to certain policies.
The unified data channel is implemented by the system ability provided by the UDMF. When an application (data provider) needs to share data, it calls the **insert()** method provided by the UDMF to write the data to the UDMF data channel, and calls UDMF **update()** or **delete()** to update or delete the data. After passing the permission verification, the target application (data consumer) calls the UDMF **read()** to access the data. After the data is read, the UDMF performs lifecycle management of the data.
The unified data object (**UnifiedData**) is uniquely identified by a URI in the UDMF data channel. The URI is in the **udmf://*intention*/*bundleName*/*groupId*** format, where:
+ **udmf**: protocol used to provide the data channel.
+ *intention*: an enum of the data channel types supported by the UDMF.
+ *bundleName*: bundle name of the data source application.
+ *groupId*: group ID used for batch data management.
Currently, the UDMF provides the public data channel for cross-application data sharing.
**Public data channel**: allows applications to write and read data. The corresponding **intention** is **DATA_HUB**.
## Available APIs
The following table lists the UDMF APIs. All of them are executed asynchronously in callback or promise mode. In the following table, callback-based APIs are used as an example. For more information about the APIs, see [UDMF](../reference/apis/js-apis-data-udmf.md).
| API | Description |
|-----------------------------------------------------------------------------------------|---------------------------------------------|
| insertData(options: Options, data: UnifiedData, callback: AsyncCallback\<string>): void | Inserts data to the UDMF public data channel. A unique data identifier is returned.|
| updateData(options: Options, data: UnifiedData, callback: AsyncCallback\<void>): void | Updates the data in the UDMF public data channel. |
| queryData(options: Options, callback: AsyncCallback\<Array\<UnifiedData>>): void | Queries data in the UDMF public data channel. |
| deleteData(options: Options, callback: AsyncCallback\<Array\<UnifiedData>>): void | Deletes data from the UDMF public data channel. The deleted data set is returned.|
## How to Develop
The following example describes how to implement many-to-many data sharing. The data provider writes data to the UMDF public data channel, and updates and deletes the data. The data consumer obtains the data shared by the data provider.
### Data Provider
1. Import the **@ohos.data.UDMF** module.
```ts
import UDMF from '@ohos.data.UDMF';
```
2. Create a **UnifiedData** object and insert it into the UDMF public data channel.
```ts
let plainText = new UDMF.PlainText();
plainText.textContent = 'hello world!';
let unifiedData = new UDMF.UnifiedData(plainText);
// Specify the type of the data channel to which the data is to be inserted.
let options = {
intention: UDMF.Intention.DATA_HUB
}
try {
UDMF.insertData(options, unifiedData, (err, data) => {
if (err === undefined) {
console.info(`Succeeded in inserting data. key = ${data}`);
} else {
console.error(`Failed to insert data. code is ${err.code},message is ${err.message} `);
}
});
} catch(e) {
console.error(`Insert data throws an exception. code is ${e.code},message is ${e.message} `);
}
```
3. Update the **UnifiedData** object inserted.
```ts
let plainText = new UDMF.PlainText();
plainText.textContent = 'How are you!';
let unifiedData = new UDMF.UnifiedData(plainText);
// Specify the URI of the UnifiedData object to update.
let options = {
key: 'udmf://DataHub/com.ohos.test/0123456789'
};
try {
UDMF.updateData(options, unifiedData, (err) => {
if (err === undefined) {
console.info('Succeeded in updating data.');
} else {
console.error(`Failed to update data. code is ${err.code},message is ${err.message} `);
}
});
} catch(e) {
console.error(`Update data throws an exception. code is ${e.code},message is ${e.message} `);
}
```
4. Delete the **UnifiedData** object from the UDMF public data channel.
```ts
// Specify the type of the data channel whose data is to be deleted.
let options = {
intention: UDMF.Intention.DATA_HUB
};
try {
UDMF.deleteData(options, (err, data) => {
if (err === undefined) {
console.info(`Succeeded in deleting data. size = ${data.length}`);
for (let i = 0; i < data.length; i++) {
let records = data[i].getRecords();
for (let j = 0; j < records.length; j++) {
if (records[j].getType() === UDMF.UnifiedDataType.PLAIN_TEXT) {
let text = <UDMF.PlainText>(records[j]);
console.info(`${i + 1}.${text.textContent}`);
}
}
}
} else {
console.error(`Failed to delete data. code is ${err.code},message is ${err.message} `);
}
});
} catch(e) {
console.error(`Delete data throws an exception. code is ${e.code},message is ${e.message} `);
}
```
### Data Consumer
1. Import the **@ohos.data.UDMF** module.
```ts
import UDMF from '@ohos.data.UDMF';
```
2. Query the **UnifiedData** object in the UDMF public data channel.
```ts
// Specify the type of the data channel whose data is to be queried.
let options = {
intention: UDMF.Intention.DATA_HUB
};
try {
UDMF.queryData(options, (err, data) => {
if (err === undefined) {
console.info(`Succeeded in querying data. size = ${data.length}`);
for (let i = 0; i < data.length; i++) {
let records = data[i].getRecords();
for (let j = 0; j < records.length; j++) {
if (records[j].getType() === UDMF.UnifiedDataType.PLAIN_TEXT) {
let text = <UDMF.PlainText>(records[j]);
console.info(`${i + 1}.${text.textContent}`);
}
}
}
} else {
console.error(`Failed to query data. code is ${err.code},message is ${err.message} `);
}
});
} catch(e) {
console.error(`Query data throws an exception. code is ${e.code},message is ${e.message} `);
}
```
# Unified Data Definition
## When to Use
To streamline cross-application data interaction of OpenHarmony and minimize the application/service data interaction costs, the Unified Data Management Framework (UDMF) provides standard data definitions to define common data types. Applications can use the APIs provided by the UDMF to create and use these data types.
## Unified Data Types
The UDMF provides the following unified data types:
**Basic data types**<br>Basic data types include File and Text, which can be used for cross-application and cross-platform data interaction. Figure 1 and Figure 2 illustrate the basic data types.
**Figure 1** UDMF File
![UDMF_FILE](figures/udmf_type_File.png)
Figure 2 UDMF Text
![UDMF_TEXT](figures/udmf_type_Text.png)
**System Defined Types (SDTs)**<br>The SDTs are specific to the platform or operating system, such as Form (UI card information), AppItem (app description information), and PixelMap (thumbnail). This type of data can be used for cross-application data interaction in a system or platform. Figure 3 illustrates the SDT data.
**Figure 3** UDMF SDT data
![UDMF_SDT](figures/udmf_type_SDT.png)
**App Defined Type (ADT)**<br>The SDT data is application-specific. This type of data can be used for across-platform data interaction for an application. As shown in Figure 4, the MyFile file format can be defined for use in an application ecosystem.
**Figure 4** UDMF ADT data
![UDMF_ADT](figures/udmf_type_ADT.png)
## Restrictions
- The size of each data record in the UDMF cannot exceed 2 MB.
- The UDMF supports data group management. The size of each group cannot exceed 4 MB.
## Available APIs
The UDMF provides the unified data object **UnifiedData** to encapsulate a group of data records **UnifiedRecord**. **UnifiedRecord** is an abstract definition of data content supported by the UDMF, for example, a text record or an image record. The data content type in a data record corresponds to **UnifiedDataType**.
The following table describes common UDMF APIs. For more information, see [UDMF](../reference/apis/js-apis-data-udmf.md).
| Class | API | Description |
|---------------|-------------------|-----------------------------------------------------------------------------------------------|
| UnifiedRecord | getType(): string | Obtains the data type of this data record.|
| UnifiedData | constructor(record: UnifiedRecord) | A constructor used to create a **UnifiedData** object with a data record. |
| UnifiedData | addRecord(record: UnifiedRecord): void | Adds a data record to this **UnifiedRecord** object. |
| UnifiedData | getRecords(): Array\<UnifiedRecord> | Obtains all data records from this **UnifiedData** object. The data obtained is of the **UnifiedRecord** type. You need to obtain the data type by using **getType** and convert the data type to a child class before using it.|
## How to Develop
The following describes how to create a **UnifiedData** object containing two data records: image and plain text.
1. Import the **@ohos.data.UDMF** module.
```ts
import UDMF from '@ohos.data.UDMF';
```
2. Create an image data record and initialize the **UnifiedData** object with the image data record.
(1) Create an image data record.
```ts
let image = new UDMF.Image();
```
(2) Modify object attributes.
```ts
// The Image object contains the imageUri attribute.
image.imageUri = '...';
```
(3) Access the object attributes.
```ts
console.info(`imageUri = ${image.imageUri}`);
```
(4) Create a **UnifiedData** instance.
```ts
let unifiedData = new UDMF.UnifiedData(image);
```
3. Create a plain text data record and add it to the **UnifiedData** instance created.
```ts
let plainText = new UDMF.PlainText();
plainText.textContent = 'this is textContent of plainText';
plainText.abstract = 'abstract of plainText';
plainText.details = {
plainKey1: 'plainValue1',
plainKey2: 'plainValue2',
};
unifiedData.addRecord(plainText);
```
4. Obtain all data records in this **UnifiedData** instance.
```ts
let records = unifiedData.getRecords();
```
5. Traverse each record, determine the data type of the record, and convert the record into a child class object to obtain the original data record.
```ts
for (let i = 0; i < records.length; i ++) {
// Read the type of the data record.
let type = records[i].getType();
switch (type) {
case UDMF.UnifiedDataType.IMAGE:
// Convert the data to obtain the original image data record.
let image = <UDMF.Image>(records[i]);
break;
case UDMF.UnifiedDataType.PLAIN_TEXT:
// Convert the data to obtain the original text record.
let plainText = <UDMF.PlainText>(records[i]);
break;
default:
break;
}
}
```
...@@ -4,6 +4,8 @@ When a user needs to download a file from the network to a local directory or sa ...@@ -4,6 +4,8 @@ When a user needs to download a file from the network to a local directory or sa
The operations for saving images, audio or video clips, and documents are similar. Call **save()** of the corresponding picker instance and pass in **saveOptions**. The operations for saving images, audio or video clips, and documents are similar. Call **save()** of the corresponding picker instance and pass in **saveOptions**.
The **save()** interface saves the file in the file manager, not in the Gallery.
## Saving Images or Video Files ## Saving Images or Video Files
...@@ -23,14 +25,14 @@ The operations for saving images, audio or video clips, and documents are simila ...@@ -23,14 +25,14 @@ The operations for saving images, audio or video clips, and documents are simila
3. Create a **photoViewPicker** instance and call [save()](../reference/apis/js-apis-file-picker.md#save) to open the **FilePicker** page to save the files. After the user selects the target folder, the file saving operation is complete. After the files are saved successfully, the URIs of the files saved are returned. 3. Create a **photoViewPicker** instance and call [save()](../reference/apis/js-apis-file-picker.md#save) to open the **FilePicker** page to save the files. After the user selects the target folder, the file saving operation is complete. After the files are saved successfully, the URIs of the files saved are returned.
<br>The permission on the URIs returned by **save()** is read/write. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening. The permission on the URIs returned by **save()** is read/write. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening.
```ts ```ts
let URI = null; let uri = null;
const photoViewPicker = new picker.PhotoViewPicker(); const photoViewPicker = new picker.PhotoViewPicker();
photoViewPicker.save(photoSaveOptions).then((photoSaveResult) => { photoViewPicker.save(photoSaveOptions).then((photoSaveResult) => {
URI = photoSaveResult[0]; uri = photoSaveResult[0];
console.info('photoViewPicker.save to file succeed and URI is:' + URI); console.info('photoViewPicker.save to file succeed and uri is:' + uri);
}).catch((err) => { }).catch((err) => {
console.error(`Invoke photoViewPicker.save failed, code is ${err.code}, message is ${err.message}`); console.error(`Invoke photoViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
}) })
...@@ -39,7 +41,7 @@ The operations for saving images, audio or video clips, and documents are simila ...@@ -39,7 +41,7 @@ The operations for saving images, audio or video clips, and documents are simila
4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_WRITE**. 4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_WRITE**.
```ts ```ts
let file = fs.openSync(URI, fs.OpenMode.READ_WRITE); let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
console.info('file fd: ' + file.fd); console.info('file fd: ' + file.fd);
``` ```
...@@ -72,11 +74,11 @@ The operations for saving images, audio or video clips, and documents are simila ...@@ -72,11 +74,11 @@ The operations for saving images, audio or video clips, and documents are simila
The permission on the URIs returned by **save()** is read/write. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening. The permission on the URIs returned by **save()** is read/write. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening.
```ts ```ts
let URI = null; let uri = null;
const documentViewPicker = new picker.DocumentViewPicker(); // Create a documentViewPicker instance. const documentViewPicker = new picker.DocumentViewPicker(); // Create a documentViewPicker instance.
documentViewPicker.save(documentSaveOptions).then((documentSaveResult) => { documentViewPicker.save(documentSaveOptions).then((documentSaveResult) => {
URI = documentSaveResult[0]; uri = documentSaveResult[0];
console.info('documentViewPicker.save to file succeed and URI is:' + URI); console.info('documentViewPicker.save to file succeed and uri is:' + uri);
}).catch((err) => { }).catch((err) => {
console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`); console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
}) })
...@@ -85,7 +87,7 @@ The operations for saving images, audio or video clips, and documents are simila ...@@ -85,7 +87,7 @@ The operations for saving images, audio or video clips, and documents are simila
4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_WRITE**. 4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_WRITE**.
```ts ```ts
let file = fs.openSync(URI, fs.OpenMode.READ_WRITE); let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
console.info('file fd: ' + file.fd); console.info('file fd: ' + file.fd);
``` ```
...@@ -118,11 +120,11 @@ The operations for saving images, audio or video clips, and documents are simila ...@@ -118,11 +120,11 @@ The operations for saving images, audio or video clips, and documents are simila
The permission on the URIs returned by **save()** is read/write. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening. The permission on the URIs returned by **save()** is read/write. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening.
```ts ```ts
let URI = null; let uri = null;
const audioViewPicker = new picker.AudioViewPicker(); const audioViewPicker = new picker.AudioViewPicker();
audioViewPicker.save(audioSaveOptions).then((audioSelectResult) => { audioViewPicker.save(audioSaveOptions).then((audioSelectResult) => {
URI = audioSelectResult[0]; uri = audioSelectResult[0];
console.info('audioViewPicker.save to file succeed and URI is:' + URI); console.info('audioViewPicker.save to file succeed and uri is:' + uri);
}).catch((err) => { }).catch((err) => {
console.error(`Invoke audioViewPicker.save failed, code is ${err.code}, message is ${err.message}`); console.error(`Invoke audioViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
}) })
...@@ -131,11 +133,11 @@ The operations for saving images, audio or video clips, and documents are simila ...@@ -131,11 +133,11 @@ The operations for saving images, audio or video clips, and documents are simila
4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_WRITE**. 4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_WRITE**.
```ts ```ts
let file = fs.openSync(URI, fs.OpenMode.READ_WRITE); let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
console.info('file fd: ' + file.fd); console.info('file fd: ' + file.fd);
``` ```
5. Use [fs.writeSync](../reference/apis/js-apis-file-fs.md#writesync) to edit the file based on the FD, and then close the FD. 5. Use [fs.writeSync()](../reference/apis/js-apis-file-fs.md#writesync) to edit the file based on the FD, and then close the FD.
```ts ```ts
let writeLen = fs.writeSync(file.fd, 'hello, world'); let writeLen = fs.writeSync(file.fd, 'hello, world');
......
...@@ -35,14 +35,14 @@ The **FilePicker** provides the following interfaces by file type: ...@@ -35,14 +35,14 @@ The **FilePicker** provides the following interfaces by file type:
4. Create a **photoPicker** instance and call [select()](../reference/apis/js-apis-file-picker.md#select) to open the **FilePicker** page for the user to select files. After the files are selected, [PhotoSelectResult](../reference/apis/js-apis-file-picker.md#photoselectresult) is returned. 4. Create a **photoPicker** instance and call [select()](../reference/apis/js-apis-file-picker.md#select) to open the **FilePicker** page for the user to select files. After the files are selected, [PhotoSelectResult](../reference/apis/js-apis-file-picker.md#photoselectresult) is returned.
<br>The permission on the URIs returned by **select()** is read-only. Further file operations can be performed based on the URIs in the **PhotoSelectResult**. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening. The permission on the URIs returned by **select()** is read-only. Further file operations can be performed based on the URIs in the **PhotoSelectResult**. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening.
```ts ```ts
let URI = null; let uri = null;
const photoViewPicker = new picker.PhotoViewPicker(); const photoViewPicker = new picker.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => { photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => {
URI = photoSelectResult.photoUris[0]; uri = photoSelectResult.photoUris[0];
console.info('photoViewPicker.select to file succeed and URI is:' + URI); console.info('photoViewPicker.select to file succeed and uri is:' + uri);
}).catch((err) => { }).catch((err) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`); console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
}) })
...@@ -51,7 +51,7 @@ The **FilePicker** provides the following interfaces by file type: ...@@ -51,7 +51,7 @@ The **FilePicker** provides the following interfaces by file type:
5. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_ONLY**. 5. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_ONLY**.
```ts ```ts
let file = fs.openSync(URI, fs.OpenMode.READ_ONLY); let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
console.info('file fd: ' + file.fd); console.info('file fd: ' + file.fd);
``` ```
...@@ -81,20 +81,20 @@ The **FilePicker** provides the following interfaces by file type: ...@@ -81,20 +81,20 @@ The **FilePicker** provides the following interfaces by file type:
3. Create a **documentViewPicker** instance, and call [**select()**](../reference/apis/js-apis-file-picker.md#select-3) to open the **FilePicker** page for the user to select documents. After the documents are selected, a result set containing the file URIs is returned. 3. Create a **documentViewPicker** instance, and call [**select()**](../reference/apis/js-apis-file-picker.md#select-3) to open the **FilePicker** page for the user to select documents. After the documents are selected, a result set containing the file URIs is returned.
<br>The permission on the URIs returned by **select()** is read-only. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening. The permission on the URIs returned by **select()** is read-only. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening.
<br>For example, you can use [file management APIs](../reference/apis/js-apis-file-fs.md) to obtain file attribute information, such as the file size, access time, and last modification time, based on the URI. If you need to obtain the file name, use [startAbilityForResult](../../application-dev/application-models/uiability-intra-device-interaction.md). For example, you can use [file management APIs](../reference/apis/js-apis-file-fs.md) to obtain file attribute information, such as the file size, access time, and last modification time, based on the URI. If you need to obtain the file name, use [startAbilityForResult](../../application-dev/application-models/uiability-intra-device-interaction.md).
> **NOTE** > **NOTE**
> >
> Currently, **DocumentSelectOptions** is not configurable. By default, all types of user files are selected. > Currently, **DocumentSelectOptions** is not configurable. By default, all types of user files are selected.
```ts ```ts
let URI = null; let uri = null;
const documentViewPicker = new picker.DocumentViewPicker(); // Create a documentViewPicker instance. const documentViewPicker = new picker.DocumentViewPicker(); // Create a documentViewPicker instance.
documentViewPicker.select(documentSelectOptions).then((documentSelectResult) => { documentViewPicker.select(documentSelectOptions).then((documentSelectResult) => {
URI = documentSelectResult[0]; uri = documentSelectResult[0];
console.info('documentViewPicker.select to file succeed and URI is:' + URI); console.info('documentViewPicker.select to file succeed and uri is:' + uri);
}).catch((err) => { }).catch((err) => {
console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`); console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
}) })
...@@ -129,7 +129,7 @@ The **FilePicker** provides the following interfaces by file type: ...@@ -129,7 +129,7 @@ The **FilePicker** provides the following interfaces by file type:
4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_ONLY**. 4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_ONLY**.
```ts ```ts
let file = fs.openSync(URI, fs.OpenMode.READ_ONLY); let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
console.info('file fd: ' + file.fd); console.info('file fd: ' + file.fd);
``` ```
...@@ -160,20 +160,20 @@ The **FilePicker** provides the following interfaces by file type: ...@@ -160,20 +160,20 @@ The **FilePicker** provides the following interfaces by file type:
3. Create an **audioViewPicker** instance, and call [**select()**](../reference/apis/js-apis-file-picker.md#select-6) to open the **FilePicker** page for the user to select audio files. After the files are selected, a result set containing the URIs of the audio files selected is returned. 3. Create an **audioViewPicker** instance, and call [**select()**](../reference/apis/js-apis-file-picker.md#select-6) to open the **FilePicker** page for the user to select audio files. After the files are selected, a result set containing the URIs of the audio files selected is returned.
<br>The permission on the URIs returned by **select()** is read-only. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening. The permission on the URIs returned by **select()** is read-only. Further file operations can be performed based on the URIs in the result set. Note that the URI cannot be directly used in the **picker** callback to open a file. You need to define a global variable to save the URI and use a button to trigger file opening.
<br>For example, use the [file management interface](../reference/apis/js-apis-file-fs.md) to obtain the file handle (FD) of the audio clip based on the URI, and then develop the audio playback function based on the media service. For details, see [Audio Playback Development](../media/audio-playback-overview.md). For example, use the [file management interface](../reference/apis/js-apis-file-fs.md) to obtain the file handle (FD) of the audio clip based on the URI, and then develop the audio playback function based on the media service. For details, see [Audio Playback Development](../media/audio-playback-overview.md).
> **NOTE** > **NOTE**
> >
> Currently, **AudioSelectOptions** is not configurable. By default, all types of user files are selected. > Currently, **AudioSelectOptions** is not configurable. By default, all types of user files are selected.
```ts ```ts
let URI = null; let uri = null;
const audioViewPicker = new picker.AudioViewPicker(); const audioViewPicker = new picker.AudioViewPicker();
audioViewPicker.select(audioSelectOptions).then(audioSelectResult => { audioViewPicker.select(audioSelectOptions).then(audioSelectResult => {
URI = audioSelectOptions[0]; uri = audioSelectOptions[0];
console.info('audioViewPicker.select to file succeed and URI is:' + URI); console.info('audioViewPicker.select to file succeed and uri is:' + uri);
}).catch((err) => { }).catch((err) => {
console.error(`Invoke audioViewPicker.select failed, code is ${err.code}, message is ${err.message}`); console.error(`Invoke audioViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
}) })
...@@ -182,7 +182,7 @@ The **FilePicker** provides the following interfaces by file type: ...@@ -182,7 +182,7 @@ The **FilePicker** provides the following interfaces by file type:
4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_ONLY**. 4. Use a button to trigger invocation of other functions. Use [fs.openSync()](../reference/apis/js-apis-file-fs.md#fsopensync) to open the file based on the URI and obtain the FD. Note that the **mode** parameter of **fs.openSync()** must be **fs.OpenMode.READ_ONLY**.
```ts ```ts
let file = fs.openSync(URI, fs.OpenMode.READ_ONLY); let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
console.info('file fd: ' + file.fd); console.info('file fd: ' + file.fd);
``` ```
......
...@@ -40,7 +40,7 @@ Obtains a **Preferences** instance. This API uses an asynchronous callback to re ...@@ -40,7 +40,7 @@ Obtains a **Preferences** instance. This API uses an asynchronous callback to re
| -------- | ------------------------------------------------ | ---- | ------------------------------------------------------------ | | -------- | ------------------------------------------------ | ---- | ------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). | | context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). |
| name | string | Yes | Name of the **Preferences** instance. | | name | string | Yes | Name of the **Preferences** instance. |
| callback | AsyncCallback&lt;[Preferences](#preferences)&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined** and the **Preferences** instance obtained is returned. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;[Preferences](#preferences)&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined** and the **Preferences** instance obtained is returned. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -159,15 +159,166 @@ class EntryAbility extends UIAbility { ...@@ -159,15 +159,166 @@ class EntryAbility extends UIAbility {
} }
``` ```
## data_preferences.getPreferences<sup>10+</sup>
getPreferences(context: Context, options: Options, callback: AsyncCallback&lt;Preferences&gt;): void
Obtains a **Preferences** instance. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | --------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| options | [Options](#options10) | Yes | Configuration options of the **Preferences** instance. |
| callback | AsyncCallback&lt;[Preferences](#preferences)&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined** and the **Preferences** instance obtained is returned. Otherwise, **err** is an error object. |
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | ------------------------------ |
| 15501001 | Only supported in stage mode. |
| 15501002 | The data group id is not valid. |
**Example**
FA model:
```js
// Obtain the context.
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
let preferences = null;
try {
data_preferences.getPreferences(context, {name: 'mystore'}, function (err, val) {
if (err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
return;
}
preferences = val;
console.info("Obtained the preferences successfully.");
})
} catch (err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
}
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
let preferences = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
data_preferences.getPreferences(this.context, {name: 'mystore', dataGroupId:'myId'}, function (err, val) {
if (err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
return;
}
preferences = val;
console.info("Obtained the preferences successfully.");
})
} catch (err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
}
}
}
```
## data_preferences.getPreferences<sup>10+</sup>
getPreferences(context: Context, options: Options): Promise&lt;Preferences&gt;
Obtains a **Preferences** instance. This API uses a promise to return the result.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ------- | ---------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| options | [Options](#options10) | Yes | Configuration options of the **Preferences** instance. |
**Return value**
| Type | Description |
| --------------------------------------- | ---------------------------------- |
| Promise&lt;[Preferences](#preferences)&gt; | Promise used to return the **Preferences** instance obtained.|
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | ------------------------------ |
| 15501001 | Only supported in stage mode. |
| 15501002 | The data group id is not valid. |
**Example**
FA model:
```js
// Obtain the context.
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
let preferences = null;
try {
let promise = data_preferences.getPreferences(context, {name: 'mystore'});
promise.then((object) => {
preferences = object;
console.info("Obtained the preferences successfully.");
}).catch((err) => {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
})
} catch(err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
}
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
let preferences = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
let promise = data_preferences.getPreferences(this.context, {name: 'mystore', dataGroupId:'myId'});
promise.then((object) => {
preferences = object;
console.info("Obtained the preferences successfully.");
}).catch((err) => {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
})
} catch(err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
}
}
}
```
## data_preferences.deletePreferences ## data_preferences.deletePreferences
deletePreferences(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void deletePreferences(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void
Deletes a **Preferences** instance from the memory. This API uses an asynchronous callback to return the result. Deletes a **Preferences** instance from the cache. If the **Preferences** instance has a persistent file, the persistent file will also be deleted. This API uses an asynchronous callback to return the result.
If the **Preferences** instance has a persistent file, this API also deletes the persistent file. After the **Preferences** instance is deleted, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the deleted **Preferences** instance to null. The system will reclaim the deleted **Preferences** instances in a unified manner.
The deleted **Preferences** instance cannot be used for data operations. Otherwise, data inconsistency will be caused.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core **System capability**: SystemCapability.DistributedDataManager.Preferences.Core
...@@ -176,8 +327,8 @@ The deleted **Preferences** instance cannot be used for data operations. Otherwi ...@@ -176,8 +327,8 @@ The deleted **Preferences** instance cannot be used for data operations. Otherwi
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------------------- | ---- | ---------------------------------------------------- | | -------- | ------------------------------------- | ---- | ---------------------------------------------------- |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). | | context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). |
| name | string | Yes | Name of the **Preferences** instance to delete. | | name | string | Yes | Name of the **Preferences** instance. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object.|
**Error codes** **Error codes**
...@@ -235,11 +386,9 @@ class EntryAbility extends UIAbility { ...@@ -235,11 +386,9 @@ class EntryAbility extends UIAbility {
deletePreferences(context: Context, name: string): Promise&lt;void&gt; deletePreferences(context: Context, name: string): Promise&lt;void&gt;
Deletes a **Preferences** instance from the memory. This API uses a promise to return the result. Deletes a **Preferences** instance from the cache. If the **Preferences** instance has a persistent file, the persistent file will also be deleted. This API uses a promise to return the result.
If the **Preferences** instance has a persistent file, this API also deletes the persistent file.
The deleted **Preferences** instance cannot be used for data operations. Otherwise, data inconsistency will be caused. After the **Preferences** instance is deleted, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the deleted **Preferences** instance to null. The system will reclaim the deleted **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core **System capability**: SystemCapability.DistributedDataManager.Preferences.Core
...@@ -248,7 +397,7 @@ The deleted **Preferences** instance cannot be used for data operations. Otherwi ...@@ -248,7 +397,7 @@ The deleted **Preferences** instance cannot be used for data operations. Otherwi
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| ------- | ------------------------------------- | ---- | ----------------------- | | ------- | ------------------------------------- | ---- | ----------------------- |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). | | context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). |
| name | string | Yes | Name of the **Preferences** instance to delete.| | name | string | Yes | Name of the **Preferences** instance.|
**Return value** **Return value**
...@@ -306,13 +455,164 @@ class EntryAbility extends UIAbility { ...@@ -306,13 +455,164 @@ class EntryAbility extends UIAbility {
} }
``` ```
## data_preferences.deletePreferences<sup>10+</sup>
deletePreferences(context: Context, options: Options, callback: AsyncCallback&lt;void&gt;): void
Deletes a **Preferences** instance from the cache. If the **Preferences** instance has a persistent file, the persistent file will also be deleted. This API uses an asynchronous callback to return the result.
After the **Preferences** instance is deleted, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the deleted **Preferences** instance to null. The system will reclaim the deleted **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| options | [Options](#options10) | Yes | Configuration options of the **Preferences** instance. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object. |
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | ---------------------------------- |
| 15500010 | Failed to delete preferences file. |
| 15501001 | Only supported in stage mode. |
| 15501002 | The data group id is not valid. |
**Example**
FA model:
```js
// Obtain the context.
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
try {
data_preferences.deletePreferences(context, {name: 'mystore'}, function (err) {
if (err) {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
return;
}
console.info("Deleted the preferences successfully." );
})
} catch (err) {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
}
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
data_preferences.deletePreferences(this.context, {name: 'mystore', dataGroupId:'myId'}, function (err) {
if (err) {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
return;
}
console.info("Deleted the preferences successfully." );
})
} catch (err) {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
}
}
}
```
## data_preferences.deletePreferences<sup>10+</sup>
deletePreferences(context: Context, options: Options): Promise&lt;void&gt;
Deletes a **Preferences** instance from the cache. If the **Preferences** instance has a persistent file, the persistent file will also be deleted. This API uses a promise to return the result.
After the **Preferences** instance is deleted, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the deleted **Preferences** instance to null. The system will reclaim the deleted **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ------- | ---------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| options | [Options](#options10) | Yes | Configuration options of the **Preferences** instance. |
**Return value**
| Type | Description |
| ------------------- | ------------------------- |
| Promise&lt;void&gt; | Promise that returns no value.|
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | ---------------------------------- |
| 15500010 | Failed to delete preferences file. |
| 15501001 | Only supported in stage mode. |
| 15501002 | The data group id is not valid. |
**Example**
FA model:
```js
// Obtain the context.
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
try {
let promise = data_preferences.deletePreferences(context, {name: 'mystore'});
promise.then(() => {
console.info("Deleted the preferences successfully.");
}).catch((err) => {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
})
} catch(err) {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
}
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try{
let promise = data_preferences.deletePreferences(this.context, {name: 'mystore', dataGroupId:'myId'});
promise.then(() => {
console.info("Deleted the preferences successfully.");
}).catch((err) => {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
})
} catch(err) {
console.info("Failed to delete the preferences. code =" + err.code + ", message =" + err.message);
}
}
}
```
## data_preferences.removePreferencesFromCache ## data_preferences.removePreferencesFromCache
removePreferencesFromCache(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void removePreferencesFromCache(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void
Removes a **Preferences** instance from the cache. This API uses an asynchronous callback to return the result. Removes a **Preferences** instance from the cache. This API uses an asynchronous callback to return the result.
The removed **Preferences** instance cannot be used for data operations. Otherwise, data inconsistency will be caused. After an application calls [getPreferences](#data_preferencesgetpreferences) for the first time to obtain a **Preferences** instance, the obtained **Preferences** instance is cached. When the application calls [getPreferences](#data_preferencesgetpreferences) again, the **Preferences** instance will be read from the cache instead of from the persistent file. After this API is called to remove the instance from the cache, calling **getPreferences** again will read data from the persistent file and create a new **Preferences** instance.
After the **Preferences** instance is removed, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the removed **Preferences** instance to null. The system will reclaim the removed **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core **System capability**: SystemCapability.DistributedDataManager.Preferences.Core
...@@ -321,8 +621,8 @@ The removed **Preferences** instance cannot be used for data operations. Otherwi ...@@ -321,8 +621,8 @@ The removed **Preferences** instance cannot be used for data operations. Otherwi
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------------------- | ---- | ---------------------------------------------------- | | -------- | ------------------------------------- | ---- | ---------------------------------------------------- |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). | | context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). |
| name | string | Yes | Name of the **Preferences** instance to remove. | | name | string | Yes | Name of the **Preferences** instance. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -332,17 +632,16 @@ FA model: ...@@ -332,17 +632,16 @@ FA model:
// Obtain the context. // Obtain the context.
import featureAbility from '@ohos.ability.featureAbility'; import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext(); let context = featureAbility.getContext();
try { try {
data_preferences.removePreferencesFromCache(context, 'mystore', function (err) { data_preferences.removePreferencesFromCache(context, 'mystore', function (err) {
if (err) { if (err) {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
return; return;
} }
console.info("Removed the preferences successfully."); console.info("Removed the preferences successfully.");
}) })
} catch (err) { } catch (err) {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -356,17 +655,16 @@ class EntryAbility extends UIAbility { ...@@ -356,17 +655,16 @@ class EntryAbility extends UIAbility {
try { try {
data_preferences.removePreferencesFromCache(this.context, 'mystore', function (err) { data_preferences.removePreferencesFromCache(this.context, 'mystore', function (err) {
if (err) { if (err) {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
return; return;
} }
console.info("Removed the preferences successfully."); console.info("Removed the preferences successfully.");
}) })
} catch (err) { } catch (err) {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
} }
} }
} }
``` ```
## data_preferences.removePreferencesFromCache ## data_preferences.removePreferencesFromCache
...@@ -375,7 +673,9 @@ removePreferencesFromCache(context: Context, name: string): Promise&lt;void&gt; ...@@ -375,7 +673,9 @@ removePreferencesFromCache(context: Context, name: string): Promise&lt;void&gt;
Removes a **Preferences** instance from the cache. This API uses a promise to return the result. Removes a **Preferences** instance from the cache. This API uses a promise to return the result.
The removed **Preferences** instance cannot be used for data operations. Otherwise, data inconsistency will be caused. After an application calls [getPreferences](#data_preferencesgetpreferences) for the first time to obtain a **Preferences** instance, the obtained **Preferences** instance is cached. When the application calls [getPreferences](#data_preferencesgetpreferences) again, the **Preferences** instance will be read from the cache instead of from the persistent file. After this API is called to remove the instance from the cache, calling **getPreferences** again will read data from the persistent file and create a new **Preferences** instance.
After the **Preferences** instance is removed, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the removed **Preferences** instance to null. The system will reclaim the removed **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core **System capability**: SystemCapability.DistributedDataManager.Preferences.Core
...@@ -384,7 +684,7 @@ The removed **Preferences** instance cannot be used for data operations. Otherwi ...@@ -384,7 +684,7 @@ The removed **Preferences** instance cannot be used for data operations. Otherwi
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| ------- | ------------------------------------- | ---- | ----------------------- | | ------- | ------------------------------------- | ---- | ----------------------- |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). | | context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md). |
| name | string | Yes | Name of the **Preferences** instance to remove.| | name | string | Yes | Name of the **Preferences** instance.|
**Return value** **Return value**
...@@ -400,16 +700,15 @@ FA model: ...@@ -400,16 +700,15 @@ FA model:
// Obtain the context. // Obtain the context.
import featureAbility from '@ohos.ability.featureAbility'; import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext(); let context = featureAbility.getContext();
try { try {
let promise = data_preferences.removePreferencesFromCache(context, 'mystore'); let promise = data_preferences.removePreferencesFromCache(context, 'mystore');
promise.then(() => { promise.then(() => {
console.info("Removed the preferences successfully."); console.info("Removed the preferences successfully.");
}).catch((err) => { }).catch((err) => {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}) })
} catch(err) { } catch(err) {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -425,10 +724,10 @@ class EntryAbility extends UIAbility { ...@@ -425,10 +724,10 @@ class EntryAbility extends UIAbility {
promise.then(() => { promise.then(() => {
console.info("Removed the preferences successfully."); console.info("Removed the preferences successfully.");
}).catch((err) => { }).catch((err) => {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}) })
} catch(err) { } catch(err) {
console.info("Failed to remove the preferences. code =" + err.code + ", message =" + err.message); console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
} }
} }
} }
...@@ -440,7 +739,9 @@ removePreferencesFromCacheSync(context: Context, name: string): void ...@@ -440,7 +739,9 @@ removePreferencesFromCacheSync(context: Context, name: string): void
Synchronously removes a **Preferences** instance from the cache. Synchronously removes a **Preferences** instance from the cache.
The deleted **Preferences** instance cannot be used to perform data operations. Otherwise, data inconsistency will be caused. After an application calls [getPreferences](#data_preferencesgetpreferences) for the first time to obtain a **Preferences** instance, the obtained **Preferences** instance is cached. When the application calls [getPreferences](#data_preferencesgetpreferences) again, the **Preferences** instance will be read from the cache instead of from the persistent file. After this API is called to remove the instance from the cache, calling **getPreferences** again will read data from the persistent file and create a new **Preferences** instance.
After the **Preferences** instance is removed, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the removed **Preferences** instance to null. The system will reclaim the removed **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core **System capability**: SystemCapability.DistributedDataManager.Preferences.Core
...@@ -459,7 +760,6 @@ FA model: ...@@ -459,7 +760,6 @@ FA model:
// Obtain the context. // Obtain the context.
import featureAbility from '@ohos.ability.featureAbility'; import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext(); let context = featureAbility.getContext();
try { try {
data_preferences.removePreferencesFromCacheSync(context, 'mystore'); data_preferences.removePreferencesFromCacheSync(context, 'mystore');
} catch(err) { } catch(err) {
...@@ -483,6 +783,164 @@ class EntryAbility extends UIAbility { ...@@ -483,6 +783,164 @@ class EntryAbility extends UIAbility {
} }
``` ```
## data_preferences.removePreferencesFromCache<sup>10+</sup>
removePreferencesFromCache(context: Context, options: Options, callback: AsyncCallback&lt;void&gt;): void
Removes a **Preferences** instance from the cache. This API uses an asynchronous callback to return the result.
After an application calls [getPreferences](#data_preferencesgetpreferences) for the first time to obtain a **Preferences** instance, the obtained **Preferences** instance is cached. When the application calls [getPreferences](#data_preferencesgetpreferences) again, the **Preferences** instance will be read from the cache instead of from the persistent file. After this API is called to remove the instance from the cache, calling **getPreferences** again will read data from the persistent file and create a new **Preferences** instance.
After the **Preferences** instance is removed, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the removed **Preferences** instance to null. The system will reclaim the removed **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| options | [Options](#options10) | Yes | Configuration options of the **Preferences** instance. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object. |
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | ------------------------------ |
| 15501001 | Only supported in stage mode. |
| 15501002 | The data group id is not valid. |
**Example**
FA model:
```js
// Obtain the context.
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
try {
data_preferences.removePreferencesFromCache(context, {name: 'mystore'}, function (err) {
if (err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
return;
}
console.info("Removed the preferences successfully.");
})
} catch (err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
data_preferences.removePreferencesFromCache(this.context, {name: 'mystore', dataGroupId:'myId'}, function (err) {
if (err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
return;
}
console.info("Removed the preferences successfully.");
})
} catch (err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}
}
}
```
## data_preferences.removePreferencesFromCache<sup>10+</sup>
removePreferencesFromCache(context: Context, options: Options): Promise&lt;void&gt;
Removes a **Preferences** instance from the cache. This API uses a promise to return the result.
After an application calls [getPreferences](#data_preferencesgetpreferences) for the first time to obtain a **Preferences** instance, the obtained **Preferences** instance is cached. When the application calls [getPreferences](#data_preferencesgetpreferences) again, the **Preferences** instance will be read from the cache instead of from the persistent file. After this API is called to remove the instance from the cache, calling **getPreferences** again will read data from the persistent file and create a new **Preferences** instance.
After the **Preferences** instance is removed, do not use it to perform data operations. Otherwise, data inconsistency may be caused. For this purpose, set the removed **Preferences** instance to null. The system will reclaim the removed **Preferences** instances in a unified manner.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ------- | ---------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| options | [Options](#options10) | Yes | Configuration options of the **Preferences** instance. |
**Return value**
| Type | Description |
| ------------------- | ------------------------- |
| Promise&lt;void&gt; | Promise that returns no value.|
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | ------------------------------ |
| 15501001 | Only supported in stage mode. |
| 15501002 | The data group id is not valid. |
**Example**
FA model:
```js
// Obtain the context.
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
try {
let promise = data_preferences.removePreferencesFromCache(context, {name: 'mystore'});
promise.then(() => {
console.info("Removed the preferences successfully.");
}).catch((err) => {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
})
} catch(err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
let promise = data_preferences.removePreferencesFromCache(this.context, {name: 'mystore', dataGroupId:'myId'});
promise.then(() => {
console.info("Removed the preferences successfully.");
}).catch((err) => {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
})
} catch(err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}
}
}
```
## Options<sup>10+</sup>
Represents the configuration options of a **Preferences** instance.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
| Name | Type | Mandatory| Description |
| ----------- | ------ | ---- | ------------------------------------------------------------ |
| name | string | Yes | Name of the **Preferences** instance. |
| dataGroupId | string | No | Application group ID, which needs to be obtained from the AppGallery.<br>**Model restriction**: This attribute can be used only in the stage model.<br>This parameter is supported since API version 10. It specifies the **Preferences** instance created in the sandbox directory corresponding to the **dataGroupId**. If this parameter is not specified, the **Preferences** instance is created in the sandbox directory of the application.|
## Preferences ## Preferences
Provides APIs for obtaining and modifying the stored data. Provides APIs for obtaining and modifying the stored data.
...@@ -504,7 +962,7 @@ Obtains the value corresponding to the specified key from the cached **Preferenc ...@@ -504,7 +962,7 @@ Obtains the value corresponding to the specified key from the cached **Preferenc
| -------- | -------------------------------------------- | ---- | ------------------------------------------------------------ | | -------- | -------------------------------------------- | ---- | ------------------------------------------------------------ |
| key | string | Yes | Key of the data to obtain. It cannot be empty. | | key | string | Yes | Key of the data to obtain. It cannot be empty. |
| defValue | [ValueType](#valuetype) | Yes | Default value to be returned. The value can be a number, a string, a Boolean value, or an array of numbers, strings, or Boolean values.| | defValue | [ValueType](#valuetype) | Yes | Default value to be returned. The value can be a number, a string, a Boolean value, or an array of numbers, strings, or Boolean values.|
| callback | AsyncCallback&lt;[ValueType](#valuetype)&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is** undefined** and **data** is the value obtained. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;[ValueType](#valuetype)&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined** and **data** is the value obtained. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -512,13 +970,13 @@ Obtains the value corresponding to the specified key from the cached **Preferenc ...@@ -512,13 +970,13 @@ Obtains the value corresponding to the specified key from the cached **Preferenc
try { try {
preferences.get('startup', 'default', function (err, val) { preferences.get('startup', 'default', function (err, val) {
if (err) { if (err) {
console.info("Failed to obtain the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain value of 'startup'. code =" + err.code + ", message =" + err.message);
return; return;
} }
console.info("Obtained the value of 'startup' successfully. val: " + val); console.info("Obtained the value of 'startup' successfully. val: " + val);
}) })
} catch (err) { } catch (err) {
console.info("Failed to obtain the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain value of 'startup'. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -552,10 +1010,10 @@ try { ...@@ -552,10 +1010,10 @@ try {
promise.then((data) => { promise.then((data) => {
console.info("Got the value of 'startup'. Data: " + data); console.info("Got the value of 'startup'. Data: " + data);
}).catch((err) => { }).catch((err) => {
console.info("Failed to obtain the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain value of 'startup'. code =" + err.code + ", message =" + err.message);
}) })
} catch(err) { } catch(err) {
console.info("Failed to obtain the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain value of 'startup'. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -587,7 +1045,7 @@ try { ...@@ -587,7 +1045,7 @@ try {
let value = preferences.getSync('startup', 'default'); let value = preferences.getSync('startup', 'default');
console.info("Obtained the value of 'startup'. Data: " + value); console.info("Obtained the value of 'startup'. Data: " + value);
} catch(err) { } catch(err) {
console.info("Failed to obtain the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain value of 'startup'. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -603,7 +1061,7 @@ Obtains all KV pairs from the cached **Preferences** instance. This API uses an ...@@ -603,7 +1061,7 @@ Obtains all KV pairs from the cached **Preferences** instance. This API uses an
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | --------------------------- | ---- | ------------------------------------------------------------ | | -------- | --------------------------- | ---- | ------------------------------------------------------------ |
| callback | AsyncCallback&lt;Object&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined** and **value** provides all KV pairs obtained. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;Object&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined** and **value** provides all KV pairs obtained. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -611,7 +1069,7 @@ Obtains all KV pairs from the cached **Preferences** instance. This API uses an ...@@ -611,7 +1069,7 @@ Obtains all KV pairs from the cached **Preferences** instance. This API uses an
try { try {
preferences.getAll(function (err, value) { preferences.getAll(function (err, value) {
if (err) { if (err) {
console.info("Failed to get all KV pairs. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain all KV pairs. code =" + err.code + ", message =" + err.message);
return; return;
} }
let allKeys = Object.keys(value); let allKeys = Object.keys(value);
...@@ -619,7 +1077,7 @@ try { ...@@ -619,7 +1077,7 @@ try {
console.info("getAll object = " + JSON.stringify(value)); console.info("getAll object = " + JSON.stringify(value));
}) })
} catch (err) { } catch (err) {
console.info("Failed to get all KV pairs. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain all KV pairs. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -648,10 +1106,10 @@ try { ...@@ -648,10 +1106,10 @@ try {
console.info('getAll keys = ' + allKeys); console.info('getAll keys = ' + allKeys);
console.info("getAll object = " + JSON.stringify(value)); console.info("getAll object = " + JSON.stringify(value));
}).catch((err) => { }).catch((err) => {
console.info("Failed to get all KV pairs. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain all KV pairs. code =" + err.code + ", message =" + err.message);
}) })
} catch (err) { } catch (err) {
console.info("Failed to get all KV pairs. code =" + err.code + ", message =" + err.message); console.info("Failed to obtain all KV pairs. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -659,7 +1117,7 @@ try { ...@@ -659,7 +1117,7 @@ try {
getAllSync(): Object getAllSync(): Object
Synchronously obtains all KV pairs from the cached **Preferences** instance. Obtains all KV pairs from the cached **Preferences** instance synchronously.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core **System capability**: SystemCapability.DistributedDataManager.Preferences.Core
...@@ -696,7 +1154,7 @@ Writes data to the cached **Preferences** instance. This API uses an asynchronou ...@@ -696,7 +1154,7 @@ Writes data to the cached **Preferences** instance. This API uses an asynchronou
| -------- | ------------------------- | ---- | ------------------------------------------------------------ | | -------- | ------------------------- | ---- | ------------------------------------------------------------ |
| key | string | Yes | Key of the data. It cannot be empty. | | key | string | Yes | Key of the data. It cannot be empty. |
| value | [ValueType](#valuetype) | Yes | Value to write. The value can be a number, a string, a Boolean value, or an array of numbers, strings, or Boolean values.| | value | [ValueType](#valuetype) | Yes | Value to write. The value can be a number, a string, a Boolean value, or an array of numbers, strings, or Boolean values.|
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is undefined. Otherwise, **err** is an error code. | | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If data is written successfully, **err** is **undefined**. Otherwise, **err** is an error object. |
**Example** **Example**
...@@ -707,7 +1165,7 @@ try { ...@@ -707,7 +1165,7 @@ try {
console.info("Failed to put the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to put the value of 'startup'. code =" + err.code + ", message =" + err.message);
return; return;
} }
console.info("Put the value of 'startup' successfully."); console.info("Successfully put the value of 'startup'.");
}) })
} catch (err) { } catch (err) {
console.info("Failed to put the value of 'startup'. code =" + err.code + ", message =" + err.message); console.info("Failed to put the value of 'startup'. code =" + err.code + ", message =" + err.message);
...@@ -742,7 +1200,7 @@ Writes data to the cached **Preferences** instance. This API uses a promise to r ...@@ -742,7 +1200,7 @@ Writes data to the cached **Preferences** instance. This API uses a promise to r
try { try {
let promise = preferences.put('startup', 'auto'); let promise = preferences.put('startup', 'auto');
promise.then(() => { promise.then(() => {
console.info("Put the value of 'startup' successfully."); console.info("Successfully put the value of 'startup'.");
}).catch((err) => { }).catch((err) => {
console.info("Failed to put the value of 'startup'. code =" + err.code +", message =" + err.message); console.info("Failed to put the value of 'startup'. code =" + err.code +", message =" + err.message);
}) })
...@@ -773,7 +1231,7 @@ Synchronously writes data to the cached **Preferences** instance. You can use [f ...@@ -773,7 +1231,7 @@ Synchronously writes data to the cached **Preferences** instance. You can use [f
try { try {
preferences.putSync('startup', 'auto'); preferences.putSync('startup', 'auto');
} catch(err) { } catch(err) {
console.info("Failed to put value of 'startup'. code =" + err.code +", message =" + err.message); console.info("Failed to put the value of 'startup'. code =" + err.code +", message =" + err.message);
} }
``` ```
...@@ -903,7 +1361,7 @@ Deletes a KV pair from the cached **Preferences** instance based on the specifie ...@@ -903,7 +1361,7 @@ Deletes a KV pair from the cached **Preferences** instance based on the specifie
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------- | ---- | ---------------------------------------------------- | | -------- | ------------------------- | ---- | ---------------------------------------------------- |
| key | string | Yes | Key of the KV pair to delete. It cannot be empty. | | key | string | Yes | Key of the KV pair to delete. It cannot be empty. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -995,7 +1453,7 @@ Flushes the data in the cached **Preferences** instance to the persistent file. ...@@ -995,7 +1453,7 @@ Flushes the data in the cached **Preferences** instance to the persistent file.
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------- | ---- | ---------------------------------------------------- | | -------- | ------------------------- | ---- | ---------------------------------------------------- |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -1006,7 +1464,7 @@ try { ...@@ -1006,7 +1464,7 @@ try {
console.info("Failed to flush data. code =" + err.code + ", message =" + err.message); console.info("Failed to flush data. code =" + err.code + ", message =" + err.message);
return; return;
} }
console.info("Flushed data successfully."); console.info("Successfully flushed data.");
}) })
} catch (err) { } catch (err) {
console.info("Failed to flush data. code =" + err.code + ", message =" + err.message); console.info("Failed to flush data. code =" + err.code + ", message =" + err.message);
...@@ -1034,7 +1492,7 @@ Flushes the data in the cached **Preferences** instance to the persistent file. ...@@ -1034,7 +1492,7 @@ Flushes the data in the cached **Preferences** instance to the persistent file.
try { try {
let promise = preferences.flush(); let promise = preferences.flush();
promise.then(() => { promise.then(() => {
console.info("Flushed data successfully."); console.info("Successfully flushed data.");
}).catch((err) => { }).catch((err) => {
console.info("Failed to flush data. code =" + err.code + ", message =" + err.message); console.info("Failed to flush data. code =" + err.code + ", message =" + err.message);
}) })
...@@ -1056,7 +1514,7 @@ Clears all data in the cached **Preferences** instance. This API uses an asynchr ...@@ -1056,7 +1514,7 @@ Clears all data in the cached **Preferences** instance. This API uses an asynchr
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------- | ---- | ---------------------------------------------------- | | -------- | ------------------------- | ---- | ---------------------------------------------------- |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error code.| | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. If the operation is successful, **err** is **undefined**. Otherwise, **err** is an error object.|
**Example** **Example**
...@@ -1067,7 +1525,7 @@ try { ...@@ -1067,7 +1525,7 @@ try {
console.info("Failed to clear data. code =" + err.code + ", message =" + err.message); console.info("Failed to clear data. code =" + err.code + ", message =" + err.message);
return; return;
} }
console.info("Cleared data successfully."); console.info("Successfully cleared data.");
}) })
} catch (err) { } catch (err) {
console.info("Failed to clear data. code =" + err.code + ", message =" + err.message); console.info("Failed to clear data. code =" + err.code + ", message =" + err.message);
...@@ -1095,7 +1553,7 @@ Clears all data in the cached **Preferences** instance. This API uses a promise ...@@ -1095,7 +1553,7 @@ Clears all data in the cached **Preferences** instance. This API uses a promise
try { try {
let promise = preferences.clear(); let promise = preferences.clear();
promise.then(() => { promise.then(() => {
console.info("Cleared data successfully."); console.info("Successfully cleared data.");
}).catch((err) => { }).catch((err) => {
console.info("Failed to clear data. code =" + err.code + ", message =" + err.message); console.info("Failed to clear data. code =" + err.code + ", message =" + err.message);
}) })
...@@ -1119,7 +1577,7 @@ Synchronously clears all data in the cached **Preferences** instance. You can us ...@@ -1119,7 +1577,7 @@ Synchronously clears all data in the cached **Preferences** instance. You can us
try { try {
preferences.clearSync(); preferences.clearSync();
} catch(err) { } catch(err) {
console.info("Failed to clear. code =" + err.code + ", message =" + err.message); console.info("Failed to clear data. code =" + err.code + ", message =" + err.message);
} }
``` ```
...@@ -1157,14 +1615,14 @@ try { ...@@ -1157,14 +1615,14 @@ try {
console.info("Failed to put the value of 'startup'. Cause: " + err); console.info("Failed to put the value of 'startup'. Cause: " + err);
return; return;
} }
console.info("Put the value of 'startup' successfully."); console.info("Successfully put the value of 'startup'.");
preferences.flush(function (err) { preferences.flush(function (err) {
if (err) { if (err) {
console.info("Failed to flush data. Cause: " + err); console.info("Failed to flush data. Cause: " + err);
return; return;
} }
console.info("Flushed data successfully."); console.info("Successfully flushed data.");
}) })
}) })
}) })
...@@ -1173,6 +1631,125 @@ try { ...@@ -1173,6 +1631,125 @@ try {
} }
``` ```
### on('multiProcessChange')<sup>10+</sup>
on(type: 'multiProcessChange', callback: Callback&lt;{ key : string }&gt;): void
Subscribes to inter-process data changes. For the multiple processes holding the same preference file, if the value of the subscribed key changes in any process, the callback in this API will be invoked after [flush()](#flush) is executed.
This API can be used with [removePreferencesFromCache](#data_preferencesremovepreferencesfromcache) to update the **Preferences** instance in the callback when detecting that a process updates a file. For details, see example 2.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | -------------------------------- | ---- | -------------------------------------------------------------- |
| type | string | Yes | Event type. The value is **multiProcessChange**, which indicates data changes between multiple processes.|
| callback | Callback&lt;{ key : string }&gt; | Yes | Callback invoked to return data changes. |
**Error codes**
For details about the error codes, see [User Preference Error Codes](../errorcodes/errorcode-preferences.md).
| ID| Error Message |
| -------- | -------------------------------------- |
| 15500019 | Failed to obtain subscription service. |
**Example 1**
```js
try {
data_preferences.getPreferences(this.context, {name: 'mystore', dataGroupId:'myId'}, function (err, preferences) {
if (err) {
console.info("Failed to obtain the preferences.");
return;
}
let observer = function (key) {
console.info("The key " + key + " changed.");
}
preferences.on('multiProcessChange', observer);
preferences.put('startup', 'manual', function (err) {
if (err) {
console.info("Failed to put the value of 'startup'. Cause: " + err);
return;
}
console.info("Successfully put the value of 'startup'.");
preferences.flush(function (err) {
if (err) {
console.info("Failed to flush data. Cause: " + err);
return;
}
console.info("Successfully flushed data.");
})
})
})
} catch (err) {
console.info("Failed to flush data. code =" + err.code + ", message =" + err.message);
}
```
**Example 2**
```js
let preferences = null;
try {
data_preferences.getPreferences(this.context, { name: 'mystore' }, function (err, val) {
if (err) {
console.info("Failed to obtain the preferences.");
return;
}
preferences = val;
let observer = function (key) {
console.info("The key " + key + " changed.");
try {
data_preferences.removePreferencesFromCache(context, { name: 'mystore' }, function (err) {
if (err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
return;
}
preferences = null;
console.info("Removed the preferences successfully.");
})
} catch (err) {
console.info("Failed to remove preferences. code =" + err.code + ", message =" + err.message);
}
try {
data_preferences.getPreferences(context, { name: 'mystore' }, function (err, val) {
if (err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
return;
}
preferences = val;
console.info("Obtained the preferences successfully.");
})
} catch (err) {
console.info("Failed to obtain the preferences. code =" + err.code + ", message =" + err.message);
}
}
preferences.on('multiProcessChange', observer);
preferences.put('startup', 'manual', function (err) {
if (err) {
console.info("Failed to put the value of 'startup'. Cause: " + err);
return;
}
console.info("Successfully put the value of 'startup'.");
preferences.flush(function (err) {
if (err) {
console.info("Failed to flush data. Cause: " + err);
return;
}
console.info("Successfully flushed data.");
})
})
})
} catch (err) {
console.info("Failed to flush data. code =" + err.code + ", message =" + err.message);
}
```
### off('change') ### off('change')
...@@ -1186,7 +1763,7 @@ Unsubscribes from data changes. ...@@ -1186,7 +1763,7 @@ Unsubscribes from data changes.
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | -------------------------------- | ---- | ------------------------------------------ | | -------- | -------------------------------- | ---- | ------------------------------------------ |
| type | string | Yes | Event type to unsubscribe from. The value **change** indicates data change events. | | type | string | Yes | Event type to unsubscribe from. The value **change** indicates data change events. |
| callback | Callback&lt;{ key : string }&gt; | No | Callback to unregister. If this parameter is left blank, the callbacks for all data changes will be unregistered.| | callback | Callback&lt;{ key : string }&gt; | No | Callback to unregister. If this parameter is left blank, the callbacks for all data changes will be unregistered.|
**Example** **Example**
...@@ -1207,14 +1784,14 @@ try { ...@@ -1207,14 +1784,14 @@ try {
console.info("Failed to put the value of 'startup'. Cause: " + err); console.info("Failed to put the value of 'startup'. Cause: " + err);
return; return;
} }
console.info("Put the value of 'startup' successfully."); console.info("Successfully put the value of 'startup'.");
preferences.flush(function (err) { preferences.flush(function (err) {
if (err) { if (err) {
console.info("Failed to flush data. Cause: " + err); console.info("Failed to flush data. Cause: " + err);
return; return;
} }
console.info("Flushed data successfully."); console.info("Successfully flushed data.");
}) })
preferences.off('change', observer); preferences.off('change', observer);
}) })
...@@ -1224,6 +1801,55 @@ try { ...@@ -1224,6 +1801,55 @@ try {
} }
``` ```
### off('multiProcessChange')<sup>10+</sup>
off(type: 'multiProcessChange', callback?: Callback&lt;{ key : string }&gt;): void
Unsubscribes from inter-process data changes.
**System capability**: SystemCapability.DistributedDataManager.Preferences.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | -------------------------------- | ---- | -------------------------------------------------------------- |
| type | string | Yes | Event type. The value is **multiProcessChange**, which indicates data changes between multiple processes.|
| callback | Callback&lt;{ key : string }&gt; | No | Callback to unregister. If this parameter is left blank, all callbacks for **multiProcessChange** will be unregistered. |
**Example**
```js
try {
data_preferences.getPreferences(this.context, {name: 'mystore', dataGroupId:'myId'}, function (err, preferences) {
if (err) {
console.info("Failed to obtain the preferences.");
return;
}
let observer = function (key) {
console.info("The key " + key + " changed.");
}
preferences.on('multiProcessChange', observer);
preferences.put('startup', 'auto', function (err) {
if (err) {
console.info("Failed to put the value of 'startup'. Cause: " + err);
return;
}
console.info("Successfully put the value of 'startup'.");
preferences.flush(function (err) {
if (err) {
console.info("Failed to flush data. Cause: " + err);
return;
}
console.info("Successfully flushed data.");
})
preferences.off('multiProcessChange', observer);
})
})
} catch (err) {
console.info("Failed to flush data. code =" + err.code + ", message =" + err.message);
}
```
## ValueType ## ValueType
Enumerates the value types. Enumerates the value types.
...@@ -1237,4 +1863,4 @@ Enumerates the value types. ...@@ -1237,4 +1863,4 @@ Enumerates the value types.
| boolean | The value is of Boolean type. | | boolean | The value is of Boolean type. |
| Array\<number> | The value is an array of numbers. | | Array\<number> | The value is an array of numbers. |
| Array\<boolean> | The value is a Boolean array. | | Array\<boolean> | The value is a Boolean array. |
| Array\<string> | The value is an array of the strings.| | Array\<string> | The value is an array of strings.|
# @ohos.data.relationalStore (RDB Store) # @ohos.data.relationalStore (RDB Store)
The relational database (RDB) store manages data based on relational models. The RDB store provides a complete mechanism for managing local databases based on the underlying SQLite. To satisfy different needs in complicated scenarios, the RDB store offers a series of APIs for performing operations such as adding, deleting, modifying, and querying data, and supports direct execution of SQL statements. The worker threads are not supported. The relational database (RDB) store manages data based on relational models. It provides a complete mechanism for managing local databases based on the underlying SQLite. To satisfy different needs in complicated scenarios, the RDB store offers a series of APIs for performing operations such as adding, deleting, modifying, and querying data, and supports direct execution of SQL statements. The worker threads are not supported.
The **relationalStore** module provides the following functions: The **relationalStore** module provides the following functions:
...@@ -43,6 +43,8 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode ...@@ -43,6 +43,8 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode
| 14800010 | Failed to open or delete database by invalid database path. | | 14800010 | Failed to open or delete database by invalid database path. |
| 14800011 | Failed to open database by database corrupted. | | 14800011 | Failed to open database by database corrupted. |
| 14800000 | Inner error. | | 14800000 | Inner error. |
| 14801001 | Only supported in stage mode. |
| 14801002 | The data group id is not valid. |
**Example** **Example**
...@@ -127,6 +129,8 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode ...@@ -127,6 +129,8 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode
| 14800010 | Failed to open or delete database by invalid database path. | | 14800010 | Failed to open or delete database by invalid database path. |
| 14800011 | Failed to open database by database corrupted. | | 14800011 | Failed to open database by database corrupted. |
| 14800000 | Inner error. | | 14800000 | Inner error. |
| 14801001 | Only supported in stage mode. |
| 14801002 | The data group id is not valid. |
**Example** **Example**
...@@ -184,6 +188,8 @@ deleteRdbStore(context: Context, name: string, callback: AsyncCallback&lt;void&g ...@@ -184,6 +188,8 @@ deleteRdbStore(context: Context, name: string, callback: AsyncCallback&lt;void&g
Deletes an RDB store. This API uses an asynchronous callback to return the result. Deletes an RDB store. This API uses an asynchronous callback to return the result.
After the deletion, you are advised to set the database object to null.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters** **Parameters**
...@@ -218,6 +224,7 @@ relationalStore.deleteRdbStore(context, "RdbTest.db", function (err) { ...@@ -218,6 +224,7 @@ relationalStore.deleteRdbStore(context, "RdbTest.db", function (err) {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`); console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
return; return;
} }
store = null;
console.info(`Delete RdbStore successfully.`); console.info(`Delete RdbStore successfully.`);
}) })
``` ```
...@@ -234,6 +241,7 @@ class EntryAbility extends UIAbility { ...@@ -234,6 +241,7 @@ class EntryAbility extends UIAbility {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`); console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
return; return;
} }
store = null;
console.info(`Delete RdbStore successfully.`); console.info(`Delete RdbStore successfully.`);
}) })
} }
...@@ -246,6 +254,8 @@ deleteRdbStore(context: Context, name: string): Promise&lt;void&gt; ...@@ -246,6 +254,8 @@ deleteRdbStore(context: Context, name: string): Promise&lt;void&gt;
Deletes an RDB store. This API uses a promise to return the result. Deletes an RDB store. This API uses a promise to return the result.
After the deletion, you are advised to set the database object to null.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters** **Parameters**
...@@ -282,6 +292,7 @@ let context = featureAbility.getContext(); ...@@ -282,6 +292,7 @@ let context = featureAbility.getContext();
let promise = relationalStore.deleteRdbStore(context, "RdbTest.db"); let promise = relationalStore.deleteRdbStore(context, "RdbTest.db");
promise.then(()=>{ promise.then(()=>{
store = null;
console.info(`Delete RdbStore successfully.`); console.info(`Delete RdbStore successfully.`);
}).catch((err) => { }).catch((err) => {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`); console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
...@@ -297,6 +308,162 @@ class EntryAbility extends UIAbility { ...@@ -297,6 +308,162 @@ class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage){ onWindowStageCreate(windowStage){
let promise = relationalStore.deleteRdbStore(this.context, "RdbTest.db"); let promise = relationalStore.deleteRdbStore(this.context, "RdbTest.db");
promise.then(()=>{ promise.then(()=>{
store = null;
console.info(`Delete RdbStore successfully.`);
}).catch((err) => {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
})
}
}
```
## relationalStore.deleteRdbStore<sup>10+</sup>
deleteRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback\<void>): void
Deletes an RDB store. This API uses an asynchronous callback to return the result.
After the deletion, you are advised to set the database object to null. If the database file is in the public sandbox directory, you must use this API to delete the database. If the database is accessed by multiple processes at the same time, you are advised to send a database deletion notification to other processes.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | --------------------------- | ---- | ------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| config | [StoreConfig](#storeconfig) | Yes | Configuration of the RDB store. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. |
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | ----------------------------------------------------------- |
| 14800010 | Failed to open or delete database by invalid database path. |
| 14800000 | Inner error. |
| 14801001 | Only supported in stage mode. |
| 14801002 | The data group id is not valid. |
**Example**
FA model:
```js
import featureAbility from '@ohos.ability.featureAbility'
// Obtain the context.
let context = featureAbility.getContext()
const STORE_CONFIG = {
name: "RdbTest.db",
securityLevel: relationalStore.SecurityLevel.S1
};
relationalStore.deleteRdbStore(context, STORE_CONFIG, function (err) {
if (err) {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
return;
}
store = null;
console.info(`Delete RdbStore successfully.`);
})
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility'
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage){
const STORE_CONFIG = {
name: "RdbTest.db",
securityLevel: relationalStore.SecurityLevel.S1
};
relationalStore.deleteRdbStore(this.context, STORE_CONFIG, function (err) {
if (err) {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
return;
}
store = null;
console.info(`Delete RdbStore successfully.`);
})
}
}
```
## relationalStore.deleteRdbStore<sup>10+</sup>
deleteRdbStore(context: Context, config: StoreConfig): Promise\<void>
Deletes an RDB store. This API uses a promise to return the result.
After the deletion, you are advised to set the database object to null. If the database file is in the public sandbox directory, you must use this API to delete the database. If the database is accessed by multiple processes at the same time, you are advised to send a database deletion notification to other processes.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ------- | --------------------------- | ---- | ------------------------------------------------------------ |
| context | Context | Yes | Application context.<br>For details about the application context of the FA model, see [Context](js-apis-inner-app-context.md).<br>For details about the application context of the stage model, see [Context](js-apis-inner-application-uiAbilityContext.md).|
| config | [StoreConfig](#storeconfig) | Yes | Configuration of the RDB store. |
**Return value**
| Type | Description |
| ------------------- | ------------------------- |
| Promise&lt;void&gt; | Promise that returns no value.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | ----------------------------------------------------------- |
| 14800010 | Failed to open or delete database by invalid database path. |
| 14800000 | Inner error. |
| 14801001 | Only supported in stage mode. |
| 14801002 | The data group id is not valid. |
**Example**
FA model:
```js
import featureAbility from '@ohos.ability.featureAbility'
// Obtain the context.
let context = featureAbility.getContext();
const STORE_CONFIG = {
name: "RdbTest.db",
securityLevel: relationalStore.SecurityLevel.S1
};
let promise = relationalStore.deleteRdbStore(context, STORE_CONFIG);
promise.then(()=>{
store = null;
console.info(`Delete RdbStore successfully.`);
}).catch((err) => {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
})
```
Stage model:
```ts
import UIAbility from '@ohos.app.ability.UIAbility'
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage){
const STORE_CONFIG = {
name: "RdbTest.db",
securityLevel: relationalStore.SecurityLevel.S1
};
let promise = relationalStore.deleteRdbStore(this.context, STORE_CONFIG);
promise.then(()=>{
store = null;
console.info(`Delete RdbStore successfully.`); console.info(`Delete RdbStore successfully.`);
}).catch((err) => { }).catch((err) => {
console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`); console.error(`Delete RdbStore failed, code is ${err.code},message is ${err.message}`);
...@@ -315,7 +482,8 @@ Defines the RDB store configuration. ...@@ -315,7 +482,8 @@ Defines the RDB store configuration.
| ------------- | ------------- | ---- | --------------------------------------------------------- | | ------------- | ------------- | ---- | --------------------------------------------------------- |
| name | string | Yes | Database file name. | | name | string | Yes | Database file name. |
| securityLevel | [SecurityLevel](#securitylevel) | Yes | Security level of the RDB store. | | securityLevel | [SecurityLevel](#securitylevel) | Yes | Security level of the RDB store. |
| encrypt | boolean | No | Whether to encrypt the RDB store.<br> The value **true** means to encrypt the RDB store;<br> the value **false** (default) means the opposite.| | encrypt | boolean | No | Whether to encrypt the RDB store.<br>The value **true** means to encrypt the RDB store;<br>the value **false** (default) means the opposite.|
| dataGroupId<sup>10+</sup> | string | No| Application group ID, which needs to be obtained from the AppGallery.<br>**Model restriction**: This attribute can be used only in the stage model.<br>This parameter is supported since API version 10. It specifies the **relationalStore** instance created in the sandbox directory corresponding to the **dataGroupId**. If this parameter is not specified, the **relationalStore** instance is created in the sandbox directory of the application.|
## SecurityLevel ## SecurityLevel
...@@ -323,7 +491,7 @@ Enumerates the RDB store security levels. ...@@ -323,7 +491,7 @@ Enumerates the RDB store security levels.
> **NOTE** > **NOTE**
> >
> To perform data synchronization operations, the RDB store security level must be lower than or equal to that of the peer device. For details, see the [Access Control Mechanism in Cross-Device Synchronization]( ../../database/access-control-by-device-and-data-level.md#access-control-mechanism-in-cross-device-synchronization). > To perform data synchronization operations, the RDB store security level must be lower than or equal to that of the peer device. For details, see the [Access Control Mechanism in Cross-Device Synchronization](../../database/access-control-by-device-and-data-level.md#access-control-mechanism-in-cross-device-synchronization).
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
...@@ -369,6 +537,8 @@ Defines information about an asset (such as a document, image, and video). The a ...@@ -369,6 +537,8 @@ Defines information about an asset (such as a document, image, and video). The a
Defines an array of the [Asset](#asset10) type. Defines an array of the [Asset](#asset10) type.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Type | Description | | Type | Description |
| ------- | -------------------- | | ------- | -------------------- |
| [Asset](#asset10)[] | Array of assets. | | [Asset](#asset10)[] | Array of assets. |
...@@ -399,6 +569,36 @@ Defines the types of the key and value in a KV pair. This type is not multi-thre ...@@ -399,6 +569,36 @@ Defines the types of the key and value in a KV pair. This type is not multi-thre
| ------ | ----------------------- | | ------ | ----------------------- |
| string | [ValueType](#valuetype) | | string | [ValueType](#valuetype) |
## PRIKeyType<sup>10+</sup>
Represents the type of the primary key in a row of a database table.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Type | Description |
| ---------------- | ---------------------------------- |
| number \| string | The type of the primary key can be number or string.|
## UTCTime<sup>10+</sup>
Represents the data type of the UTC time.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Type| Description |
| ---- | --------------- |
| Date | UTC time.|
## ModifyTime<sup>10+</sup>
Represents the data type of the primary key and modification time of a database table.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Type | Description |
| ------------------------------------------------------- | ------------------------------------------------------------ |
| Map<[PRIKeyType](#prikeytype10), [UTCTime](#utctime10)> | The key is the primary key of a row in the database table, and the value is the last modification time of the row in UTC format.|
## SyncMode ## SyncMode
Enumerates the database synchronization modes. Enumerates the database synchronization modes.
...@@ -407,9 +607,9 @@ Enumerates the database synchronization modes. ...@@ -407,9 +607,9 @@ Enumerates the database synchronization modes.
| -------------- | ---- | ---------------------------------- | | -------------- | ---- | ---------------------------------- |
| SYNC_MODE_PUSH | 0 | Push data from a local device to a remote device.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | SYNC_MODE_PUSH | 0 | Push data from a local device to a remote device.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core |
| SYNC_MODE_PULL | 1 | Pull data from a remote device to a local device.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | SYNC_MODE_PULL | 1 | Pull data from a remote device to a local device.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core |
| SYNC_MODE_TIME_FIRST<sup>10+</sup> | - | Synchronize with the data with the latest modification time. Use the enum names instead of the enum values. Currently, manual synchronization between the device and cloud is not supported.<br>**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client| | SYNC_MODE_TIME_FIRST<sup>10+</sup> | - | Synchronize with the data with the latest modification time. Use the enum names instead of the enum values.<br>**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client|
| SYNC_MODE_NATIVE_FIRST<sup>10+</sup> | - | Synchronize data from a local device to the cloud. Use the enum names instead of the enum values. Currently, manual synchronization between the device and cloud is not supported.<br>**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client | | SYNC_MODE_NATIVE_FIRST<sup>10+</sup> | - | Synchronize data from a local device to the cloud. Use the enum names instead of the enum values.<br>**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client |
| SYNC_MODE_CLOUD_FIRST<sup>10+</sup> | - | Synchronize data from the cloud to a local device. Use the enum names instead of the enum values. Currently, manual synchronization between the device and cloud is not supported.<br>**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client | | SYNC_MODE_CLOUD_FIRST<sup>10+</sup> | - | Synchronize data from the cloud to a local device. Use the enum names instead of the enum values.<br>**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client |
## SubscribeType ## SubscribeType
...@@ -440,15 +640,15 @@ Enumerates data change types. Use the enum names instead of the enum values. ...@@ -440,15 +640,15 @@ Enumerates data change types. Use the enum names instead of the enum values.
Defines the details about the device-cloud synchronization process. Defines the details about the device-cloud synchronization process.
**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Name | Type | Mandatory | Description | | Name | Type | Mandatory| Description |
| -------- | ---------------------------------- | --- | -------------------------------------------------------------------------------------------------------------------- | | -------- | ---------------------------------- | ---- | ------------------------------------------------------------ |
| table | string | Yes | Name of the table with data changes.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | table | string | Yes | Name of the table with data changes. |
| type | [ChangeType](#changetype10) | Yes | Type of the data changed, which can be data or asset.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | type | [ChangeType](#changetype10) | Yes | Type of the data changed, which can be data or asset. |
| inserted | Array\<string\> \| Array\<number\> | Yes | Location where data is inserted. If the primary key of the table is of the string type, the value is the value of the primary key. Otherwise, the value is the row number of the inserted data.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | inserted | Array\<string\> \| Array\<number\> | Yes | Location where data is inserted. If the primary key of the table is of the string type, the value is the value of the primary key. Otherwise, the value is the row number of the inserted data.|
| updated | Array\<string\> \| Array\<number\> | Yes | Location where data is updated. If the primary key of the table is of the string type, the value is the value of the primary key. Otherwise, the value is the row number of the updated data.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | updated | Array\<string\> \| Array\<number\> | Yes | Location where data is updated. If the primary key of the table is of the string type, the value is the value of the primary key. Otherwise, the value is the row number of the updated data.|
| deleted | Array\<string\> \| Array\<number\> | Yes | Location where data is deleted. If the primary key of the table is of the string type, the value is the value of the primary key. Otherwise, the value is the row number of the deleted data.<br>**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core | | deleted | Array\<string\> \| Array\<number\> | Yes | Location where data is deleted. If the primary key of the table is of the string type, the value is the value of the primary key. Otherwise, the value is the row number of the deleted data.|
## DistributedType<sup>10+</sup> ## DistributedType<sup>10+</sup>
...@@ -486,6 +686,70 @@ Defines the resolution to use when **insert()** and **update()** conflict. ...@@ -486,6 +686,70 @@ Defines the resolution to use when **insert()** and **update()** conflict.
| ON_CONFLICT_IGNORE | 4 | Skip the rows that contain constraint violations and continue to process the subsequent rows of the SQL statement.| | ON_CONFLICT_IGNORE | 4 | Skip the rows that contain constraint violations and continue to process the subsequent rows of the SQL statement.|
| ON_CONFLICT_REPLACE | 5 | Delete pre-existing rows that cause the constraint violation before inserting or updating the current row, and continue to execute the command normally.| | ON_CONFLICT_REPLACE | 5 | Delete pre-existing rows that cause the constraint violation before inserting or updating the current row, and continue to execute the command normally.|
## Progress<sup>10+</sup>
Enumerates the device-cloud synchronization processes. Use the enum names instead of the enum values.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Name | Value | Description |
| ---------------- | ---- | ------------------------ |
| SYNC_BEGIN | - | The device-cloud synchronization starts. |
| SYNC_IN_PROGRESS | - | The device-cloud synchronization is in progress.|
| SYNC_FINISH | - | The device-cloud synchronization is complete.|
## Statistic<sup>10+</sup>
Represents the device-cloud synchronization statistics information.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Name | Type | Mandatory| Description |
| -------- | ------ | ---- | ---------------------------------------- |
| total | number | Yes | Total number of rows to be synchronized between the device and cloud in the database table. |
| success | number | Yes | Number of rows that are successfully synchronized between the device and cloud in the database table. |
| failed | number | Yes | Number of rows that failed to be synchronized between the device and cloud in the database table. |
| remained | number | Yes | Number of rows that are not executed for device-cloud synchronization in the database table.|
## TableDetails<sup>10+</sup>
Represents the upload and download statistics of device-cloud synchronization tasks.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Name | Type | Mandatory| Description |
| -------- | ------------------------- | ---- | ------------------------------------------ |
| upload | [Statistic](#statistic10) | Yes | Statistics of the device-cloud upload tasks.|
| download | [Statistic](#statistic10) | Yes | Statistics of the device-cloud download tasks.|
## ProgressCode<sup>10+</sup>
Enumerates the device-cloud synchronization states. Use the enum names instead of the enum values.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Name | Value | Description |
| --------------------- | ---- | ------------------------------------------------------------ |
| SUCCESS | - | The device-cloud synchronization is successful. |
| UNKNOWN_ERROR | - | An unknown error occurs during device-cloud synchronization. |
| NETWORK_ERROR | - | A network error occurs during device-cloud synchronization. |
| CLOUD_DISABLED | - | The cloud is unavailable. |
| LOCKED_BY_OTHERS | - | The device-cloud synchronization of another device is being performed.<br>Start device-cloud synchronization after checking that cloud resources are not occupied by other devices.|
| RECORD_LIMIT_EXCEEDED | - | The number of records or size of the data to be synchronized exceeds the maximum. The maximum value is configured on the cloud.|
| NO_SPACE_FOR_ASSET | - | The remaining cloud space is less than the size of the data to be synchronized. |
## ProgressDetails<sup>10+</sup>
Represents the statistics of the overall device-cloud synchronization (upload and download) tasks.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
| Name | Type | Mandatory| Description |
| -------- | ------------------------------------------------- | ---- | ------------------------------------------------------------ |
| schedule | [Progress](#progress10) | Yes | Device-cloud synchronization process. |
| code | [ProgressCode](#progresscode10) | Yes | Device-cloud synchronization state. |
| details | [table: string] : [TableDetails](#tabledetails10) | Yes | Statistics of each table.<br>The key indicates the table name, and the value indicates the device-cloud synchronization statistics of the table.|
## RdbPredicates ## RdbPredicates
Defines the predicates for an RDB store. This class determines whether the conditional expression for the RDB store is true or false. This type is not multi-thread safe. If an **RdbPredicates** instance is operated by multiple threads at the same time in an application, use a lock for the instance. Defines the predicates for an RDB store. This class determines whether the conditional expression for the RDB store is true or false. This type is not multi-thread safe. If an **RdbPredicates** instance is operated by multiple threads at the same time in an application, use a lock for the instance.
...@@ -2133,6 +2397,53 @@ promise.then((rows) => { ...@@ -2133,6 +2397,53 @@ promise.then((rows) => {
}) })
``` ```
### query<sup>10+</sup>
query(predicates: RdbPredicates, callback: AsyncCallback&lt;ResultSet&gt;):void
Queries data from the RDB store based on specified conditions. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ---------- | ------------------------------------------------------------ | ---- | ----------------------------------------------------------- |
| predicates | [RdbPredicates](#rdbpredicates) | Yes | Query conditions specified by the **RdbPredicates** object. |
| callback | AsyncCallback&lt;[ResultSet](#resultset)&gt; | Yes | Callback invoked to return the result. If the operation is successful, a **ResultSet** object will be returned.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | ---------------------------- |
| 14800000 | Inner error. |
**Example**
```js
let predicates = new relationalStore.RdbPredicates("EMPLOYEE");
predicates.equalTo("NAME", "Rose");
store.query(predicates, function (err, resultSet) {
if (err) {
console.error(`Query failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
// resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0.
while(resultSet.goToNextRow()) {
const id = resultSet.getLong(resultSet.getColumnIndex("ID"));
const name = resultSet.getString(resultSet.getColumnIndex("NAME"));
const age = resultSet.getLong(resultSet.getColumnIndex("AGE"));
const salary = resultSet.getDouble(resultSet.getColumnIndex("SALARY"));
console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}`);
}
// Release the dataset memory.
resultSet.close();
})
```
### query ### query
query(predicates: RdbPredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void query(predicates: RdbPredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void
...@@ -2233,9 +2544,9 @@ promise.then((resultSet) => { ...@@ -2233,9 +2544,9 @@ promise.then((resultSet) => {
}) })
``` ```
### query ### query<sup>10+</sup>
query(table: string, predicates: dataSharePredicates.DataSharePredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void query(table: string, predicates: dataSharePredicates.DataSharePredicates, callback: AsyncCallback&lt;ResultSet&gt;):void
Queries data from the RDB store based on specified conditions. This API uses an asynchronous callback to return the result. Queries data from the RDB store based on specified conditions. This API uses an asynchronous callback to return the result.
...@@ -2251,7 +2562,6 @@ Queries data from the RDB store based on specified conditions. This API uses an ...@@ -2251,7 +2562,6 @@ Queries data from the RDB store based on specified conditions. This API uses an
| ---------- | ------------------------------------------------------------ | ---- | ----------------------------------------------------------- | | ---------- | ------------------------------------------------------------ | ---- | ----------------------------------------------------------- |
| table | string | Yes | Name of the target table. | | table | string | Yes | Name of the target table. |
| predicates | [dataSharePredicates.DataSharePredicates](js-apis-data-dataSharePredicates.md#datasharepredicates) | Yes | Query conditions specified by the **DataSharePredicates** object. | | predicates | [dataSharePredicates.DataSharePredicates](js-apis-data-dataSharePredicates.md#datasharepredicates) | Yes | Query conditions specified by the **DataSharePredicates** object. |
| columns | Array&lt;string&gt; | Yes | Columns to query. If this parameter is not specified, the query applies to all columns. |
| callback | AsyncCallback&lt;[ResultSet](#resultset)&gt; | Yes | Callback invoked to return the result. If the operation is successful, a **ResultSet** object will be returned.| | callback | AsyncCallback&lt;[ResultSet](#resultset)&gt; | Yes | Callback invoked to return the result. If the operation is successful, a **ResultSet** object will be returned.|
**Error codes** **Error codes**
...@@ -2268,7 +2578,7 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode ...@@ -2268,7 +2578,7 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode
import dataSharePredicates from '@ohos.data.dataSharePredicates' import dataSharePredicates from '@ohos.data.dataSharePredicates'
let predicates = new dataSharePredicates.DataSharePredicates(); let predicates = new dataSharePredicates.DataSharePredicates();
predicates.equalTo("NAME", "Rose"); predicates.equalTo("NAME", "Rose");
store.query("EMPLOYEE", predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"], function (err, resultSet) { store.query("EMPLOYEE", predicates, function (err, resultSet) {
if (err) { if (err) {
console.error(`Query failed, code is ${err.code},message is ${err.message}`); console.error(`Query failed, code is ${err.code},message is ${err.message}`);
return; return;
...@@ -2289,9 +2599,9 @@ store.query("EMPLOYEE", predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"], fu ...@@ -2289,9 +2599,9 @@ store.query("EMPLOYEE", predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"], fu
### query ### query
query(table: string, predicates: dataSharePredicates.DataSharePredicates, columns?: Array&lt;string&gt;):Promise&lt;ResultSet&gt; query(table: string, predicates: dataSharePredicates.DataSharePredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void
Queries data from the RDB store based on specified conditions. This API uses a promise to return the result. Queries data from the RDB store based on specified conditions. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
...@@ -2301,17 +2611,12 @@ Queries data from the RDB store based on specified conditions. This API uses a p ...@@ -2301,17 +2611,12 @@ Queries data from the RDB store based on specified conditions. This API uses a p
**Parameters** **Parameters**
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| ---------- | ------------------------------------------------------------ | ---- | ------------------------------------------------ | | ---------- | ------------------------------------------------------------ | ---- | ----------------------------------------------------------- |
| table | string | Yes | Name of the target table. | | table | string | Yes | Name of the target table. |
| predicates | [dataSharePredicates.DataSharePredicates](js-apis-data-dataSharePredicates.md#datasharepredicates) | Yes | Query conditions specified by the **DataSharePredicates** object. | | predicates | [dataSharePredicates.DataSharePredicates](js-apis-data-dataSharePredicates.md#datasharepredicates) | Yes | Query conditions specified by the **DataSharePredicates** object. |
| columns | Array&lt;string&gt; | No | Columns to query. If this parameter is not specified, the query applies to all columns.| | columns | Array&lt;string&gt; | Yes | Columns to query. If this parameter is not specified, the query applies to all columns. |
| callback | AsyncCallback&lt;[ResultSet](#resultset)&gt; | Yes | Callback invoked to return the result. If the operation is successful, a **ResultSet** object will be returned.|
**Return value**
| Type | Description |
| ------------------------------------------------------- | -------------------------------------------------- |
| Promise&lt;[ResultSet](#resultset)&gt; | Promise used to return the result. If the operation is successful, a **ResultSet** object will be returned.|
**Error codes** **Error codes**
...@@ -2327,8 +2632,11 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode ...@@ -2327,8 +2632,11 @@ For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode
import dataSharePredicates from '@ohos.data.dataSharePredicates' import dataSharePredicates from '@ohos.data.dataSharePredicates'
let predicates = new dataSharePredicates.DataSharePredicates(); let predicates = new dataSharePredicates.DataSharePredicates();
predicates.equalTo("NAME", "Rose"); predicates.equalTo("NAME", "Rose");
let promise = store.query("EMPLOYEE", predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"]); store.query("EMPLOYEE", predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"], function (err, resultSet) {
promise.then((resultSet) => { if (err) {
console.error(`Query failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`); console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
// resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0. // resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0.
while(resultSet.goToNextRow()) { while(resultSet.goToNextRow()) {
...@@ -2340,12 +2648,68 @@ promise.then((resultSet) => { ...@@ -2340,12 +2648,68 @@ promise.then((resultSet) => {
} }
// Release the dataset memory. // Release the dataset memory.
resultSet.close(); resultSet.close();
}).catch((err) => {
console.error(`Query failed, code is ${err.code},message is ${err.message}`);
}) })
``` ```
### remoteQuery ### query
query(table: string, predicates: dataSharePredicates.DataSharePredicates, columns?: Array&lt;string&gt;):Promise&lt;ResultSet&gt;
Queries data from the RDB store based on specified conditions. This API uses a promise to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Model restriction**: This API can be used only in the stage model.
**System API**: This is a system API.
**Parameters**
| Name | Type | Mandatory| Description |
| ---------- | ------------------------------------------------------------ | ---- | ------------------------------------------------ |
| table | string | Yes | Name of the target table. |
| predicates | [dataSharePredicates.DataSharePredicates](js-apis-data-dataSharePredicates.md#datasharepredicates) | Yes | Query conditions specified by the **DataSharePredicates** object. |
| columns | Array&lt;string&gt; | No | Columns to query. If this parameter is not specified, the query applies to all columns.|
**Return value**
| Type | Description |
| ------------------------------------------------------- | -------------------------------------------------- |
| Promise&lt;[ResultSet](#resultset)&gt; | Promise used to return the result. If the operation is successful, a **ResultSet** object will be returned.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | ---------------------------- |
| 14800000 | Inner error. |
**Example**
```js
import dataSharePredicates from '@ohos.data.dataSharePredicates'
let predicates = new dataSharePredicates.DataSharePredicates();
predicates.equalTo("NAME", "Rose");
let promise = store.query("EMPLOYEE", predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"]);
promise.then((resultSet) => {
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
// resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0.
while(resultSet.goToNextRow()) {
const id = resultSet.getLong(resultSet.getColumnIndex("ID"));
const name = resultSet.getString(resultSet.getColumnIndex("NAME"));
const age = resultSet.getLong(resultSet.getColumnIndex("AGE"));
const salary = resultSet.getDouble(resultSet.getColumnIndex("SALARY"));
console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}`);
}
// Release the dataset memory.
resultSet.close();
}).catch((err) => {
console.error(`Query failed, code is ${err.code},message is ${err.message}`);
})
```
### remoteQuery
remoteQuery(device: string, table: string, predicates: RdbPredicates, columns: Array&lt;string&gt; , callback: AsyncCallback&lt;ResultSet&gt;): void remoteQuery(device: string, table: string, predicates: RdbPredicates, columns: Array&lt;string&gt; , callback: AsyncCallback&lt;ResultSet&gt;): void
...@@ -2487,6 +2851,51 @@ promise.then((resultSet) => { ...@@ -2487,6 +2851,51 @@ promise.then((resultSet) => {
}) })
``` ```
### querySql<sup>10+</sup>
querySql(sql: string, callback: AsyncCallback&lt;ResultSet&gt;):void
Queries data using the specified SQL statement. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | -------------------------------------------- | ---- | ------------------------------------------------------------ |
| sql | string | Yes | SQL statement to run. |
| callback | AsyncCallback&lt;[ResultSet](#resultset)&gt; | Yes | Callback invoked to return the result. If the operation is successful, a **ResultSet** object will be returned. |
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | ---------------------------- |
| 14800000 | Inner error. |
**Example**
```js
store.querySql("SELECT * FROM EMPLOYEE CROSS JOIN BOOK WHERE BOOK.NAME = 'sanguo'", function (err, resultSet) {
if (err) {
console.error(`Query failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
// resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0.
while(resultSet.goToNextRow()) {
const id = resultSet.getLong(resultSet.getColumnIndex("ID"));
const name = resultSet.getString(resultSet.getColumnIndex("NAME"));
const age = resultSet.getLong(resultSet.getColumnIndex("AGE"));
const salary = resultSet.getDouble(resultSet.getColumnIndex("SALARY"));
console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}`);
}
// Release the dataset memory.
resultSet.close();
})
```
### querySql ### querySql
querySql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void querySql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void
...@@ -2583,6 +2992,43 @@ promise.then((resultSet) => { ...@@ -2583,6 +2992,43 @@ promise.then((resultSet) => {
}) })
``` ```
### executeSql<sup>10+</sup>
executeSql(sql: string, callback: AsyncCallback&lt;void&gt;):void
Executes an SQL statement that contains specified arguments but returns no value. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ------------------------------------ | ---- | ------------------------------------------------------------ |
| sql | string | Yes | SQL statement to run. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result. |
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | -------------------------------------------- |
| 14800047 | The WAL file size exceeds the default limit. |
| 14800000 | Inner error. |
**Example**
```js
const SQL_DELETE_TABLE = "DELETE FROM test WHERE name = 'zhangsan'"
store.executeSql(SQL_DELETE_TABLE, function(err) {
if (err) {
console.error(`ExecuteSql failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info(`Delete table done.`);
})
```
### executeSql ### executeSql
executeSql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;void&gt;):void executeSql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;void&gt;):void
...@@ -2663,6 +3109,85 @@ promise.then(() => { ...@@ -2663,6 +3109,85 @@ promise.then(() => {
}) })
``` ```
### getModifyTime<sup>10+</sup>
getModifyTime(table: string, columnName: string, primaryKeys: PRIKeyType[], callback: AsyncCallback&lt;ModifyTime&gt;): void
Obtains the last modification time of the data in a table. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ----------- | ------------------------------------------------ | ---- | ------------------------------------------------------------ |
| table | string | Yes | Name of the database table to query. |
| columnName | string | Yes | Column name of the database table to query. |
| primaryKeys | [PRIKeyType](#prikeytype10)[] | Yes | Primary keys of the rows to query.<br>If the database table has no primary key, **rowid** must be passed in through **columnName**. In this case, **primaryKeys** specifies the row numbers of the database table to query.<br>If the database table has no primary key and no **rowid** is passed in through **columnName**, an error code will be returned.|
| callback | AsyncCallback&lt;[ModifyTime](#modifytime10)&gt; | Yes | Callback invoked to return the result. If the operation is successful, the **ModifyTime** object is returned.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message**|
| ------------ | ------------ |
| 14800000 | Inner error. |
**Example**
```js
let PRIKey = [1, 4, 2, 3];
store.getModifyTime("cloud_tasks", "uuid", PRIKey, function (err, modifyTime) {
if (err) {
console.error(`getModifyTime failed, code is ${err.code},message is ${err.message}`);
return;
}
let size = modifyTime.size();
});
```
### getModifyTime<sup>10+</sup>
getModifyTime(table: string, columnName: string, primaryKeys: PRIKeyType[]): Promise&lt;ModifyTime&gt;
Obtains the last modification time of the data in a table. This API uses a promise to return the result.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ----------- | ----------------------------- | ---- | ------------------------------------------------------------ |
| table | string | Yes | Name of the database table to query. |
| columnName | string | Yes | Column name of the database table to query. |
| primaryKeys | [PRIKeyType](#prikeytype10)[] | Yes | Primary keys of the rows to query.<br>If the database table has no primary key, **rowid** must be passed in through **columnName**. In this case, **primaryKeys** specifies the row numbers of the database table to query.<br>If the database table has no primary key and no **rowid** is passed in through **columnName**, an error code will be returned.|
**Return value**
| Type | Description |
| ------------------------------------------ | --------------------------------------------------------- |
| Promise&lt;[ModifyTime](#modifytime10)&gt; | Promise used to return the **ModifyTime** object.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message**|
| ------------ | ------------ |
| 14800000 | Inner error. |
**Example**
```js
let PRIKey = [1, 2, 3];
store.getModifyTime("cloud_tasks", "uuid", PRIKey).then((modifyTime) => {
let size = modifyTime.size();
}).catch((err) => {
console.error(`getModifyTime failed, code is ${err.code},message is ${err.message}`);
});
```
### beginTransaction ### beginTransaction
beginTransaction():void beginTransaction():void
...@@ -3021,14 +3546,14 @@ Sets distributed tables. This API uses an asynchronous callback to return the re ...@@ -3021,14 +3546,14 @@ Sets distributed tables. This API uses an asynchronous callback to return the re
| Name | Type | Mandatory | Description | | Name | Type | Mandatory | Description |
| -------- | ----------------------------------- | --- | --------------- | | -------- | ----------------------------------- | --- | --------------- |
| tables | Array&lt;string&gt; | Yes | Names of the distributed tables to set. | | tables | Array&lt;string&gt; | Yes | Names of the distributed tables to set. |
| type | number | Yes | Distributed type of the tables. Currently, only **relationalStore.DistributedType.DISTRIBUTED_DEVICE** and **relationalStore.DistributedType.DISTRIBUTED_CLOUD** are supported.<br> The value **relationalStore.DistributedType.DISTRIBUTED_DEVICE** indicates distributed tables across different devices.<br> The value **relationalStore.DistributedType.DISTRIBUTED_CLOUD** indicates distributed tables between the device and cloud.| | type | number | Yes | Distributed type of the tables. Currently, only **relationalStore.DistributedType.DISTRIBUTED_DEVICE** and **relationalStore.DistributedType.DISTRIBUTED_CLOUD** are supported.<br>The value **relationalStore.DistributedType.DISTRIBUTED_DEVICE** indicates distributed tables across different devices.<br>The value **relationalStore.DistributedType.DISTRIBUTED_CLOUD** indicates distributed tables between the device and cloud.|
| config | [DistributedConfig](#distributedconfig10) | Yes| Configuration of the distributed mode.| | config | [DistributedConfig](#distributedconfig10) | Yes| Configuration of the distributed mode.|
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result.| | callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to return the result.|
**Example** **Example**
```js ```js
let config = new DistributedConfig(); let config = new relationalStore.DistributedConfig();
config.autoSync = true; config.autoSync = true;
store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIBUTED_CLOUD, config, function (err) { store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIBUTED_CLOUD, config, function (err) {
if (err) { if (err) {
...@@ -3054,7 +3579,7 @@ Sets distributed tables. This API uses a promise to return the result. ...@@ -3054,7 +3579,7 @@ Sets distributed tables. This API uses a promise to return the result.
| Name| Type | Mandatory| Description | | Name| Type | Mandatory| Description |
| ------ | ----------------------------------------- | ---- | ------------------------------------------------------------ | | ------ | ----------------------------------------- | ---- | ------------------------------------------------------------ |
| tables | Array&lt;string&gt; | Yes | Names of the distributed tables to set. | | tables | Array&lt;string&gt; | Yes | Names of the distributed tables to set. |
| type | number | No | Distributed type of the tables. The default value is **relationalStore.DistributedType.DISTRIBUTED_DEVICE**.<br> Currently, only **relationalStore.DistributedType.DISTRIBUTED_DEVICE** and **relationalStore.DistributedType.DISTRIBUTED_CLOUD** are supported.<br> The value **relationalStore.DistributedType.DISTRIBUTED_DEVICE** indicates distributed tables across different devices.<br> The value **relationalStore.DistributedType.DISTRIBUTED_CLOUD** indicates distributed tables between the device and cloud.| | type | number | No | Distributed type of the tables. The default value is **relationalStore.DistributedType.DISTRIBUTED_DEVICE**.<br>Currently, only **relationalStore.DistributedType.DISTRIBUTED_DEVICE** and **relationalStore.DistributedType.DISTRIBUTED_CLOUD** are supported.<br>The value **relationalStore.DistributedType.DISTRIBUTED_DEVICE** indicates distributed tables across different devices.<br>The value **relationalStore.DistributedType.DISTRIBUTED_CLOUD** indicates distributed tables between the device and cloud.|
| config | [DistributedConfig](#distributedconfig10) | No | Configuration of the distributed mode. If this parameter is not specified, the value of **autoSync** is **false** by default, which means only manual synchronization is supported.| | config | [DistributedConfig](#distributedconfig10) | No | Configuration of the distributed mode. If this parameter is not specified, the value of **autoSync** is **false** by default, which means only manual synchronization is supported.|
**Return value** **Return value**
...@@ -3066,7 +3591,7 @@ Sets distributed tables. This API uses a promise to return the result. ...@@ -3066,7 +3591,7 @@ Sets distributed tables. This API uses a promise to return the result.
**Example** **Example**
```js ```js
let config = new DistributedConfig(); let config = new relationalStore.DistributedConfig();
config.autoSync = true; config.autoSync = true;
let promise = store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIBUTED_CLOUD, config); let promise = store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIBUTED_CLOUD, config);
promise.then(() => { promise.then(() => {
...@@ -3314,11 +3839,153 @@ promise.then((result) =>{ ...@@ -3314,11 +3839,153 @@ promise.then((result) =>{
}) })
``` ```
### cloudSync<sup>10+</sup>
cloudSync(mode: SyncMode, progress: Callback&lt;ProgressDetails&gt;, callback: AsyncCallback&lt;void&gt;): void
Manually starts device-cloud synchronization for all distributed tables. This API uses an asynchronous callback to return the result. Before using this API, ensure that the cloud service must be available.
**Required permissions**: ohos.permission.DISTRIBUTED_DATASYNC
**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ----------------------------------------------------- | ---- | -------------------------------------------------- |
| mode | [SyncMode](#syncmode) | Yes | Synchronization mode of the database. |
| progress | Callback&lt;[ProgressDetails](#progressdetails10)&gt; | Yes | Callback used to process database synchronization details. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to send the synchronization result to the caller.|
**Example**
```js
relationalStore.cloudSync(relationalStore.SyncMode.SYNC_MODE_CLOUD_FIRST, function (progressDetails) {
console.info(`Progess: ${progressDetails}`);
}, function (err) {
if (err) {
console.error(`Cloud sync failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info('Cloud sync succeeded');
});
```
### cloudSync<sup>10+</sup>
cloudSync(mode: SyncMode, progress: Callback&lt;ProgressDetails&gt;): Promise&lt;void&gt;
Manually starts device-cloud synchronization for all distributed tables. This API uses a promise to return the result. Before using this API, ensure that the cloud service must be available.
**Required permissions**: ohos.permission.DISTRIBUTED_DATASYNC
**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ----------------------------------------------------- | ---- | -------------------------------------- |
| mode | [SyncMode](#syncmode) | Yes | Synchronization mode of the database. |
| progress | Callback&lt;[ProgressDetails](#progressdetails10)&gt; | Yes | Callback used to process database synchronization details.|
**Return value**
| Type | Description |
| ------------------- | --------------------------------------- |
| Promise&lt;void&gt; | Promise used to send the synchronization result.|
**Example**
```js
function progress(progressDetail) {
console.info(`progress: ${progressDetail}`);
}
relationalStore.cloudSync(relationalStore.SyncMode.SYNC_MODE_CLOUD_FIRST, progress).then(() => {
console.info('Cloud sync succeeded');
}).catch((err) => {
console.error(`cloudSync failed, code is ${err.code},message is ${err.message}`);
});
```
### cloudSync<sup>10+</sup>
cloudSync(mode: SyncMode, tables: string[], progress: Callback&lt;ProgressDetails&gt;, callback: AsyncCallback&lt;void&gt;): void
Manually starts device-cloud synchronization of the specified table. This API uses an asynchronous callback to return the result. Before using this API, ensure that the cloud service must be available.
**Required permissions**: ohos.permission.DISTRIBUTED_DATASYNC
**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ----------------------------------------------------- | ---- | -------------------------------------------------- |
| mode | [SyncMode](#syncmode) | Yes | Synchronization mode of the database. |
| tables | string[] | Yes | Name of the table to synchronize. |
| progress | Callback&lt;[ProgressDetails](#progressdetails10)&gt; | Yes | Callback used to process database synchronization details. |
| callback | AsyncCallback&lt;void&gt; | Yes | Callback invoked to send the synchronization result to the caller.|
**Example**
```js
const tables = ["table1", "table2"];
relationalStore.cloudSync(relationalStore.SyncMode.SYNC_MODE_CLOUD_FIRST, tables, function (progressDetails) {
console.info(`Progess: ${progressDetails}`);
}, function (err) {
if (err) {
console.error(`Cloud sync failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info('Cloud sync succeeded');
});
```
### cloudSync<sup>10+</sup>
cloudSync(mode: SyncMode, tables: string[], progress: Callback&lt;ProgressDetails&gt;): Promise&lt;void&gt;
Manually starts device-cloud synchronization of the specified table. This API uses a promise to return the result. Before using this API, ensure that the cloud service must be available.
**Required permissions**: ohos.permission.DISTRIBUTED_DATASYNC
**System capability**: SystemCapability.DistributedDataManager.CloudSync.Client
**Parameters**
| Name | Type | Mandatory| Description |
| -------- | ----------------------------------------------------- | ---- | -------------------------------------- |
| mode | [SyncMode](#syncmode) | Yes | Synchronization mode of the database. |
| tables | string[] | Yes | Name of the table to synchronize. |
| progress | Callback&lt;[ProgressDetails](#progressdetails10)&gt; | Yes | Callback used to process database synchronization details.|
**Return value**
| Type | Description |
| ------------------- | --------------------------------------- |
| Promise&lt;void&gt; | Promise used to send the synchronization result.|
**Example**
```js
const tables = ["table1", "table2"];
function progress(progressDetail) {
console.info(`progress: ${progressDetail}`);
}
relationalStore.cloudSync(relationalStore.SyncMode.SYNC_MODE_CLOUD_FIRST, tables, progress).then(() => {
console.info('Cloud sync succeeded');
}).catch((err) => {
console.error(`cloudSync failed, code is ${err.code},message is ${err.message}`);
});
```
### on('dataChange') ### on('dataChange')
on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;): void on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;): void
Registers the data change event listener for the RDB store. When the data in the RDB store changes, a callback is invoked to return the data changes. Registers a data change event listener for the RDB store. When the data in the RDB store changes, a callback is invoked to return the data changes.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
...@@ -3327,12 +3994,12 @@ Registers the data change event listener for the RDB store. When the data in the ...@@ -3327,12 +3994,12 @@ Registers the data change event listener for the RDB store. When the data in the
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | | -------- | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ |
| event | string | Yes | Event to observe. The value is **dataChange**, which indicates a data change event. | | event | string | Yes | Event to observe. The value is **dataChange**, which indicates a data change event. |
| type | [SubscribeType](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-data-relationalStore.md#subscribetype) | Yes | Subscription type to register. | | type | [SubscribeType](#subscribetype) | Yes | Subscription type to register. |
| observer | Callback&lt;Array&lt;string&gt;&gt; | Yes | Callback invoked to return the data change. **Array<string>** indicates the IDs of the peer devices whose data in the database is changed.| | observer | Callback&lt;Array&lt;string&gt;&gt; | Yes | Callback invoked to return the data change. **Array<string>** indicates the IDs of the peer devices whose data in the database is changed.|
**Example** **Example**
``` ```js
function storeObserver(devices) { function storeObserver(devices) {
for (let i = 0; i < devices.length; i++) { for (let i = 0; i < devices.length; i++) {
console.info(`device= ${devices[i]} data changed`); console.info(`device= ${devices[i]} data changed`);
...@@ -3349,7 +4016,7 @@ try { ...@@ -3349,7 +4016,7 @@ try {
on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;\| Callback&lt;Array&lt;ChangeInfo&gt;&gt;): void on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;\| Callback&lt;Array&lt;ChangeInfo&gt;&gt;): void
Registers the data change event listener for the RDB store. When the data in the RDB store changes, a callback is invoked to return the data changes. Registers a data change event listener for the RDB store. When the data in the RDB store changes, a callback is invoked to return the data changes.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core **System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
...@@ -3359,7 +4026,7 @@ Registers the data change event listener for the RDB store. When the data in the ...@@ -3359,7 +4026,7 @@ Registers the data change event listener for the RDB store. When the data in the
| -------- | ----------------------------------- | ---- | ------------------------------------------- | | -------- | ----------------------------------- | ---- | ------------------------------------------- |
| event | string | Yes | Event to observe. The value is **dataChange**, which indicates a data change event. | | event | string | Yes | Event to observe. The value is **dataChange**, which indicates a data change event. |
| type | [SubscribeType](#subscribetype) | Yes | Subscription type to register.| | type | [SubscribeType](#subscribetype) | Yes | Subscription type to register.|
| observer | Callback&lt;Array&lt;string&gt;&gt; \| Callback&lt;Array&lt;[ChangeInfo](#changeinfo10)&gt;&gt; | Yes | Callback invoked to return the data change event.<br>If **type** is **SUBSCRIBE_TYPE_REMOTE**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the IDs of the peer devices with data changes.<br> If **type** is **SUBSCRIBE_TYPE_CLOUD**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the cloud accounts with data changes.<br> If **type** is **SUBSCRIBE_TYPE_CLOUD_DETAILS**, **observer** must be **Callback&lt;Array&lt;ChangeInfo&gt;&gt;**, where **Array&lt;ChangeInfo&gt;** specifies the details about the device-cloud synchronization.| | observer | Callback&lt;Array&lt;string&gt;&gt; \| Callback&lt;Array&lt;[ChangeInfo](#changeinfo10)&gt;&gt; | Yes | Callback invoked to return the data change.<br>If **type** is **SUBSCRIBE_TYPE_REMOTE**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the IDs of the peer devices with data changes.<br>If **type** is **SUBSCRIBE_TYPE_CLOUD**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the cloud accounts with data changes.<br>If **type** is **SUBSCRIBE_TYPE_CLOUD_DETAILS**, **observer** must be **Callback&lt;Array&lt;ChangeInfo&gt;&gt;**, where **Array&lt;ChangeInfo&gt;** specifies the details about the device-cloud synchronization.|
**Example** **Example**
...@@ -3376,6 +4043,44 @@ try { ...@@ -3376,6 +4043,44 @@ try {
} }
``` ```
### on<sup>10+</sup>
on(event: string, interProcess: boolean, observer: Callback\<void>): void
Registers an intra-process or inter-process event listener for the RDB store. This callback is invoked by [emit](#emit10).
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ------------ | --------------- | ---- | ------------------------------------------------------------ |
| event | string | Yes | Event name to observe. |
| interProcess | boolean | Yes | Type of the event to observe.<br>The value **true** means the inter-process event.<br>The value **false** means the intra-process event. |
| observer | Callback\<void> | Yes | Callback invoked to return the result. |
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | -------------------------------------- |
| 14800000 | Inner error. |
| 14800050 | Failed to obtain subscription service. |
**Example**
```js
function storeObserver() {
console.info(`storeObserver`);
}
try {
store.on('storeObserver', false, storeObserver);
} catch (err) {
console.error(`Register observer failed, code is ${err.code},message is ${err.message}`);
}
```
### off('dataChange') ### off('dataChange')
off(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
...@@ -3388,8 +4093,8 @@ Unregisters the data change event listener. ...@@ -3388,8 +4093,8 @@ Unregisters the data change event listener.
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | | -------- | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ |
| event | string | Yes | Event type. The value is **dataChange**, which indicates a data change event. | | event | string | Yes | Event type. The value is **dataChange**, which indicates a data change event. |
| type | [SubscribeType](#subscribetype)| Yes | Subscription type to unregister. | | type | [SubscribeType](#subscribetype) | Yes | Subscription type to unregister. |
| observer | Callback&lt;Array&lt;string&gt;&gt; | Yes | Callback for the data change event. **Array<string>** indicates the IDs of the peer devices whose data in the database is changed.| | observer | Callback&lt;Array&lt;string&gt;&gt; | Yes | Callback for the data change event. **Array<string>** indicates the IDs of the peer devices whose data in the database is changed.|
**Example** **Example**
...@@ -3419,9 +4124,9 @@ Unregisters the data change event listener. ...@@ -3419,9 +4124,9 @@ Unregisters the data change event listener.
| Name | Type | Mandatory| Description | | Name | Type | Mandatory| Description |
| -------- | ---------------------------------- | ---- | ------------------------------------------ | | -------- | ---------------------------------- | ---- | ------------------------------------------ |
| event | string | Yes | Event to observe. The value is **dataChange**, which indicates a data change event. | | event | string | Yes | Event type. The value is **dataChange**, which indicates a data change event. |
| type | [SubscribeType](#subscribetype) | Yes | Subscription type to register. | | type | [SubscribeType](#subscribetype) | Yes | Subscription type to unregister. |
| observer | Callback&lt;Array&lt;string&gt;&gt;\| Callback&lt;Array&lt;[ChangeInfo](#changeinfo10)&gt;&gt; | No| Callback invoked to return the result.<br>If **type** is **SUBSCRIBE_TYPE_REMOTE**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the IDs of the peer devices with data changes.<br> If **type** is **SUBSCRIBE_TYPE_CLOUD**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the cloud accounts with data changes.<br> If **type** is **SUBSCRIBE_TYPE_CLOUD_DETAILS**, **observer** must be **Callback&lt;Array&lt;ChangeInfo&gt;&gt;**, where **Array&lt;ChangeInfo&gt;** specifies the details about the device-cloud synchronization.<br> If **observer** is not specified, listening for all data change events of the specified **type** will be canceled.| | observer | Callback&lt;Array&lt;string&gt;&gt;\| Callback&lt;Array&lt;[ChangeInfo](#changeinfo10)&gt;&gt; | No| Callback invoked to return the result.<br>If **type** is **SUBSCRIBE_TYPE_REMOTE**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the IDs of the peer devices with data changes.<br>If **type** is **SUBSCRIBE_TYPE_CLOUD**, **observer** must be **Callback&lt;Array&lt;string&gt;&gt;**, where **Array&lt;string&gt;** specifies the cloud accounts with data changes.<br>If **type** is **SUBSCRIBE_TYPE_CLOUD_DETAILS**, **observer** must be **Callback&lt;Array&lt;ChangeInfo&gt;&gt;**, where **Array&lt;ChangeInfo&gt;** specifies the details about the device-cloud synchronization.<br>If **observer** is not specified, listening for all data change events of the specified **type** will be canceled.|
**Example** **Example**
...@@ -3438,6 +4143,73 @@ try { ...@@ -3438,6 +4143,73 @@ try {
} }
``` ```
### off<sup>10+</sup>
off(event: string, interProcess: boolean, observer?: Callback\<void>): void
Unregisters the data change event listener.
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name | Type | Mandatory| Description |
| ------------ | --------------- | ---- | ------------------------------------------------------------ |
| event | string | Yes | Name of the event to unsubscribe from. |
| interProcess | boolean | Yes | Type of the event.<br>The value **true** means the inter-process event.<br>The value **false** means the intra-process event.|
| observer | Callback\<void> | No | Callback for the event to unregister. If this parameter is specified, the specified callback will be unregistered. If this parameter is not specified, all callbacks of the specified event will be unregistered.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | -------------------------------------- |
| 14800000 | Inner error. |
| 14800050 | Failed to obtain subscription service. |
**Example**
```js
function storeObserver() {
console.info(`storeObserver`);
}
try {
store.off('storeObserver', false, storeObserver);
} catch (err) {
console.error(`Register observer failed, code is ${err.code},message is ${err.message}`);
}
```
### emit<sup>10+</sup>
emit(event: string): void
Triggers the inter-process or intra-process event listener registered through [on](#on10).
**System capability**: SystemCapability.DistributedDataManager.RelationalStore.Core
**Parameters**
| Name| Type | Mandatory| Description |
| ------ | ------ | ---- | -------------------- |
| event | string | Yes | Name of the event.|
**Error codes**
For details about the error codes, see [RDB Error Codes](../errorcodes/errorcode-data-rdb.md).
| **ID**| **Error Message** |
| ------------ | -------------------------------------- |
| 14800000 | Inner error. |
| 14800050 | Failed to obtain subscription service. |
**Example**
```js
store.emit('storeObserver');
```
## ResultSet ## ResultSet
Provides APIs to access the result set obtained by querying the RDB store. A result set is a set of results returned after **query()** is called. Provides APIs to access the result set obtained by querying the RDB store. A result set is a set of results returned after **query()** is called.
...@@ -3857,7 +4629,7 @@ Obtains the value of the Long type based on the specified column and the current ...@@ -3857,7 +4629,7 @@ Obtains the value of the Long type based on the specified column and the current
| Type | Description | | Type | Description |
| ------ | ------------------------------------------------------------ | | ------ | ------------------------------------------------------------ |
| number | Value obtained.<br>The value range supported by API is **Number.MIN_SAFE_INTEGER** to **Number.MAX_SAFE_INTEGER**. If the value is out of this range, use [getDouble](#getdouble).| | number | Value obtained.<br>The value range supported by this API is **Number.MIN_SAFE_INTEGER** to **Number.MAX_SAFE_INTEGER**. If the value is out of this range, use [getDouble](#getdouble).|
**Error codes** **Error codes**
......
...@@ -180,7 +180,7 @@ Represents the text data. It is a child class of [UnifiedRecord](#unifiedrecord) ...@@ -180,7 +180,7 @@ Represents the text data. It is a child class of [UnifiedRecord](#unifiedrecord)
| Name | Type | Readable| Writable| Description | | Name | Type | Readable| Writable| Description |
| ------- | ------------------------- | ---- | ---- |-----------------------------------------------------------------------------------------------------------------------------------------------------| | ------- | ------------------------- | ---- | ---- |-----------------------------------------------------------------------------------------------------------------------------------------------------|
| details | { [key: string]: string } | Yes | Yes | A dictionary type object, where both the key and value are of the string type and are used to describe the text content. For example, a data object with the following content can be created to describe a text file:<br>{<br>"title":"Title",<br>"content":"Content"<br>}<br>This parameter is optional. The default value is an empty dictionary object.| | details | { [key: string]: string } | Yes | Yes | A dictionary type object, where both the key and value are of the string type and are used to describe the text content. For example, a data object with the following content can be created to describe a text file:<br>{<br>"title":"Title",<br>"content":"Content"<br>}<br> This parameter is optional. The default value is an empty dictionary object.|
**Example** **Example**
...@@ -258,7 +258,7 @@ Represents the file data. It is a child class of [UnifiedRecord](#unifiedrecord) ...@@ -258,7 +258,7 @@ Represents the file data. It is a child class of [UnifiedRecord](#unifiedrecord)
| Name | Type | Readable| Writable| Description | | Name | Type | Readable| Writable| Description |
|---------|---------------------------| ---- | ---- |------------------------------------------------------------------------------------------------------------------------------------------------------| |---------|---------------------------| ---- | ---- |------------------------------------------------------------------------------------------------------------------------------------------------------|
| details | { [key: string]: string } | Yes | Yes | A dictionary type object, where both the key and value are of the string type and are used to describe file information. For example, a data object with the following content can be created to describe a file:<br>{<br>"name":"File name",<br>"type":"File type"<br>}<br>This parameter is optional. The default value is an empty dictionary object.| | details | { [key: string]: string } | Yes | Yes | A dictionary type object, where both the key and value are of the string type and are used to describe file information. For example, a data object with the following content can be created to describe a file:<br>{<br>"name":"File name",<br>"type":"File type"<br>}<br> This parameter is optional. The default value is an empty dictionary object.|
| uri | string | Yes | Yes | URI of the file data. | | uri | string | Yes | Yes | URI of the file data. |
**Example** **Example**
...@@ -484,13 +484,13 @@ let unifiedData = new UDMF.UnifiedData(record); ...@@ -484,13 +484,13 @@ let unifiedData = new UDMF.UnifiedData(record);
## Intention ## Intention
Enumerates the types of the system abilities connected to the UDMF. It identifies the purpose of the data written by the user to the UDMF and the system abilities connected to the UDMF to implement data transmission between applications. Enumerates the data channel types supported by the UDMF. It is used to identify different service scenarios, to which the UDMF data channels apply.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
| Name | Value | Description | | Name | Value | Description |
|----------|-----------|---------| |----------|-----------|---------|
| DATA_HUB | 'DataHub' | Public data hub.| | DATA_HUB | 'DataHub' | Public data channel.|
## Options ## Options
...@@ -499,9 +499,9 @@ Defines the data operation performed by the UDMF. It includes two optional param ...@@ -499,9 +499,9 @@ Defines the data operation performed by the UDMF. It includes two optional param
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
| Name | Type | Readable| Writable| Mandatory| Description | | Name | Type | Readable| Writable| Mandatory| Description |
|-----------|-------------------------|----|----|----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------|-------------------------|----|----|----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| intention | [Intention](#intention) | Yes | Yes | No | Service tag related to the data operation. | | intention | [Intention](#intention) | Yes | Yes | No | Type of the data channel related to the data operation. |
| key | string | Yes | Yes | No | Unique identifier of a data object in the UDMF, which can be obtained from the return value of [insertData](#udmfinsertdata).<br>The key consists of **udmf:/**, **intention**, **bundleName**, and **groupId** with a (/) in between, for example, **udmf://DataHub/com.ohos.test/0123456789**.<br>**udmf:/** is fixed, **DataHub** is an enum of **intention**, **com.ohos.test** is the bundle name, and **0123456789** is a group ID randomly generated.| | key | string | Yes | Yes | No | Unique identifier of a data object in the UDMF, which can be obtained from the return value of [insertData](#udmfinsertdata).<br>The key consists of **udmf:/**, **intention**, **bundleName**, and **groupId** with a (/) in between, for example, **udmf://DataHub/com.ohos.test/0123456789**.<br>**udmf:/** is fixed, **DataHub** is an enum of **intention**, **com.ohos.test** is the bundle name, and **0123456789** is a group ID randomly generated.|
...@@ -510,7 +510,7 @@ Defines the data operation performed by the UDMF. It includes two optional param ...@@ -510,7 +510,7 @@ Defines the data operation performed by the UDMF. It includes two optional param
insertData(options: Options, data: UnifiedData, callback: AsyncCallback&lt;string&gt;): void insertData(options: Options, data: UnifiedData, callback: AsyncCallback&lt;string&gt;): void
Inserts data to the UDMF. This API uses an asynchronous callback to return the result. Inserts data to the UDMF public data channel. This API uses an asynchronous callback to return the unique identifier of the data inserted.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -552,7 +552,7 @@ try { ...@@ -552,7 +552,7 @@ try {
insertData(options: Options, data: UnifiedData): Promise&lt;string&gt; insertData(options: Options, data: UnifiedData): Promise&lt;string&gt;
Inserts data to the UDMF. This API uses a promise to return the result. Inserts data to the UDMF public data channel. This API uses a promise to return the unique identifier of the data inserted.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -596,7 +596,7 @@ try { ...@@ -596,7 +596,7 @@ try {
updateData(options: Options, data: UnifiedData, callback: AsyncCallback&lt;void&gt;): void updateData(options: Options, data: UnifiedData, callback: AsyncCallback&lt;void&gt;): void
Updates data in the UDMF. This API uses an asynchronous callback to return the result. Updates the data in the UDMF public data channel. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -626,7 +626,7 @@ try { ...@@ -626,7 +626,7 @@ try {
if (err === undefined) { if (err === undefined) {
console.info('Succeeded in updating data.'); console.info('Succeeded in updating data.');
} else { } else {
console.error('Failed to update data. code is ${err.code},message is ${err.message} `); console.error(`Failed to update data. code is ${err.code},message is ${err.message} `);
} }
}); });
} catch(e) { } catch(e) {
...@@ -638,7 +638,7 @@ try { ...@@ -638,7 +638,7 @@ try {
updateData(options: Options, data: UnifiedData): Promise&lt;void&gt; updateData(options: Options, data: UnifiedData): Promise&lt;void&gt;
Updates data in the UDMF. This API uses a promise to return the result. Updates the data in the UDMF public data channel. This API uses a promise to return the result.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -683,7 +683,7 @@ try { ...@@ -683,7 +683,7 @@ try {
queryData(options: Options, callback: AsyncCallback&lt;Array&lt;UnifiedData&gt;&gt;): void queryData(options: Options, callback: AsyncCallback&lt;Array&lt;UnifiedData&gt;&gt;): void
Queries data in the UDMF. This API uses an asynchronous callback to return the result. Queries data in the UDMF public data channel. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -729,7 +729,7 @@ try { ...@@ -729,7 +729,7 @@ try {
queryData(options: Options): Promise&lt;Array&lt;UnifiedData&gt;&gt; queryData(options: Options): Promise&lt;Array&lt;UnifiedData&gt;&gt;
Queries data in the UDMF. This API uses a promise to return the result. Queries data in the UDMF public data channel. This API uses a promise to return the result.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -778,7 +778,7 @@ try { ...@@ -778,7 +778,7 @@ try {
deleteData(options: Options, callback: AsyncCallback&lt;Array&lt;UnifiedData&gt;&gt;): void deleteData(options: Options, callback: AsyncCallback&lt;Array&lt;UnifiedData&gt;&gt;): void
Deletes data from the UDMF. This API uses an asynchronous callback to return the result. Deletes data from the UDMF public data channel. This API uses an asynchronous callback to return the result.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
...@@ -824,7 +824,7 @@ try { ...@@ -824,7 +824,7 @@ try {
deleteData(options: Options): Promise&lt;Array&lt;UnifiedData&gt;&gt; deleteData(options: Options): Promise&lt;Array&lt;UnifiedData&gt;&gt;
Deletes data in the UDMF. This API uses a promise to return the result. Deletes data from the UDMF public data channel. This API uses a promise to return the result.
**System capability**: SystemCapability.DistributedDataManager.UDMF.Core **System capability**: SystemCapability.DistributedDataManager.UDMF.Core
......
...@@ -117,4 +117,59 @@ Data is added, deleted, and modified continuously without closing the read trans ...@@ -117,4 +117,59 @@ Data is added, deleted, and modified continuously without closing the read trans
**Solution** **Solution**
1. Check for unclosed result sets or transactions. 1. Check for unclosed result sets or transactions.
2. Closes all result sets or transactions. 2. Closes all result sets or transactions.
## 14800050 Failed to Obtain the Subscription Service
**Error Message**
Failed to obtain subscription service.
**Description**
The error code is returned when the subscription service failed to be obtained.
**Possible Causes**
The platform does not support service subscription.
**Solution**
Deploy the subscription service on the platform.
## 14801001 Stage Model Required
**Error Message**
Only supported in stage mode.
**Description**
This operation can be performed only on the stage model.
**Possible Causes**
The context is not a stage model.
**Solution**
Perform the operation on the stage model.
## 14801002 Invalid dataGroupId in storeConfig
**Error Message**
The data group id is not valid.
**Description**
The **dataGroupId** parameter is invalid.
**Possible Causes**
The **dataGroupId** is not obtained from the AppGallery.
**Solution**
Obtain **dataGroupId** from the AppGallery and pass it to **storeConfig** correctly.
...@@ -23,3 +23,57 @@ The possible causes are as follows: ...@@ -23,3 +23,57 @@ The possible causes are as follows:
1. Check that the file name is correct. 1. Check that the file name is correct.
2. Check that the file path is correct. 2. Check that the file path is correct.
## 15500019 Failed to Obtain the Subscription Service
**Error Message**
Failed to obtain subscription service.
**Description**
Failed to obtain the subscription service in inter-process event subscription.
**Possible Causes**
The platform does not support service subscription.
**Solution**
Deploy the subscription service on the platform.
## 14801001 Stage Model Required
**Error Message**
Only supported in stage mode.
**Description**
This operation can be performed only on the stage model.
**Possible Causes**
The context is not a stage model.
**Solution**
Perform the operation on the stage model.
## 15501002 The dataGroupId parameter in Options is invalid.
**Error Message**
The data group id is not valid.
**Description**
The **dataGroupId** parameter is invalid.
**Possible Causes**
The **dataGroupId** is not obtained from the AppGallery.
**Solution**
Obtain **dataGroupId** from the AppGallery and pass it correctly.
...@@ -24,6 +24,7 @@ Typical key generation operations involve the following: ...@@ -24,6 +24,7 @@ Typical key generation operations involve the following:
The following table describes the APIs used in typical key generation operations. For more information about the APIs, see [Crypto Framework](../reference/apis/js-apis-cryptoFramework.md). The following table describes the APIs used in typical key generation operations. For more information about the APIs, see [Crypto Framework](../reference/apis/js-apis-cryptoFramework.md).
|Instance|API|Description| |Instance|API|Description|
|---|---|---| |---|---|---|
|cryptoFramework|createAsyKeyGenerator(algName : string) : AsyKeyGenerator|Creates an **AsyKeyGenerator** instance based on the asymmetric key pair specifications specified by **algName**.| |cryptoFramework|createAsyKeyGenerator(algName : string) : AsyKeyGenerator|Creates an **AsyKeyGenerator** instance based on the asymmetric key pair specifications specified by **algName**.|
...@@ -54,17 +55,17 @@ import cryptoFramework from '@ohos.security.cryptoFramework'; ...@@ -54,17 +55,17 @@ import cryptoFramework from '@ohos.security.cryptoFramework';
function generateAsyKey() { function generateAsyKey() {
// Create an AsyKeyGenerator instance. // Create an AsyKeyGenerator instance.
let rsaGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024|PRIMES_2"); let rsaGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024|PRIMES_2');
// Use the key generator to randomly generate an asymmetric key pair. // Use the key generator to randomly generate an asymmetric key pair.
let keyGenPromise = rsaGenerator.generateKeyPair(); let keyGenPromise = rsaGenerator.generateKeyPair();
keyGenPromise.then( keyPair => { keyGenPromise.then(keyPair => {
let pubKey = keyPair.pubKey; let pubKey = keyPair.pubKey;
let priKey = keyPair.priKey; let priKey = keyPair.priKey;
// Obtain the binary data of the asymmetric key pair. // Obtain the binary data of the asymmetric key pair.
let pkBlob = pubKey.getEncoded(); let pkBlob = pubKey.getEncoded();
let skBlob = priKey.getEncoded(); let skBlob = priKey.getEncoded();
AlertDialog.show({ message : "pk bin data" + pkBlob.data} ); AlertDialog.show({ message: 'pk bin data' + pkBlob.data });
AlertDialog.show({ message : "sk bin data" + skBlob.data} ); AlertDialog.show({ message: 'sk bin data' + skBlob.data });
}) })
} }
``` ```
...@@ -94,8 +95,8 @@ function testGenerateAesKey() { ...@@ -94,8 +95,8 @@ function testGenerateAesKey() {
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256'); let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256');
// Use the key generator to randomly generate a symmetric key. // Use the key generator to randomly generate a symmetric key.
let promiseSymKey = symKeyGenerator.generateSymKey(); let promiseSymKey = symKeyGenerator.generateSymKey();
promiseSymKey.then( key => { promiseSymKey.then(key => {
// Obtain the binary data of the symmetric key and output a 256-bit byte stream. // Obtain the binary data of the symmetric key and output the 256-bit key in hexadecimal format. The length is 64, that is, 32 bytes.
let encodedKey = key.getEncoded(); let encodedKey = key.getEncoded();
console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data)); console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data));
}) })
...@@ -113,14 +114,15 @@ Generate an RSA asymmetric key pair from the binary data. ...@@ -113,14 +114,15 @@ Generate an RSA asymmetric key pair from the binary data.
import cryptoFramework from '@ohos.security.cryptoFramework'; import cryptoFramework from '@ohos.security.cryptoFramework';
function convertAsyKey() { function convertAsyKey() {
let rsaGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024"); let rsaGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024');
let pkval = new Uint8Array([48,129,159,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,129,141,0,48,129,137,2,129,129,0,174,203,113,83,113,3,143,213,194,79,91,9,51,142,87,45,97,65,136,24,166,35,5,179,42,47,212,79,111,74,134,120,73,67,21,19,235,80,46,152,209,133,232,87,192,140,18,206,27,106,106,169,106,46,135,111,118,32,129,27,89,255,183,116,247,38,12,7,238,77,151,167,6,102,153,126,66,28,253,253,216,64,20,138,117,72,15,216,178,37,208,179,63,204,39,94,244,170,48,190,21,11,73,169,156,104,193,3,17,100,28,60,50,92,235,218,57,73,119,19,101,164,192,161,197,106,105,73,2,3,1,0,1]); let pkVal = new Uint8Array([48, 129, 159, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 129, 141, 0, 48, 129, 137, 2, 129, 129, 0, 174, 203, 113, 83, 113, 3, 143, 213, 194, 79, 91, 9, 51, 142, 87, 45, 97, 65, 136, 24, 166, 35, 5, 179, 42, 47, 212, 79, 111, 74, 134, 120, 73, 67, 21, 19, 235, 80, 46, 152, 209, 133, 232, 87, 192, 140, 18, 206, 27, 106, 106, 169, 106, 46, 135, 111, 118, 32, 129, 27, 89, 255, 183, 116, 247, 38, 12, 7, 238, 77, 151, 167, 6, 102, 153, 126, 66, 28, 253, 253, 216, 64, 20, 138, 117, 72, 15, 216, 178, 37, 208, 179, 63, 204, 39, 94, 244, 170, 48, 190, 21, 11, 73, 169, 156, 104, 193, 3, 17, 100, 28, 60, 50, 92, 235, 218, 57, 73, 119, 19, 101, 164, 192, 161, 197, 106, 105, 73, 2, 3, 1, 0, 1]);
let pkBlob = {data : pkval}; let pkBlob = { data: pkVal };
rsaGenerator.convertKey(pkBlob, null, function(err, keyPair) { rsaGenerator.convertKey(pkBlob, null, (err, keyPair) => {
if (keyPair == null) { if (err) {
AlertDialog.show({message : "Convert keypair fail"}); AlertDialog.show({ message: 'Convert keyPair fail' });
return;
} }
AlertDialog.show({message : "Convert KeyPair success"}); AlertDialog.show({ message: 'Convert keyPair success' });
}) })
} }
``` ```
...@@ -140,17 +142,18 @@ Generate an ECC asymmetric key pair from the binary data. ...@@ -140,17 +142,18 @@ Generate an ECC asymmetric key pair from the binary data.
import cryptoFramework from '@ohos.security.cryptoFramework'; import cryptoFramework from '@ohos.security.cryptoFramework';
function convertEccAsyKey() { function convertEccAsyKey() {
let pubKeyArray = new Uint8Array([48,89,48,19,6,7,42,134,72,206,61,2,1,6,8,42,134,72,206,61,3,1,7,3,66,0,4,83,96,142,9,86,214,126,106,247,233,92,125,4,128,138,105,246,162,215,71,81,58,202,121,26,105,211,55,130,45,236,143,55,16,248,75,167,160,167,106,2,152,243,44,68,66,0,167,99,92,235,215,159,239,28,106,124,171,34,145,124,174,57,92]); let pubKeyArray = new Uint8Array([48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 83, 96, 142, 9, 86, 214, 126, 106, 247, 233, 92, 125, 4, 128, 138, 105, 246, 162, 215, 71, 81, 58, 202, 121, 26, 105, 211, 55, 130, 45, 236, 143, 55, 16, 248, 75, 167, 160, 167, 106, 2, 152, 243, 44, 68, 66, 0, 167, 99, 92, 235, 215, 159, 239, 28, 106, 124, 171, 34, 145, 124, 174, 57, 92]);
let priKeyArray = new Uint8Array([48,49,2,1,1,4,32,115,56,137,35,207,0,60,191,90,61,136,105,210,16,27,4,171,57,10,61,123,40,189,28,34,207,236,22,45,223,10,189,160,10,6,8,42,134,72,206,61,3,1,7]); let priKeyArray = new Uint8Array([48, 49, 2, 1, 1, 4, 32, 115, 56, 137, 35, 207, 0, 60, 191, 90, 61, 136, 105, 210, 16, 27, 4, 171, 57, 10, 61, 123, 40, 189, 28, 34, 207, 236, 22, 45, 223, 10, 189, 160, 10, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7]);
let pubKeyBlob = { data: pubKeyArray }; let pubKeyBlob = { data: pubKeyArray };
let priKeyBlob = { data: priKeyArray }; let priKeyBlob = { data: priKeyArray };
let generator = cryptoFramework.createAsyKeyGenerator("ECC256"); let generator = cryptoFramework.createAsyKeyGenerator('ECC256');
generator.convertKey(pubKeyBlob, priKeyBlob, (error, data) => { generator.convertKey(pubKeyBlob, priKeyBlob, (error, data) => {
if (error) { if (error) {
AlertDialog.show({message : "Convert keypair fail"}); AlertDialog.show({ message: 'Convert keyPair fail' });
} return;
AlertDialog.show({message : "Convert KeyPair success"}); }
}) AlertDialog.show({ message: 'Convert keyPair success' });
})
} }
``` ```
...@@ -178,34 +181,135 @@ function genKeyMaterialBlob() { ...@@ -178,34 +181,135 @@ function genKeyMaterialBlob() {
let arr = [ let arr = [
0xba, 0x3d, 0xc2, 0x71, 0x21, 0x1e, 0x30, 0x56, 0xba, 0x3d, 0xc2, 0x71, 0x21, 0x1e, 0x30, 0x56,
0xad, 0x47, 0xfc, 0x5a, 0x46, 0x39, 0xee, 0x7c, 0xad, 0x47, 0xfc, 0x5a, 0x46, 0x39, 0xee, 0x7c,
0xba, 0x3b, 0xc2, 0x71, 0xab, 0xa0, 0x30, 0x72]; // keyLen = 192 (24 bytes) 0xba, 0x3b, 0xc2, 0x71, 0xab, 0xa0, 0x30, 0x72]; // keyLen = 192 (24 bytes)
let keyMaterial = new Uint8Array(arr); let keyMaterial = new Uint8Array(arr);
return {data : keyMaterial}; return { data: keyMaterial };
} }
function testConvertAesKey() { function testConvertSymKey() {
// Create a SymKeyGenerator instance. // Create a SymKeyGenerator instance.
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('3DES192'); let symKeyGenerator = cryptoFramework.createSymKeyGenerator('3DES192');
// Generate a symmetric key based on the specified data. // Generate a symmetric key based on the specified data.
let keyMaterialBlob = genKeyMaterialBlob(); let keyMaterialBlob = genKeyMaterialBlob();
try { try {
symKeyGenerator.convertKey(keyMaterialBlob, (error, key) => { symKeyGenerator.convertKey(keyMaterialBlob, (error, key) => {
if (error) { // If the service logic fails to be executed, return the error information in the first parameter of the callback. if (error) {// If the service logic fails to be executed, the first parameter of callback returns error information, that is, an exception is thrown asynchronously.
console.error(`convertKey error, ${error.code}, ${error.message}`); console.error(`convertKey error, ${error.code}, ${error.message}`);
return; return;
} }
console.info(`key algName: ${key.algName}`); console.info(`key algName: ${key.algName}`);
console.info(`key format: ${key.format}`); console.info(`key format: ${key.format}`);
let encodedKey = key.getEncoded(); // Obtain the binary data of the symmetric key and output a 192-bit byte stream. let encodedKey = key.getEncoded(); // Obtain the binary data of the symmetric key and output a 192-bit byte stream in hexadecimal format. The length is 48, that is, 24 bytes.
console.info('key getEncoded hex: ' + uint8ArrayToShowStr(encodedKey.data)); console.info('key getEncoded hex: ' + uint8ArrayToShowStr(encodedKey.data));
}) })
} catch (error) { // Throw an exception immediately after an error is detected during the parameter check. } catch (error) { // Throw an exception immediately when an error is detected in parameter check.
console.error(`convertKey failed, ${error.code}, ${error.message}`); console.error(`convertKey failed, ${error.code}, ${error.message}`);
return; return;
} }
} }
``` ```
### Randomly Generating an SM2 Key Pair and Obtaining the Binary Data
> **NOTE**
>
> SM2 asymmetric keys can be randomly generated from API version 10.
Randomly generate an asymmetric key pair and obtain its binary data.
1. Create an **AsyKeyGenerator** instance.
2. Randomly generate an asymmetric key pair using **AsyKeyGenerator**.
3. Obtain the binary data of the key pair generated.
Example: Randomly generate an SM2 key (256 bits) in promise mode.
```js
import cryptoFramework from '@ohos.security.cryptoFramework';
function generateAsyKey() {
// Create an AsyKeyGenerator instance.
let rsaGenerator = cryptoFramework.createAsyKeyGenerator("SM2_256");
// Use the key generator to randomly generate an asymmetric key pair.
let keyGenPromise = rsaGenerator.generateKeyPair();
keyGenPromise.then(keyPair => {
let pubKey = keyPair.pubKey;
let priKey = keyPair.priKey;
// Obtain the binary data of the asymmetric key pair.
let pkBlob = pubKey.getEncoded();
let skBlob = priKey.getEncoded();
AlertDialog.show({ message: "pk bin data" + pkBlob.data });
AlertDialog.show({ message: "sk bin data" + skBlob.data });
})
}
```
### Randomly Generating an SM4 Key and Obtaining the Binary Data
> **NOTE**
>
> SM4 keys can be randomly generated from API version 10.
Randomly generate a symmetric key and obtain its binary data.
1. Create a **SymKeyGenerator** instance.
2. Randomly generate a symmetric key using **SymKeyGenerator**.
3. Obtain the binary data of the key generated.
Example: Randomly generate an SM4 key (128 bits) in promise mode.
```js
import cryptoFramework from '@ohos.security.cryptoFramework';
// Output the byte streams in hexadecimal format.
function uint8ArrayToShowStr(uint8Array) {
return Array.prototype.map
.call(uint8Array, (x) => ('00' + x.toString(16)).slice(-2))
.join('');
}
function testGenerateSM4Key() {
// Create a SymKeyGenerator instance.
let symKeyGenerator = cryptoFramework.createSymKeyGenerator("SM4_128");
// Use the key generator to randomly generate a symmetric key.
let promiseSymKey = symKeyGenerator.generateSymKey();
promiseSymKey.then(key => {
// Obtain the binary data of the symmetric key and output a 128-bit byte stream in hexadecimal format. The length is 32, that is, 16 bytes.
let encodedKey = key.getEncoded();
console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data));
})
}
```
### Converting Binary Data into an SM2 Key Pair
> **NOTE**
>
> SM2 key conversion is supported from API version 10.
Generate an SM2 asymmetric key pair from the given binary key data.
1. Obtain the SM2 binary key data and encapsulate it into a **DataBlob** instance.
2. Call **convertKey()** to convert the binary data (data of the private or public key, or both) into a **KeyPair** instance.
```js
import cryptoFramework from '@ohos.security.cryptoFramework';
function convertSM2AsyKey() {
let pubKeyArray = new Uint8Array([48,89,48,19,6,7,42,134,72,206,61,2,1,6,8,42,129,28,207,85,1,130,45,3,66,0,4,90,3,58,157,190,248,76,7,132,200,151,208,112,230,96,140,90,238,211,155,128,109,248,40,83,214,78,42,104,106,55,148,249,35,61,32,221,135,143,100,45,97,194,176,52,73,136,174,40,70,70,34,103,103,161,99,27,187,13,187,109,244,13,7]);
let priKeyArray = new Uint8Array([48,49,2,1,1,4,32,54,41,239,240,63,188,134,113,31,102,149,203,245,89,15,15,47,202,170,60,38,154,28,169,189,100,251,76,112,223,156,159,160,10,6,8,42,129,28,207,85,1,130,45]);
let pubKeyBlob = { data: pubKeyArray };
let priKeyBlob = { data: priKeyArray };
let generator = cryptoFramework.createAsyKeyGenerator("SM2_256");
generator.convertKey(pubKeyBlob, priKeyBlob, (error, data) => {
if (error) {
AlertDialog.show({message : "Convert keypair fail"});
return;
}
AlertDialog.show({message : "Convert KeyPair success"});
})
}
```
## Generating an Asymmetric Key Object and Obtaining Key Parameters ## Generating an Asymmetric Key Object and Obtaining Key Parameters
### When to Use ### When to Use
...@@ -225,12 +329,12 @@ The following table describes the APIs used in typical key generation operations ...@@ -225,12 +329,12 @@ The following table describes the APIs used in typical key generation operations
|Instance|API|Description| |Instance|API|Description|
|---|---|---| |---|---|---|
|AsyKeyGeneratorBySpec|generateKeyPair(callback: AsyncCallback<KeyPair>): void;|Generates a **KeyPair** instance based on the key parameters. This API uses an asynchronous callback to return the result.| |AsyKeyGeneratorBySpec|generateKeyPair(callback: AsyncCallback\<KeyPair>): void;|Generates a **KeyPair** instance based on the key parameters. This API uses an asynchronous callback to return the result.|
|AsyKeyGeneratorBySpec|generateKeyPair(): Promise<KeyPair>;|Generates a **KeyPair** instance based on the key parameters. This API uses a promise to return the result.| |AsyKeyGeneratorBySpec|generateKeyPair(): Promise\<KeyPair>;|Generates a **KeyPair** instance based on the key parameters. This API uses a promise to return the result.|
|AsyKeyGeneratorBySpec|generatePriKey(callback: AsyncCallback<KeyPair>): void;|Generates a **PriKey** instance based on the key parameters. This API uses an asynchronous callback to return the result.| |AsyKeyGeneratorBySpec|generatePriKey(callback: AsyncCallback\<KeyPair>): void;|Generates a **PriKey** instance based on the key parameters. This API uses an asynchronous callback to return the result.|
|AsyKeyGeneratorBySpec|generatePriKey(): Promise<KeyPair>;|Generates a **PriKey** instance based on the key parameters. This API uses a promise to return the result.| |AsyKeyGeneratorBySpec|generatePriKey(): Promise\<KeyPair>;|Generates a **PriKey** instance based on the key parameters. This API uses a promise to return the result.|
|AsyKeyGeneratorBySpec|generatePubKey(callback: AsyncCallback<KeyPair>): void;|Generates a **PubKey** instance based on the key parameters. This API uses an asynchronous callback to return the result.| |AsyKeyGeneratorBySpec|generatePubKey(callback: AsyncCallback\<KeyPair>): void;|Generates a **PubKey** instance based on the key parameters. This API uses an asynchronous callback to return the result.|
|AsyKeyGeneratorBySpec|generatePubKey(): Promise<KeyPair>;|Generates a **PubKey** instance based on the key parameters. This API uses a promise to return the result.| |AsyKeyGeneratorBySpec|generatePubKey(): Promise\<KeyPair>;|Generates a **PubKey** instance based on the key parameters. This API uses a promise to return the result.|
| PriKey | getAsyKeySpec(itemType: AsyKeySpecItem): bigint \| string \| number; | Obtains the key specifications of a **PriKey** instance.| | PriKey | getAsyKeySpec(itemType: AsyKeySpecItem): bigint \| string \| number; | Obtains the key specifications of a **PriKey** instance.|
| PubKey | getAsyKeySpec(itemType: AsyKeySpecItem): bigint \| string \| number; | Obtains the key specifications of a **PubKey** instance.| | PubKey | getAsyKeySpec(itemType: AsyKeySpecItem): bigint \| string \| number; | Obtains the key specifications of a **PubKey** instance.|
...@@ -258,24 +362,24 @@ function showBigIntInfo(bnName, bnValue) { ...@@ -258,24 +362,24 @@ function showBigIntInfo(bnName, bnValue) {
// Construct the EccCommonSpec struct based on the key specifications. The EccCommonSpec struct defines the common parameters of the ECC private key and public key. // Construct the EccCommonSpec struct based on the key specifications. The EccCommonSpec struct defines the common parameters of the ECC private key and public key.
function genEccCommonSpec() { function genEccCommonSpec() {
let fieldFp = { let fieldFp = {
fieldType : "Fp", fieldType: "Fp",
p : BigInt("0xffffffffffffffffffffffffffffffff000000000000000000000001") p: BigInt("0xffffffffffffffffffffffffffffffff000000000000000000000001")
} }
let G = { let G = {
x : BigInt("0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"), x: BigInt("0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"),
y : BigInt("0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34") y: BigInt("0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34")
} }
let eccCommonSpec = { let eccCommonSpec = {
algName : "ECC", algName: "ECC",
specType : cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC, specType: cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC,
field : fieldFp, field: fieldFp,
a : BigInt("0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe"), a: BigInt("0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe"),
b : BigInt("0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"), b: BigInt("0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"),
g : G, g: G,
n : BigInt("0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d"), n: BigInt("0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d"),
h : 1 h: 1
} }
return eccCommonSpec; return eccCommonSpec;
} }
...@@ -285,7 +389,7 @@ function showEccSpecDetailInfo(key, keyType) { ...@@ -285,7 +389,7 @@ function showEccSpecDetailInfo(key, keyType) {
console.info("show detail of " + keyType + ":"); console.info("show detail of " + keyType + ":");
try { try {
let p = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_FP_P_BN); let p = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_FP_P_BN);
showBigIntInfo("--- p", p); //length is 224, hex : ffffffffffffffffffffffffffffffff000000000000000000000001 showBigIntInfo("--- p", p); // length is 224, hex : ffffffffffffffffffffffffffffffff000000000000000000000001
let a = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_A_BN); let a = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_A_BN);
showBigIntInfo("--- a", a); // length is 224, hex : fffffffffffffffffffffffffffffffefffffffffffffffffffffffe showBigIntInfo("--- a", a); // length is 224, hex : fffffffffffffffffffffffffffffffefffffffffffffffffffffffe
...@@ -303,7 +407,7 @@ function showEccSpecDetailInfo(key, keyType) { ...@@ -303,7 +407,7 @@ function showEccSpecDetailInfo(key, keyType) {
showBigIntInfo("--- n", n); // length is 224, hex : ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d showBigIntInfo("--- n", n); // length is 224, hex : ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d
let h = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_H_NUM); let h = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_H_NUM);
console.warn("--- h: " + h); //key h: 1 console.warn("--- h: " + h); // key h: 1
let fieldType = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_FIELD_TYPE_STR); let fieldType = key.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.ECC_FIELD_TYPE_STR);
console.warn("--- field type: " + fieldType); // key field type: Fp console.warn("--- field type: " + fieldType); // key field type: Fp
...@@ -330,20 +434,21 @@ function showEccSpecDetailInfo(key, keyType) { ...@@ -330,20 +434,21 @@ function showEccSpecDetailInfo(key, keyType) {
} }
// Generate an ECC key pair based on the EccCommonSpec instance and obtain the key specifications. // Generate an ECC key pair based on the EccCommonSpec instance and obtain the key specifications.
function testEccUseCommKeySpecGet() function testEccUseCommKeySpecGet() {
{
try { try {
let commKeySpec = genEccCommonSpec(); // Construct the EccCommonSpec object. let commKeySpec = genEccCommonSpec(); // Construct the EccCommonSpec object.
let generatorBySpec = cryptoFramework.createAsyKeyGeneratorBySpec(commKeySpec); // Create an AsyKeyGenerator instance based on the EccCommonSpec object. let generatorBySpec = cryptoFramework.createAsyKeyGeneratorBySpec(commKeySpec); // Create an AsyKeyGenerator instance based on the EccCommonSpec object.
let keyPairPromise = generatorBySpec.generateKeyPair(); // Generate an ECC key pair. let keyPairPromise = generatorBySpec.generateKeyPair(); // Generate an ECC key pair.
keyPairPromise.then( keyPair => { keyPairPromise.then(keyPair => {
showEccSpecDetailInfo(keyPair.priKey, "priKey"); // Obtain the ECC specifications of the private key. showEccSpecDetailInfo(keyPair.priKey, "priKey"); // Obtain the ECC specifications of the private key.
showEccSpecDetailInfo(keyPair.pubKey, "pubKey"); // Obtain the ECC specifications of the public key. showEccSpecDetailInfo(keyPair.pubKey, "pubKey"); // Obtain the ECC specifications of the public key.
}).catch(error => { }).catch(error => {
// Capture exceptions such as logic errors asynchronously here.
console.error("generateComm error"); console.error("generateComm error");
console.error("error code: " + error.code + ", message is: " + error.message); console.error("error code: " + error.code + ", message is: " + error.message);
}) })
} catch(error) { } catch (error) {
// Capture parameter errors synchronously here.
console.error("testEccUseCommSpec error"); console.error("testEccUseCommSpec error");
console.error("error code: " + error.code + ", message is: " + error.message); console.error("error code: " + error.code + ", message is: " + error.message);
} }
...@@ -360,12 +465,15 @@ Generate an RSA public key based on parameters and obtain key specifications. ...@@ -360,12 +465,15 @@ Generate an RSA public key based on parameters and obtain key specifications.
Example: Generate an RSA public key based on key parameters in callback mode. Example: Generate an RSA public key based on key parameters in callback mode.
```js ```js
import cryptoFramework from '@ohos.security.cryptoFramework';
// Generate RSA public key specifications. // Generate RSA public key specifications.
function genRsaPubKeySpec(nIn : bigint, eIn : bigint) { function genRsaPubKeySpec(nIn: bigint, eIn: bigint) {
let rsaCommSpec = { n : nIn, algName : "RSA", specType : cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC }; let rsaCommSpec = { n: nIn, algName: "RSA", specType: cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC };
let rsaPubKeySpec = { params: rsaCommSpec, pk : eIn, algName : "RSA", specType : cryptoFramework.AsyKeySpecType.PUBLIC_KEY_SPEC }; let rsaPubKeySpec = {
params: rsaCommSpec,
pk: eIn,
algName: "RSA",
specType: cryptoFramework.AsyKeySpecType.PUBLIC_KEY_SPEC
};
return rsaPubKeySpec; return rsaPubKeySpec;
} }
...@@ -396,7 +504,7 @@ function rsaUsePubKeySpecGetCallback() { ...@@ -396,7 +504,7 @@ function rsaUsePubKeySpecGetCallback() {
let nBN = pubKey.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.RSA_N_BN); let nBN = pubKey.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.RSA_N_BN);
let eBN = pubKey.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.RSA_PK_BN); let eBN = pubKey.getAsyKeySpec(cryptoFramework.AsyKeySpecItem.RSA_PK_BN);
if (compareRsaPubKeyBySpec(rsaPubKeySpec, nBN, eBN) != true) { if (compareRsaPubKeyBySpec(rsaPubKeySpec, nBN, eBN) != true) {
AlertDialog.show({ message : "error pub key big number"} ); AlertDialog.show({ message: "error pub key big number" });
} else { } else {
console.info("n, e in the pubKey are same as the spec."); console.info("n, e in the pubKey are same as the spec.");
} }
...@@ -424,7 +532,6 @@ Important data needs to be encrypted in data storage or transmission for securit ...@@ -424,7 +532,6 @@ Important data needs to be encrypted in data storage or transmission for securit
### Available APIs ### Available APIs
The following table describes the APIs used in the typical encryption and decryption operations. For details about the APIs, see [Crypto Framework](../reference/apis/js-apis-cryptoFramework.md). The following table describes the APIs used in the typical encryption and decryption operations. For details about the APIs, see [Crypto Framework](../reference/apis/js-apis-cryptoFramework.md).
> **NOTE** > **NOTE**
> >
> Due to complexity of cryptographic algorithms, the implementation varies depending on the key specifications and parameters you use, and cannot be enumerated by sample code. Before you start, understand the APIs to ensure correct use of these APIs. > Due to complexity of cryptographic algorithms, the implementation varies depending on the key specifications and parameters you use, and cannot be enumerated by sample code. Before you start, understand the APIs to ensure correct use of these APIs.
...@@ -433,9 +540,9 @@ The following table describes the APIs used in the typical encryption and decryp ...@@ -433,9 +540,9 @@ The following table describes the APIs used in the typical encryption and decryp
|Instance|API|Description| |Instance|API|Description|
|---|---|---| |---|---|---|
|cryptoFramework|createCipher(transformation : string) : Cipher|Creates a **Cipher** instance based on the algorithm specified by **transformation**.| |cryptoFramework|createCipher(transformation : string) : Cipher|Creates a **Cipher** instance.|
|Cipher|init(opMode : CryptoMode, key : Key, params : ParamsSpec, callback : AsyncCallback\<void>) : void|Initializes the **Cipher** instance. This API uses an asynchronous callback to return the result.| |Cipher|init(opMode : CryptoMode, key : Key, params : ParamsSpec, callback : AsyncCallback\<void>) : void|Sets a key and initializes the **Cipher** instance. This API uses an asynchronous callback to return the result.|
|Cipher|init(opMode : CryptoMode, key : Key, params : ParamsSpec) : Promise\<void>|Initializes the **Cipher** instance. This API uses a promise to return the result.| |Cipher|init(opMode : CryptoMode, key : Key, params : ParamsSpec) : Promise\<void>|Sets a key and initializes the **Cipher** instance. This API uses a promise to return the result.|
|Cipher|update(data : DataBlob, callback : AsyncCallback\<DataBlob>) : void|Updates the data for encryption and decryption. This API uses an asynchronous callback to return the result.| |Cipher|update(data : DataBlob, callback : AsyncCallback\<DataBlob>) : void|Updates the data for encryption and decryption. This API uses an asynchronous callback to return the result.|
|Cipher|update(data : DataBlob) : Promise\<DataBlob>|Updates the data for encryption and decryption. This API uses a promise to return the result.| |Cipher|update(data : DataBlob) : Promise\<DataBlob>|Updates the data for encryption and decryption. This API uses a promise to return the result.|
|Cipher|doFinal(data : DataBlob, callback : AsyncCallback\<DataBlob>) : void|Finalizes the encryption or decryption. This API uses an asynchronous callback to return the result.| |Cipher|doFinal(data : DataBlob, callback : AsyncCallback\<DataBlob>) : void|Finalizes the encryption or decryption. This API uses an asynchronous callback to return the result.|
...@@ -461,19 +568,19 @@ var globalKey; ...@@ -461,19 +568,19 @@ var globalKey;
var globalCipherText; var globalCipherText;
function genGcmParamsSpec() { function genGcmParamsSpec() {
let arr = [0, 0, 0, 0 , 0, 0, 0, 0, 0, 0 , 0, 0]; // 12 bytes let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 12 bytes
let dataIv = new Uint8Array(arr); let dataIv = new Uint8Array(arr);
let ivBlob = {data : dataIv}; let ivBlob = { data: dataIv };
arr = [0, 0, 0, 0 , 0, 0, 0, 0]; // 8 bytes arr = [0, 0, 0, 0, 0, 0, 0, 0]; // 8 bytes
let dataAad = new Uint8Array(arr); let dataAad = new Uint8Array(arr);
let aadBlob = {data : dataAad}; let aadBlob = { data: dataAad };
arr = [0, 0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0]; // 16 bytes arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 16 bytes
let dataTag = new Uint8Array(arr); let dataTag = new Uint8Array(arr);
let tagBlob = {data : dataTag}; // The authTag of GCM is obtained by doFinal() in encryption and passed in params of init() in decryption. let tagBlob = { data: dataTag }; // The GCM authTag is obtained by doFinal() in encryption and passed in params of init() in decryption.
let gcmParamsSpec = {iv : ivBlob, aad : aadBlob, authTag : tagBlob, algName : "GcmParamsSpec"}; let gcmParamsSpec = { iv: ivBlob, aad: aadBlob, authTag: tagBlob, algName: "GcmParamsSpec" };
return gcmParamsSpec; return gcmParamsSpec;
} }
...@@ -533,45 +640,45 @@ function testAesGcm() { ...@@ -533,45 +640,45 @@ function testAesGcm() {
} }
return promiseSymKey; return promiseSymKey;
}).then(key => { }).then(key => {
let encodedKey = key.getEncoded(); let encodedKey = key.getEncoded();
console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data)); console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data));
globalKey = key; globalKey = key;
return key; return key;
}).then(key => { }).then(key => {
// Initialize the cipher environment and start encryption. // Initialize the Cipher instance and start encryption.
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE; let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
let promiseInit = globalCipher.init(mode, key, globalGcmParams); // init let promiseInit = globalCipher.init(mode, key, globalGcmParams); // init
return promiseInit; return promiseInit;
}).then(() => { }).then(() => {
let plainText = {data : stringToUint8Array('this is test!')}; let plainText = {data : stringToUint8Array('this is test!')};
let promiseUpdate = globalCipher.update(plainText); // update let promiseUpdate = globalCipher.update(plainText); // update
return promiseUpdate; return promiseUpdate;
}).then(updateOutput => { }).then(updateOutput => {
globalCipherText = updateOutput; globalCipherText = updateOutput;
let promiseFinal = globalCipher.doFinal(null); // doFinal let promiseFinal = globalCipher.doFinal(null); // doFinal
return promiseFinal; return promiseFinal;
}).then(authTag => { }).then(authTag => {
// In GCM mode, the encrypted authentication information needs to be obtained from the output of doFinal() and passed in globalGcmParams of init() in decryption. // In GCM mode, the encrypted authentication information needs to be obtained from the output of doFinal() and passed in globalGcmParams of init() in decryption.
globalGcmParams.authTag = authTag; globalGcmParams.authTag = authTag;
return; return;
}).then(() => { }).then(() => {
// Initialize the Cipher instance and start decryption. // Initialize the Cipher instance and start decryption.
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE; let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
let promiseInit = globalCipher.init(mode, globalKey, globalGcmParams); // init let promiseInit = globalCipher.init(mode, globalKey, globalGcmParams); // init
return promiseInit; return promiseInit;
}).then(() => { }).then(() => {
let promiseUpdate = globalCipher.update(globalCipherText); // update let promiseUpdate = globalCipher.update(globalCipherText); // update
return promiseUpdate; return promiseUpdate;
}).then(updateOutput => { }).then(updateOutput => {
console.info('decrypt plainText: ' + uint8ArrayToString(updateOutput.data)); console.info('decrypt plainText: ' + uint8ArrayToString(updateOutput.data));
let promiseFinal = globalCipher.doFinal(null); // doFinal let promiseFinal = globalCipher.doFinal(null); // doFinal
return promiseFinal; return promiseFinal;
}).then(finalOutput => { }).then(finalOutput => {
if (finalOutput == null) { // Check whether the result is null before using finalOutput.data. if (finalOutput == null) { // Check whether the result is null before using finalOutput.data.
console.info('GCM finalOutput is null'); console.info('GCM finalOutput is null');
} }
}).catch(error => { }).catch(error => {
console.error(`catch error, ${error.code}, ${error.message}`); console.error(`catch error, ${error.code}, ${error.message}`);
}) })
} }
``` ```
...@@ -583,7 +690,7 @@ Encrypt and decrypt data using a 3DES symmetric key. ...@@ -583,7 +690,7 @@ Encrypt and decrypt data using a 3DES symmetric key.
1. Create a **SymKeyGenerator** instance. 1. Create a **SymKeyGenerator** instance.
2. Generate a key based on the existing binary data. 2. Generate a key based on the existing binary data.
3. Create a **Cipher** instance. 3. Create a **Cipher** instance.
4. Encrypt or decrypt data using the **Cipher** instance. 4. Encrypt or decrypt data.
```js ```js
import cryptoFramework from '@ohos.security.cryptoFramework'; import cryptoFramework from '@ohos.security.cryptoFramework';
...@@ -665,8 +772,8 @@ function test3DesEcb() { ...@@ -665,8 +772,8 @@ function test3DesEcb() {
// Initialize the Cipher instance and start encryption. // Initialize the Cipher instance and start encryption.
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE; let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
// init // init
globalCipher.init(mode, key, null, (err, ) => { globalCipher.init(mode, key, null, (err,) => {
let plainText = {data : stringToUint8Array('this is test!')}; let plainText = { data: stringToUint8Array('this is test!') };
// update // update
globalCipher.update(plainText, (err, updateOutput) => { globalCipher.update(plainText, (err, updateOutput) => {
globalCipherText = updateOutput; globalCipherText = updateOutput;
...@@ -681,19 +788,19 @@ function test3DesEcb() { ...@@ -681,19 +788,19 @@ function test3DesEcb() {
finalOutput = Array.from(finalOutput.data); finalOutput = Array.from(finalOutput.data);
globalCipherText = globalCipherText.concat(finalOutput); globalCipherText = globalCipherText.concat(finalOutput);
globalCipherText = new Uint8Array(globalCipherText); globalCipherText = new Uint8Array(globalCipherText);
globalCipherText = {data : globalCipherText}; globalCipherText = { data: globalCipherText };
} }
// Initialize the Cipher instance and start decryption. // Initialize the Cipher instance and start decryption.
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE; let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
// init // init
globalCipher.init(mode, globalKey, null, (err, ) => { globalCipher.init(mode, globalKey, null, (err,) => {
// update // update
globalCipher.update(globalCipherText, (err, updateOutput) => { globalCipher.update(globalCipherText, (err, updateOutput) => {
console.info('decrypt plainText: ' + uint8ArrayToString(updateOutput.data)); console.info('decrypt plainText: ' + uint8ArrayToString(updateOutput.data));
// doFinal // doFinal
globalCipher.doFinal(null, (error, finalOutput) => { globalCipher.doFinal(null, (error, finalOutput) => {
if (finalOutput == null) { // Check whether the result is null before using finalOutput.data. if (finalOutput != null) { // Check whether the result is null before using finalOutput.data.
console.info("decrypt plainText:" + uint8ArrayToString(finalOutput.data)); console.info('decrypt plainText: ' + uint8ArrayToString(finalOutput.data));
} }
}) })
}) })
...@@ -791,72 +898,82 @@ function testAesMultiUpdate() { ...@@ -791,72 +898,82 @@ function testAesMultiUpdate() {
return; return;
} }
return promiseSymKey; return promiseSymKey;
}).then(key => { })
let encodedKey = key.getEncoded(); .then(key => {
console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data)); let encodedKey = key.getEncoded();
globalKey = key; console.info('key hex:' + uint8ArrayToShowStr(encodedKey.data));
return key; globalKey = key;
}).then(key => { return key;
// Initialize the cipher environment and start encryption. })
let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE; .then(key => {
let promiseInit = globalCipher.init(mode, key, globalGcmParams); // init // Initialize the Cipher instance and start encryption.
return promiseInit; let mode = cryptoFramework.CryptoMode.ENCRYPT_MODE;
}).then(async () => { let promiseInit = globalCipher.init(mode, key, globalGcmParams); // init
let plainText = "aaaaa.....bbbbb.....ccccc.....ddddd.....eee"; // Assume that the plaintext contains 43 bytes. return promiseInit;
let messageArr = []; })
let updateLength = 20; // Pass in 20 bytes by update() each time. .then(async () => {
globalCipherText = []; let plainText = "aaaaa.....bbbbb.....ccccc.....ddddd.....eee"; // Assume that the plaintext is of 43 bytes.
let messageArr = [];
for (let i = 0; i <= plainText.length; i++) { let updateLength = 20; // Pass in 20 bytes by update() each time.
if ((i % updateLength == 0 || i == plainText.length) && messageArr.length != 0) { globalCipherText = [];
for (let i = 0; i <= plainText.length; i++) {
if ((i % updateLength == 0 || i == plainText.length) && messageArr.length != 0) {
let message = new Uint8Array(messageArr);
let messageBlob = { data: message };
let updateOutput = await globalCipher.update(messageBlob); // Update by segment.
// Combine the result of each update() to obtain the ciphertext. In certain cases, the doFinal() results need to be combined, which depends on the cipher block mode
// and padding mode you use. In this example, the doFinal() result in GCM mode contains authTag but not ciphertext. Therefore, there is no need to combine the results.
globalCipherText = globalCipherText.concat(Array.from(updateOutput.data));
messageArr = [];
}
if (i < plainText.length) {
messageArr.push(plainText.charCodeAt(i));
}
}
return;
})
.then(() => {
let promiseFinal = globalCipher.doFinal(null); // doFinal
return promiseFinal;
})
.then(authTag => {
// Obtain the authentication information after encryption.
globalGcmParams.authTag = authTag;
return;
})
.then(() => {
// Initialize the Cipher instance and start decryption.
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
let promiseInit = globalCipher.init(mode, globalKey, globalGcmParams); // init
return promiseInit;
})
.then(async () => {
let updateLength = 20;
let updateTimes = Math.ceil(globalCipherText.length / updateLength); // Round up to the nearest integer.
globalPlainText = "";
for (let i = 0; i < updateTimes; i++) {
let messageArr = globalCipherText.slice(i * updateLength, (i + 1) * updateLength);
let message = new Uint8Array(messageArr); let message = new Uint8Array(messageArr);
let messageBlob = { data : message }; let messageBlob = { data: message };
let updateOutput = await globalCipher.update(messageBlob); // Update by segment. let updateOutput = await globalCipher.update(messageBlob); // Update by segment.
// Combine the result of each update() to obtain the ciphertext. In certain cases, the doFinal() results need to be combined, which depends on the cipher block mode globalPlainText += uint8ArrayToString(updateOutput.data); // Restore the original plaintext.
// and padding mode you use. In this example, the doFinal() result in GCM mode contains authTag but not ciphertext. Therefore, there is no need to combine the results.
globalCipherText = globalCipherText.concat(Array.from(updateOutput.data));
messageArr = [];
} }
if (i < plainText.length) { return;
messageArr.push(plainText.charCodeAt(i)); })
.then(() => {
let promiseFinal = globalCipher.doFinal(null); // doFinal
return promiseFinal;
})
.then(finalOutput => {
if (finalOutput == null) {
console.info('GCM finalOutput is null');
} }
} console.info(`decrypt output: ${globalPlainText}`);
return; })
}).then(() => { .catch(error => {
let promiseFinal = globalCipher.doFinal(null); // doFinal
return promiseFinal;
}).then(authTag => {
// Obtain the authentication information after encryption.
globalGcmParams.authTag = authTag;
return;
}).then(() => {
// Initialize the Cipher instance and start decryption.
let mode = cryptoFramework.CryptoMode.DECRYPT_MODE;
let promiseInit = globalCipher.init(mode, globalKey, globalGcmParams); // init
return promiseInit;
}).then(async () => {
let updateLength = 20;
let updateTimes = Math.ceil(globalCipherText.length / updateLength); // Round up to the nearest integer.
globalPlainText = "";
for (let i = 0; i < updateTimes; i++) {
let messageArr = globalCipherText.slice(i * updateLength, (i + 1) * updateLength);
let message = new Uint8Array(messageArr);
let messageBlob = { data : message };
let updateOutput = await globalCipher.update(messageBlob); // Pass in data by segment.
globalPlainText += uint8ArrayToString(updateOutput.data); // Restore the original plaintext.
}
return;
}).then(() => {
let promiseFinal = globalCipher.doFinal(null); // doFinal
return promiseFinal;
}).then(finalOutput => {
if (finalOutput == null) {
console.info('GCM finalOutput is null');
}
console.info(`decrypt output: ${globalPlainText}`);
}).catch(error => {
console.error(`catch error, ${error.code}, ${error.message}`); console.error(`catch error, ${error.code}, ${error.message}`);
}) })
} }
``` ```
...@@ -896,7 +1013,7 @@ function encryptMessagePromise() { ...@@ -896,7 +1013,7 @@ function encryptMessagePromise() {
return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null); return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null);
}).then(() => { }).then(() => {
// doFinal // doFinal
let input = { data : stringToUint8Array(plan) }; let input = { data: stringToUint8Array(plan) };
return cipher.doFinal(input); return cipher.doFinal(input);
}).then(dataBlob => { }).then(dataBlob => {
// Obtain the encrypted data. // Obtain the encrypted data.
...@@ -915,7 +1032,7 @@ function encryptMessageCallback() { ...@@ -915,7 +1032,7 @@ function encryptMessageCallback() {
let pubKey = keyPair.pubKey; let pubKey = keyPair.pubKey;
// Initialize the Cipher instance and use the public key to encrypt the data. // Initialize the Cipher instance and use the public key to encrypt the data.
cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null, function (err, data) { cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null, function (err, data) {
let input = {data : stringToUint8Array(plan) }; let input = { data: stringToUint8Array(plan) };
// doFinal // doFinal
cipher.doFinal(input, function (err, data) { cipher.doFinal(input, function (err, data) {
// Obtain the encrypted data. // Obtain the encrypted data.
...@@ -937,32 +1054,36 @@ function decryptMessagePromise() { ...@@ -937,32 +1054,36 @@ function decryptMessagePromise() {
let keyGenPromise = rsaGenerator.generateKeyPair(); let keyGenPromise = rsaGenerator.generateKeyPair();
let keyPair; let keyPair;
let cipherDataBlob; let cipherDataBlob;
let input = { data : stringToUint8Array(plan) }; let input = { data: stringToUint8Array(plan) };
keyGenPromise.then(rsaKeyPair => { keyGenPromise.then(rsaKeyPair => {
keyPair = rsaKeyPair; keyPair = rsaKeyPair;
// Initialize the Cipher instance and use the public key to encrypt the message. // Initialize the Cipher instance and use the public key to encrypt the message.
return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null); return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null);
}).then(() => { })
// Call doFinal() to encrypt data. .then(() => {
return cipher.doFinal(input); // Call doFinal() to encrypt data.
}).then(dataBlob => { return cipher.doFinal(input);
// Obtain the encrypted information and use it as the input parameter for decryption. })
console.info("EncryptOutPut is " + dataBlob.data); .then(dataBlob => {
AlertDialog.show({message : "output" + dataBlob.data}); // Obtain the encrypted information and use it as the input parameter for decryption.
cipherDataBlob = dataBlob; console.info("EncryptOutPut is " + dataBlob.data);
AlertDialog.show({ message: "output" + dataBlob.data });
cipherDataBlob = dataBlob;
// Initialize the Cipher instance and use the private key to decrypt the message. // Initialize the Cipher instance and use the private key to decrypt the message.
return decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null); return decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null);
}).then(() => { })
// Call doFinal() to decrypt the message. .then(() => {
return decoder.doFinal(cipherDataBlob); // Call doFinal() to decrypt the message.
}).then(decodeData => { return decoder.doFinal(cipherDataBlob);
// Check whether the decrypted data is consistent with the original data. })
if (decodeData.data.toString() === input.data.toString()) { .then(decodeData => {
AlertDialog.show({message : "decrypt success"}); // Check whether the decrypted data is consistent with the original data.
return; if (decodeData.data.toString() === input.data.toString()) {
} AlertDialog.show({ message: "decrypt success" });
AlertDialog.show({message : "decrypt fail"}); return;
}); }
AlertDialog.show({ message: "decrypt fail" });
});
} }
// Encrypt and decrypt the message in callback mode. // Encrypt and decrypt the message in callback mode.
...@@ -974,7 +1095,7 @@ function decryptMessageCallback() { ...@@ -974,7 +1095,7 @@ function decryptMessageCallback() {
// Create a Cipher instance for decryption. // Create a Cipher instance for decryption.
let decoder = cryptoFramework.createCipher("RSA1024|PKCS1"); let decoder = cryptoFramework.createCipher("RSA1024|PKCS1");
let plainText = "this is cipher text"; let plainText = "this is cipher text";
let input = {data : stringToUint8Array(plainText) }; let input = { data: stringToUint8Array(plainText) };
let cipherData; let cipherData;
let keyPair; let keyPair;
// Generate an asymmetric key pair using the AsyKeyGenerator instance. // Generate an asymmetric key pair using the AsyKeyGenerator instance.
...@@ -985,7 +1106,7 @@ function decryptMessageCallback() { ...@@ -985,7 +1106,7 @@ function decryptMessageCallback() {
// Call doFinal() to encrypt the message. // Call doFinal() to encrypt the message.
cipher.doFinal(input, function (err, data) { cipher.doFinal(input, function (err, data) {
// Obtain the encrypted information and use it as the input parameter for decryption. // Obtain the encrypted information and use it as the input parameter for decryption.
AlertDialog.show({ message : "EncryptOutPut is " + data.data} ); AlertDialog.show({ message: "EncryptOutPut is " + data.data });
cipherData = data; cipherData = data;
// Initialize the Cipher instance and use the private key to decrypt the message. // Initialize the Cipher instance and use the private key to decrypt the message.
decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null, function (err, data) { decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null, function (err, data) {
...@@ -993,10 +1114,10 @@ function decryptMessageCallback() { ...@@ -993,10 +1114,10 @@ function decryptMessageCallback() {
decoder.doFinal(cipherData, function (err, data) { decoder.doFinal(cipherData, function (err, data) {
// Check whether the decrypted data is consistent with the original data. // Check whether the decrypted data is consistent with the original data.
if (input.data.toString() === data.data.toString()) { if (input.data.toString() === data.data.toString()) {
AlertDialog.show({ message : "decryption success"} ); AlertDialog.show({ message: "decrype success" });
return; return;
} }
AlertDialog.show({ message : "decryption fail"} ); AlertDialog.show({ message: "decrype fail" });
}); });
}); });
}); });
...@@ -1124,9 +1245,15 @@ function stringToUint8Array(str) { ...@@ -1124,9 +1245,15 @@ function stringToUint8Array(str) {
} }
// Construct the key parameters of the RSA asymmetric key pair based on the key pair specifications. // Construct the key parameters of the RSA asymmetric key pair based on the key pair specifications.
function genRsaKeyPairSpec(nIn : bigint, eIn : bigint, dIn : bigint) { function genRsaKeyPairSpec(nIn: bigint, eIn: bigint, dIn: bigint) {
let rsaCommSpec = { n : nIn, algName : "RSA", specType : cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC }; let rsaCommSpec = { n: nIn, algName: "RSA", specType: cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC };
let rsaKeyPairSpec = { params: rsaCommSpec, sk : dIn, pk : eIn, algName : "RSA", specType : cryptoFramework.AsyKeySpecType.KEY_PAIR_SPEC }; let rsaKeyPairSpec = {
params: rsaCommSpec,
sk: dIn,
pk: eIn,
algName: "RSA",
specType: cryptoFramework.AsyKeySpecType.KEY_PAIR_SPEC
};
return rsaKeyPairSpec; return rsaKeyPairSpec;
} }
...@@ -1150,62 +1277,292 @@ function rsaUseSpecDecryptOAEPPromise() { ...@@ -1150,62 +1277,292 @@ function rsaUseSpecDecryptOAEPPromise() {
let keyPair; let keyPair;
let cipherDataBlob; let cipherDataBlob;
// Set the pSource, which defines the encoding input P filled by OAEP. // Set the pSource, which defines the encoding input P filled by OAEP.
let pSource = new Uint8Array([1,2,3,4]); let pSource = new Uint8Array([1, 2, 3, 4]);
let input = { data : stringToUint8Array(plan) }; let input = { data: stringToUint8Array(plan) };
// Generate the key pair. // Generate the key pair.
keyGenPromise.then(rsaKeyPair => { keyGenPromise.then(rsaKeyPair => {
keyPair = rsaKeyPair; keyPair = rsaKeyPair;
// Initialize the Cipher instance for encryption. // Initialize the Cipher instance for encryption.
return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null); return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null);
})
.then(() => {
// Set and obtain the cipher specifications after the initialization.
cipher.setCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR, pSource);
let retP = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR);
// Check whether the obtained PSource is the same as the PSource set.
if (retP.toString() != pSource.toString()) {
AlertDialog.show({ message: "error init pSource" + retP });
} else {
console.info("pSource changed ==" + retP);
}
// Obtain other OAEP parameters.
let md = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MD_NAME_STR);
console.info("md == " + md);
let mgf = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF_NAME_STR);
console.info("mgf == " + mgf);
let mgf1Md = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_MD_STR);
console.info("mgf1Md == " + mgf1Md);
return cipher.doFinal(input);
})
.then(dataBlob => {
console.info("EncryptOutPut is " + dataBlob.data);
cipherDataBlob = dataBlob;
// The get() and set() operations can be performed before the init() operation of the Cipher object and are equivalent to those after the init() operation. For example, set and get the decoder.
decoder.setCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR, pSource);
let retP = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR);
// Check whether the obtained PSource is the same as the PSource set.
if (retP.toString() != pSource.toString()) {
AlertDialog.show({ message: "error init pSource" + retP });
} else {
console.info("pSource changed ==" + retP);
}
// Obtain other OAEP parameters.
let md = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MD_NAME_STR);
console.info("md == " + md);
let mgf = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF_NAME_STR);
console.info("mgf == " + mgf);
let mgf1Md = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_MD_STR);
console.info("mgf1Md == " + mgf1Md);
// Initialize the decryption operation.
return decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null);
})
.then(() => {
return decoder.doFinal(cipherDataBlob);
})
.then(decodeData => {
// The decryption is successful.
if (decodeData.data.toString() === input.data.toString()) {
console.info("oaep decrypt success");
AlertDialog.show({ message: " oaep decrypt success" });
} else {
AlertDialog.show({ message: "oeap decrypt fail" });
}
});
}
```
### Encrypting and Decrypting Data Using SM2
> **NOTE**
>
> SM2 encryption and decryption are supported from API version 10.
Use an SM2 asymmetric key pair to encrypt and decrypt data.
1. Generate an SM2 key pair. Call **createAsyKeyGenerator()** to create an **AsyKeyGenerator** instance and generate an SM2 asymmetric key pair.
2. Create a **Cipher** instance.<br>Call **createCipher()** to create a **Cipher** instance, and set the key and encryption/decryption mode.
3. Encrypt and decrypt data.<br>Call **doFinal()** provided by the **Cipher** instance to encrypt data or decrypt data.
```js
import cryptoFramework from "@ohos.security.cryptoFramework"
let plan = "This is cipher test.";
// Convert strings in plaintext into byte streams.
function stringToUint8Array(str) {
let arr = [];
for (let i = 0, j = str.length; i < j; ++i) {
arr.push(str.charCodeAt(i));
}
return new Uint8Array(arr);
}
// Encrypt the message in promise mode.
function encryptMessagePromise() {
// Create an AsyKeyGenerator instance.
let sm2Generator = cryptoFramework.createAsyKeyGenerator("SM2_256");
// Create a Cipher instance.
let cipher = cryptoFramework.createCipher("SM2_256|SM3");
// Generate an asymmetric key pair using the AsyKeyGenerator instance.
let keyGenPromise = sm2Generator.generateKeyPair();
keyGenPromise.then(sm2KeyPair => {
let pubKey = sm2KeyPair.pubKey;
// Initialize the Cipher instance and use the public key to encrypt the message.
return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null);
}).then(() => { }).then(() => {
// Set and obtain the cipher specifications after the initialization. // doFinal
cipher.setCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR, pSource); let input = { data: stringToUint8Array(plan) };
let retP = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR);
// Check whether the obtained PSource is the same as the PSource set.
if (retP.toString() != pSource.toString()) {
AlertDialog.show({message : "error init pSource" + retP});
} else {
console.info("pSource changed ==" + retP);
}
// Obtain other OAEP parameters.
let md = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MD_NAME_STR);
console.info("md == " + md);
let mgf = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF_NAME_STR);
console.info("mgf == " + mgf);
let mgf1Md = cipher.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_MD_STR);
console.info("mgf1Md == " + mgf1Md);
return cipher.doFinal(input); return cipher.doFinal(input);
}).then(dataBlob => { }).then(dataBlob => {
// Obtain the encrypted data.
console.info("EncryptOutPut is " + dataBlob.data); console.info("EncryptOutPut is " + dataBlob.data);
cipherDataBlob = dataBlob; });
// The get() and set() operations can be performed before the init() operation of the Cipher object and are equivalent to those after the init() operation. For example, set and get the decoder. }
decoder.setCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR, pSource);
let retP = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_PSRC_UINT8ARR); // Encrypt the message in callback mode.
// Check whether the obtained PSource is the same as the PSource set. function encryptMessageCallback() {
if (retP.toString() != pSource.toString()) { // Create an AsyKeyGenerator instance.
AlertDialog.show({message : "error init pSource" + retP}); let sm2Generator = cryptoFramework.createAsyKeyGenerator("SM2_256");
} else { // Create a Cipher instance.
console.info("pSource changed ==" + retP); let cipher = cryptoFramework.createCipher("SM2_256|SM3");
} // Generate an asymmetric key pair using the AsyKeyGenerator instance.
// Obtain other OAEP parameters. sm2Generator.generateKeyPair(function (err, keyPair) {
let md = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MD_NAME_STR); let pubKey = keyPair.pubKey;
console.info("md == " + md); // Initialize the Cipher instance and use the public key to encrypt the message.
let mgf = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF_NAME_STR); cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null, function (err, data) {
console.info("mgf == " + mgf); let input = { data: stringToUint8Array(plan) };
let mgf1Md = decoder.getCipherSpec(cryptoFramework.CipherSpecItem.OAEP_MGF1_MD_STR); // doFinal
console.info("mgf1Md == " + mgf1Md); cipher.doFinal(input, function (err, data) {
// Initialize the decryption operation. // Obtain the encrypted data.
return decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null); console.info("EncryptOutPut is " + data.data);
}).then(() => { })
return decoder.doFinal(cipherDataBlob); })
}).then(decodeData => { })
// The decryption is successful. }
if (decodeData.data.toString() === input.data.toString()) {
console.info("oaep decrypt success"); // Encrypt and decrypt data in promise mode.
AlertDialog.show({message : " oaep decrypt success"}); function decryptMessagePromise() {
} else { // Create an AsyKeyGenerator instance.
AlertDialog.show({message : "oeap decrypt fail"}); let sm2Generator = cryptoFramework.createAsyKeyGenerator("SM2_256");
} // Create a Cipher instance for encryption.
let cipher = cryptoFramework.createCipher("SM2_256|SM3");
// Create a Cipher instance for decryption.
let decoder = cryptoFramework.createCipher("SM2_256|SM3");
// Generate an asymmetric key pair using the AsyKeyGenerator instance.
let keyGenPromise = sm2Generator.generateKeyPair();
let keyPair;
let cipherDataBlob;
let input = { data: stringToUint8Array(plan) };
keyGenPromise.then(rsaKeyPair => {
keyPair = rsaKeyPair;
// Initialize the Cipher instance and use the public key to encrypt the data.
return cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null);
})
.then(() => {
// Call doFinal() to encrypt data.
return cipher.doFinal(input);
})
.then(dataBlob => {
// Obtain the encrypted information and use it as the input parameter for decryption.
console.info("EncryptOutPut is " + dataBlob.data);
AlertDialog.show({ message: "output" + dataBlob.data });
cipherDataBlob = dataBlob;
// Initialize the Cipher instance and use the private key to decrypt the data.
return decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null);
})
.then(() => {
// Call doFinal() to decrypt data.
return decoder.doFinal(cipherDataBlob);
})
.then(decodeData => {
// Check whether the decrypted data is consistent with the original data.
if (decodeData.data.toString() === input.data.toString()) {
AlertDialog.show({ message: "decrypt success" });
return;
}
AlertDialog.show({ message: "decrypt fail" });
});
}
// Encrypt and decrypt data in callback mode.
function decryptMessageCallback() {
// Create an AsyKeyGenerator instance.
let sm2Generator = cryptoFramework.createAsyKeyGenerator("SM2_256");
// Create a Cipher instance for encryption.
let cipher = cryptoFramework.createCipher("SM2_256|SM3");
// Create a Cipher instance for decryption.
let decoder = cryptoFramework.createCipher("SM2_256|SM3");
let plainText = "this is cipher text";
let input = { data: stringToUint8Array(plainText) };
let cipherData;
let keyPair;
// Generate an asymmetric key pair using the AsyKeyGenerator instance.
sm2Generator.generateKeyPair(function (err, newKeyPair) {
keyPair = newKeyPair;
// Initialize the Cipher instance and use the public key to encrypt the data.
cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null, function (err, data) {
// Call doFinal() to encrypt data.
cipher.doFinal(input, function (err, data) {
// Obtain the encrypted information and use it as the input parameter for decryption.
AlertDialog.show({ message: "EncryptOutPut is " + data.data });
cipherData = data;
// Initialize the Cipher instance and use the private key to decrypt the data.
decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null, function (err, data) {
// Call doFinal() to decrypt data.
decoder.doFinal(cipherData, function (err, data) {
// Check whether the decrypted data is consistent with the original data.
if (input.data.toString() === data.data.toString()) {
AlertDialog.show({ message: "decrype success" });
return;
}
AlertDialog.show({ message: "decrype fail" });
});
});
});
});
});
}
```
### Encrypting and Decrypting Data Using an SM4 ECB Symmetric Key (Callback)
> **NOTE**
>
> SM4 encryption and decryption are supported from API version 10.
Use an SM4 symmetric key to encrypt and decrypt data.
1. Create a **SymKeyGenerator** instance.
2. Generate a key based on the existing binary data.
3. Create a **Cipher** instance.
4. Encrypt or decrypt data.
```js
import cryptoFramework from '@ohos.security.cryptoFramework';
function stringToUint8Array(str) {
let arr = [];
for (let i = 0, j = str.length; i < j; ++i) {
arr.push(str.charCodeAt(i));
}
return new Uint8Array(arr);
}
// Convert byte streams into strings in plaintext.
function uint8ArrayToString(array) {
let arrayString = '';
for (let i = 0; i < array.length; i++) {
arrayString += String.fromCharCode(array[i]);
}
return arrayString;
}
// Use SM4 ECB mode as an example. Callback-based APIs are used.
function testSM4Ecb() {
// Create an AsyKeyGenerator instance.
let sm4Generator = cryptoFramework.createSymKeyGenerator('SM4_128');
// Create a Cipher instance for encryption.
let cipher = cryptoFramework.createCipher("SM4_128|ECB|PKCS7");
// Create a Cipher instance for decryption.
let decoder = cryptoFramework.createCipher("SM4_128|ECB|PKCS7");
let plainText = "this is cipher text";
let input = { data: stringToUint8Array(plainText) };
let cipherData;
let key;
// Generate an asymmetric key pair using the AsyKeyGenerator instance.
sm4Generator.generateSymKey(function (err, newKey) {
key = newKey;
// Initialize the Cipher instance and use the public key to encrypt the data.
cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, null, function (err, data) {
// Call doFinal() to encrypt data.
cipher.doFinal(input, function (err, data) {
// Obtain the encrypted information and use it as the input parameter for decryption.
AlertDialog.show({ message: "EncryptOutPut is " + data.data });
cipherData = data;
// Initialize the Cipher instance and use the private key to decrypt the data.
decoder.init(cryptoFramework.CryptoMode.DECRYPT_MODE, key, null, function (err, data) {
// Call doFinal() to decrypt data.
decoder.doFinal(cipherData, function (err, data) {
// Check whether the decrypted data is consistent with the original data.
if (input.data.toString() === data.data.toString()) {
AlertDialog.show({ message: "decrype success" });
return;
}
AlertDialog.show({ message: "decrype fail" });
});
});
});
});
}); });
} }
``` ```
...@@ -1218,6 +1575,7 @@ A digital signature can be used to verify the authenticity of a message. Typical ...@@ -1218,6 +1575,7 @@ A digital signature can be used to verify the authenticity of a message. Typical
1. Use RSA to generate a signature and verify the signature. 1. Use RSA to generate a signature and verify the signature.
2. Use ECC to generate a signature and verify the signature. 2. Use ECC to generate a signature and verify the signature.
3. Use RSA to generate a signature and verify the signature. Obtain and set **SignSpecItem** when the PSS padding mode is used. 3. Use RSA to generate a signature and verify the signature. Obtain and set **SignSpecItem** when the PSS padding mode is used.
4. Use SM2 to generate a signature and verify the signature.
> **NOTE** > **NOTE**
> >
...@@ -1227,7 +1585,6 @@ A digital signature can be used to verify the authenticity of a message. Typical ...@@ -1227,7 +1585,6 @@ A digital signature can be used to verify the authenticity of a message. Typical
### Available APIs ### Available APIs
The following table describes the APIs used in typical signing and signature verification operations. For more information about the APIs, see [Crypto Framework](../reference/apis/js-apis-cryptoFramework.md). The following table describes the APIs used in typical signing and signature verification operations. For more information about the APIs, see [Crypto Framework](../reference/apis/js-apis-cryptoFramework.md).
> **NOTE** > **NOTE**
> >
> Due to complexity of cryptographic algorithms, the implementation varies depending on the specifications and parameters you use, and cannot be enumerated by sample code. Before you start, understand the APIs to ensure correct use of these APIs. > Due to complexity of cryptographic algorithms, the implementation varies depending on the specifications and parameters you use, and cannot be enumerated by sample code. Before you start, understand the APIs to ensure correct use of these APIs.
...@@ -1261,7 +1618,7 @@ Use RSA to sign data and verify the signature. ...@@ -1261,7 +1618,7 @@ Use RSA to sign data and verify the signature.
2. Create a **Sign** instance.<br>Call **createSign()** to create a **Sign** instance, initialize the **Sign** instance, and set a private key for signing. 2. Create a **Sign** instance.<br>Call **createSign()** to create a **Sign** instance, initialize the **Sign** instance, and set a private key for signing.
3. Generate a signature.<br>Call **update()** provided by the **Sign** class to pass in the data for signing and call **sign()** to generate a signature. 3. Generate a signature.<br>Call **update()** provided by the **Sign** class to pass in the data for signing and call **sign()** to generate a signature.
4. Create a **Verify** instance.<br>Call **createVerify()** to create a **Verify** instance, initialize the instance, and set a public key for signature verification. 4. Create a **Verify** instance.<br>Call **createVerify()** to create a **Verify** instance, initialize the instance, and set a public key for signature verification.
5. Verify the signature.<br> Call **update()** provided by the **Verify** class to pass in the signature data and call **verify()** to verify the signature. 5. Verify the signature.<br>Call **update()** provided by the **Verify** class to pass in the signature data and call **verify()** to verify the signature.
```js ```js
import cryptoFramework from "@ohos.security.cryptoFramework" import cryptoFramework from "@ohos.security.cryptoFramework"
...@@ -1279,14 +1636,14 @@ let globalKeyPair; ...@@ -1279,14 +1636,14 @@ let globalKeyPair;
let SignMessageBlob; let SignMessageBlob;
let plan1 = "This is Sign test plan1"; let plan1 = "This is Sign test plan1";
let plan2 = "This is Sign test plan1"; let plan2 = "This is Sign test plan1";
let input1 = { data : stringToUint8Array(plan1) }; let input1 = { data: stringToUint8Array(plan1) };
let input2 = { data : stringToUint8Array(plan2) }; let input2 = { data: stringToUint8Array(plan2) };
function signMessagePromise() { function signMessagePromise() {
let rsaGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024|PRIMES_2"); let rsaGenerator = cryptoFramework.createAsyKeyGenerator("RSA1024|PRIMES_2");
let signer = cryptoFramework.createSign("RSA1024|PKCS1|SHA256"); // From API version 10, a Sign instance can be created by specifying a string parameter defining the key specifications. let signer = cryptoFramework.createSign("RSA1024|PKCS1|SHA256"); // From API version 10, a Sign instance can be created by specifying a string parameter defining the key specifications.
let keyGenPromise = rsaGenerator.generateKeyPair(); let keyGenPromise = rsaGenerator.generateKeyPair();
keyGenPromise.then( keyPair => { keyGenPromise.then(keyPair => {
globalKeyPair = keyPair; globalKeyPair = keyPair;
let priKey = globalKeyPair.priKey; let priKey = globalKeyPair.priKey;
return signer.init(priKey); return signer.init(priKey);
...@@ -1300,6 +1657,7 @@ function signMessagePromise() { ...@@ -1300,6 +1657,7 @@ function signMessagePromise() {
}); });
} }
// Call verify() after sign() is called.
function verifyMessagePromise() { function verifyMessagePromise() {
let verifyer = cryptoFramework.createVerify("RSA1024|PKCS1|SHA256"); let verifyer = cryptoFramework.createVerify("RSA1024|PKCS1|SHA256");
let verifyInitPromise = verifyer.init(globalKeyPair.pubKey); let verifyInitPromise = verifyer.init(globalKeyPair.pubKey);
...@@ -1318,9 +1676,9 @@ function signMessageCallback() { ...@@ -1318,9 +1676,9 @@ function signMessageCallback() {
rsaGenerator.generateKeyPair(function (err, keyPair) { rsaGenerator.generateKeyPair(function (err, keyPair) {
globalKeyPair = keyPair; globalKeyPair = keyPair;
let priKey = globalKeyPair.priKey; let priKey = globalKeyPair.priKey;
signer.init(priKey, function (err, data) { signer.init(priKey, err => {
signer.update(input1, function (err, data) { signer.update(input1, err => {
signer.sign(input2, function (err, data) { signer.sign(input2, (err, data) => {
SignMessageBlob = data; SignMessageBlob = data;
console.info("sign output is " + SignMessageBlob.data); console.info("sign output is " + SignMessageBlob.data);
}); });
...@@ -1329,11 +1687,12 @@ function signMessageCallback() { ...@@ -1329,11 +1687,12 @@ function signMessageCallback() {
}); });
} }
// Call verify() after sign() is called.
function verifyMessageCallback() { function verifyMessageCallback() {
let verifyer = cryptoFramework.createVerify("RSA1024|PKCS1|SHA256"); let verifyer = cryptoFramework.createVerify("RSA1024|PKCS1|SHA256");
verifyer.init(globalKeyPair.pubKey, function (err, data) { verifyer.init(globalKeyPair.pubKey, err => {
verifyer.update(input1, function(err, data) { verifyer.update(input1, err => {
verifyer.verify(input2, SignMessageBlob, function(err, data) { verifyer.verify(input2, SignMessageBlob, function (err, data) {
console.info("verify result is " + data); console.info("verify result is " + data);
}); });
}); });
...@@ -1367,14 +1726,14 @@ let globalKeyPair; ...@@ -1367,14 +1726,14 @@ let globalKeyPair;
let SignMessageBlob; let SignMessageBlob;
let plan1 = "This is Sign test plan1"; let plan1 = "This is Sign test plan1";
let plan2 = "This is Sign test plan1"; let plan2 = "This is Sign test plan1";
let input1 = { data : stringToUint8Array(plan1) }; let input1 = { data: stringToUint8Array(plan1) };
let input2 = { data : stringToUint8Array(plan2) }; let input2 = { data: stringToUint8Array(plan2) };
function signMessagePromise() { function signMessagePromise() {
let eccGenerator = cryptoFramework.createAsyKeyGenerator("ECC256"); let eccGenerator = cryptoFramework.createAsyKeyGenerator("ECC256");
let signer = cryptoFramework.createSign("ECC256|SHA256"); let signer = cryptoFramework.createSign("ECC256|SHA256");
let keyGenPromise = eccGenerator.generateKeyPair(); let keyGenPromise = eccGenerator.generateKeyPair();
keyGenPromise.then( keyPair => { keyGenPromise.then(keyPair => {
globalKeyPair = keyPair; globalKeyPair = keyPair;
let priKey = globalKeyPair.priKey; let priKey = globalKeyPair.priKey;
return signer.init(priKey); return signer.init(priKey);
...@@ -1406,9 +1765,9 @@ function signMessageCallback() { ...@@ -1406,9 +1765,9 @@ function signMessageCallback() {
eccGenerator.generateKeyPair(function (err, keyPair) { eccGenerator.generateKeyPair(function (err, keyPair) {
globalKeyPair = keyPair; globalKeyPair = keyPair;
let priKey = globalKeyPair.priKey; let priKey = globalKeyPair.priKey;
signer.init(priKey, function (err, data) { signer.init(priKey, err => {
signer.update(input1, function (err, data) { signer.update(input1, err => {
signer.sign(input2, function (err, data) { signer.sign(input2, (err, data) => {
SignMessageBlob = data; SignMessageBlob = data;
console.info("sign output is " + SignMessageBlob.data); console.info("sign output is " + SignMessageBlob.data);
}); });
...@@ -1419,9 +1778,9 @@ function signMessageCallback() { ...@@ -1419,9 +1778,9 @@ function signMessageCallback() {
function verifyMessageCallback() { function verifyMessageCallback() {
let verifyer = cryptoFramework.createVerify("ECC256|SHA256"); let verifyer = cryptoFramework.createVerify("ECC256|SHA256");
verifyer.init(globalKeyPair.pubKey, function (err, data) { verifyer.init(globalKeyPair.pubKey, err => {
verifyer.update(input1, function(err, data) { verifyer.update(input1, err => {
verifyer.verify(input2, SignMessageBlob, function(err, data) { verifyer.verify(input2, SignMessageBlob, function (err, data) {
console.info("verify result is " + data); console.info("verify result is " + data);
}); });
}); });
...@@ -1437,7 +1796,7 @@ Use RSA to sign data and verify the signature by segment. ...@@ -1437,7 +1796,7 @@ Use RSA to sign data and verify the signature by segment.
2. Create a **Sign** instance.<br>Call **createSign()** to create a **Sign** instance, initialize the **Sign** instance, and set a private key for signing. 2. Create a **Sign** instance.<br>Call **createSign()** to create a **Sign** instance, initialize the **Sign** instance, and set a private key for signing.
3. Generate a signature.<br>Call the **update()** provided by the **Sign** class multiple times to pass in data by segment and call **sign()** to generate a signature. 3. Generate a signature.<br>Call the **update()** provided by the **Sign** class multiple times to pass in data by segment and call **sign()** to generate a signature.
4. Create a **Verify** instance.<br>Call **createVerify()** to create a **Verify** instance, initialize the instance, and set a public key for signature verification. 4. Create a **Verify** instance.<br>Call **createVerify()** to create a **Verify** instance, initialize the instance, and set a public key for signature verification.
5. Verify the signature.<br>Call the **update()** provided by the **Verify** class multiple times to pass in data by segment and call **verify()** to verify the signature. 5. Verify the signature.<br>Call the **update()** method provided by the **Verify** class multiple times to pass in data by segment and call **verify()** to verify the signature.
```js ```js
import cryptoFramework from "@ohos.security.cryptoFramework" import cryptoFramework from "@ohos.security.cryptoFramework"
...@@ -1463,11 +1822,11 @@ function signLongMessagePromise() { ...@@ -1463,11 +1822,11 @@ function signLongMessagePromise() {
let globalSignData; let globalSignData;
let textSplitLen = 64; // Customized data splitting length. let textSplitLen = 64; // Customized data splitting length.
let keyGenName = "RSA1024"; let keyGenName = "RSA1024";
let cipherAlgName = "RSA1024|PKCS1|SHA256"; let signAlgName = "RSA1024|PKCS1|SHA256";
let globalKeyPair; let globalKeyPair;
let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator(keyGenName); // Create an AsyKeyGenerator object. let asyKeyGenerator = cryptoFramework.createAsyKeyGenerator(keyGenName); // Create an AsyKeyGenerator object.
let signer = cryptoFramework.createSign(cipherAlgName); //Create a Sign object for signing. let signer = cryptoFramework.createSign(signAlgName); // Create a Signer instance.
let verifier = cryptoFramework.createVerify(cipherAlgName); // Create a Verify object for signature verification. let verifier = cryptoFramework.createVerify(signAlgName); // Create a Verifier instance.
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
resolve("testRsaMultiUpdate"); resolve("testRsaMultiUpdate");
...@@ -1490,7 +1849,7 @@ function signLongMessagePromise() { ...@@ -1490,7 +1849,7 @@ function signLongMessagePromise() {
console.info(`globalSignOutput len is ${globalSignData.length}, data is: ${globalSignData.toString()}`); console.info(`globalSignOutput len is ${globalSignData.length}, data is: ${globalSignData.toString()}`);
return verifier.init(globalKeyPair.pubKey); return verifier.init(globalKeyPair.pubKey);
}).then(async() => { }).then(async() => {
// Split and decrypt the ciphertext by 128 bytes, and combine the plaintext obtained each time. // If the plaintext is too large, split the plaintext based on the specified length and cyclically call update() to pass in the plaintext.
for (let i = 0; i < (globalPlainText.length / textSplitLen); i++) { for (let i = 0; i < (globalPlainText.length / textSplitLen); i++) {
let tempData = globalPlainText.slice(i * textSplitLen, (i + 1) * textSplitLen); let tempData = globalPlainText.slice(i * textSplitLen, (i + 1) * textSplitLen);
let tempBlob = { data : stringToUint8Array(tempData) }; let tempBlob = { data : stringToUint8Array(tempData) };
...@@ -1505,7 +1864,7 @@ function signLongMessagePromise() { ...@@ -1505,7 +1864,7 @@ function signLongMessagePromise() {
} }
``` ```
### Using PSS in RSA Signing and Signature Verification ### Using PSS in RSA Signing and Signature Verification
Use the PSS padding mode in RSA signing and signature verification in callback mode. Use the PSS padding mode in RSA signing and signature verification in callback mode.
...@@ -1528,9 +1887,15 @@ function stringToUint8Array(str) { ...@@ -1528,9 +1887,15 @@ function stringToUint8Array(str) {
} }
// Construct the key parameters of the RSA asymmetric key pair based on the key pair specifications. // Construct the key parameters of the RSA asymmetric key pair based on the key pair specifications.
function genRsaKeyPairSpec(nIn : bigint, eIn : bigint, dIn : bigint) { function genRsaKeyPairSpec(nIn: bigint, eIn: bigint, dIn: bigint) {
let rsaCommSpec = { n : nIn, algName : "RSA", specType : cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC }; let rsaCommSpec = { n: nIn, algName: "RSA", specType: cryptoFramework.AsyKeySpecType.COMMON_PARAMS_SPEC };
let rsaKeyPairSpec = { params: rsaCommSpec, sk : dIn, pk : eIn, algName : "RSA", specType : cryptoFramework.AsyKeySpecType.KEY_PAIR_SPEC }; let rsaKeyPairSpec = {
params: rsaCommSpec,
sk: dIn,
pk: eIn,
algName: "RSA",
specType: cryptoFramework.AsyKeySpecType.KEY_PAIR_SPEC
};
return rsaKeyPairSpec; return rsaKeyPairSpec;
} }
...@@ -1545,8 +1910,8 @@ function genRsa2048KeyPairSpec() { ...@@ -1545,8 +1910,8 @@ function genRsa2048KeyPairSpec() {
function verifyMessageCallbackPSS() { function verifyMessageCallbackPSS() {
let plan1 = "This is Sign test plan1"; let plan1 = "This is Sign test plan1";
let plan2 = "This is Sign test plan1"; let plan2 = "This is Sign test plan1";
let input1 = { data : stringToUint8Array(plan1) }; let input1 = { data: stringToUint8Array(plan1) };
let input2 = { data : stringToUint8Array(plan2) }; let input2 = { data: stringToUint8Array(plan2) };
let globalKeyPair; let globalKeyPair;
let signMessageBlob; let signMessageBlob;
// Obtain the key parameter object of the RSA key pair. // Obtain the key parameter object of the RSA key pair.
...@@ -1558,7 +1923,7 @@ function verifyMessageCallbackPSS() { ...@@ -1558,7 +1923,7 @@ function verifyMessageCallbackPSS() {
let verifyer = cryptoFramework.createVerify("RSA2048|PSS|SHA256|MGF1_SHA256"); let verifyer = cryptoFramework.createVerify("RSA2048|PSS|SHA256|MGF1_SHA256");
rsaGeneratorSpec.generateKeyPair(function (err, keyPair) { rsaGeneratorSpec.generateKeyPair(function (err, keyPair) {
globalKeyPair = keyPair; globalKeyPair = keyPair;
signer.init(globalKeyPair.priKey, function (err, data) { signer.init(globalKeyPair.priKey, err => {
// After the initialization, set and obtain the PSS parameters. // After the initialization, set and obtain the PSS parameters.
let setN = 32; let setN = 32;
signer.setSignSpec(cryptoFramework.SignSpecItem.PSS_SALT_LEN_NUM, setN); signer.setSignSpec(cryptoFramework.SignSpecItem.PSS_SALT_LEN_NUM, setN);
...@@ -1572,11 +1937,11 @@ function verifyMessageCallbackPSS() { ...@@ -1572,11 +1937,11 @@ function verifyMessageCallbackPSS() {
console.info("mgf == " + mgf); console.info("mgf == " + mgf);
let mgf1Md = signer.getSignSpec(cryptoFramework.SignSpecItem.PSS_MGF1_MD_STR); let mgf1Md = signer.getSignSpec(cryptoFramework.SignSpecItem.PSS_MGF1_MD_STR);
console.info("mgf1Md == " + mgf1Md); console.info("mgf1Md == " + mgf1Md);
signer.update(input1, function (err, data) { signer.update(input1, err => {
signer.sign(input2, function (err, data) { signer.sign(input2, function (err, data) {
// Before signature verification initialization, set and obtain PSS parameters. The functions are the same as those after initialization. // Before signature verification initialization, set and obtain PSS parameters. The functions are the same as those after initialization.
signMessageBlob = data; signMessageBlob = data;
AlertDialog.show({message : "res" + signMessageBlob.data}); AlertDialog.show({ message: "res" + signMessageBlob.data });
let setN = 32; let setN = 32;
verifyer.setVerifySpec(cryptoFramework.SignSpecItem.PSS_SALT_LEN_NUM, setN); verifyer.setVerifySpec(cryptoFramework.SignSpecItem.PSS_SALT_LEN_NUM, setN);
let saltLen = verifyer.getVerifySpec(cryptoFramework.SignSpecItem.PSS_SALT_LEN_NUM); let saltLen = verifyer.getVerifySpec(cryptoFramework.SignSpecItem.PSS_SALT_LEN_NUM);
...@@ -1589,10 +1954,10 @@ function verifyMessageCallbackPSS() { ...@@ -1589,10 +1954,10 @@ function verifyMessageCallbackPSS() {
console.info("mgf == " + mgf); console.info("mgf == " + mgf);
let mgf1Md = verifyer.getVerifySpec(cryptoFramework.SignSpecItem.PSS_MGF1_MD_STR); let mgf1Md = verifyer.getVerifySpec(cryptoFramework.SignSpecItem.PSS_MGF1_MD_STR);
console.info("mgf1Md == " + mgf1Md); console.info("mgf1Md == " + mgf1Md);
verifyer.init(globalKeyPair.pubKey, function (err, data) { verifyer.init(globalKeyPair.pubKey, err => {
verifyer.update(input1, function(err, data) { verifyer.update(input1, err => {
verifyer.verify(input2, signMessageBlob, function(err, data) { verifyer.verify(input2, signMessageBlob, function (err, data) {
AlertDialog.show({message : "res " + data}); AlertDialog.show({ message: "res " + data });
}) })
}); });
}); });
...@@ -1603,10 +1968,72 @@ function verifyMessageCallbackPSS() { ...@@ -1603,10 +1968,72 @@ function verifyMessageCallbackPSS() {
} }
``` ```
### Signing and Signature Verification Using SM2
> **NOTE**
>
> SM2 signing and signature verification are supported from API version 10.
Use SM2 to sign data and verify the signature.
1. Generate an SM2 key pair. Call **createAsyKeyGenerator()** to create an **AsyKeyGenerator** instance and generate an SM2 asymmetric key pair.
2. Create a **Sign** instance.<br>Call **createSign()** to create a **Sign** instance, initialize the **Sign** instance, and set a private key for signing.
3. Generate a signature.<br>Call **update()** provided by the **Sign** class to pass in the data for signing and call **doFinal()** to generate a signature.
4. Create a **Verify** instance.<br>Call **createVerify()** to create a **Verify** instance, initialize the instance, and set a public key for signature verification.
5. Verify the signature.<br>Call **update()** provided by the **Verify** class to pass in the signature data and call **doFinal()** to verify the signature.
```js
import cryptoFramework from "@ohos.security.cryptoFramework"
// Convert strings in plaintext into byte streams.
function stringToUint8Array(str) {
var arr = [];
for (var i = 0, j = str.length; i < j; ++i) {
arr.push(str.charCodeAt(i));
}
var tmpArray = new Uint8Array(arr);
return tmpArray;
}
let plan1 = "This is Sign test plan1";
let plan2 = "This is Sign test plan2";
let input1 = { data: stringToUint8Array(plan1) };
let input2 = { data: stringToUint8Array(plan2) };
function signAndVerify() {
let signMessageBlob;
let globalKeyPair;
let sm2Generator = cryptoFramework.createAsyKeyGenerator("SM2_256");
let signer = cryptoFramework.createSign("SM2_256|SM3");
sm2Generator.generateKeyPair(function (err, keyPair) {
globalKeyPair = keyPair;
let priKey = globalKeyPair.priKey;
signer.init(priKey, err => {
signer.update(input1, err => {
signer.sign(input2, function (err, data) {
signMessageBlob = data;
console.info("sign output is " + signMessageBlob.data);
let verifyer = cryptoFramework.createVerify("SM2_256|SM3");
verifyer.init(globalKeyPair.pubKey, err => {
verifyer.update(input1, err => {
verifyer.verify(input2, signMessageBlob, function (err, data) {
console.info("verify result is " + data);
AlertDialog.show({ message: "verify success" })
});
});
})
});
});
});
});
}
```
## Key Agreement ## Key Agreement
### When to Use ### When to Use
Key agreement allows two parties to establish a shared secret over an insecure channel. Key agreement allows two parties to establish a shared secret over an insecure channel.
> **NOTE** > **NOTE**
...@@ -1637,7 +2064,7 @@ function ecdhPromise() { ...@@ -1637,7 +2064,7 @@ function ecdhPromise() {
let eccGenerator = cryptoFramework.createAsyKeyGenerator("ECC256"); let eccGenerator = cryptoFramework.createAsyKeyGenerator("ECC256");
let eccKeyAgreement = cryptoFramework.createKeyAgreement("ECC256"); // ECC is supported for key agreement from API version 10. let eccKeyAgreement = cryptoFramework.createKeyAgreement("ECC256"); // ECC is supported for key agreement from API version 10.
let keyGenPromise = eccGenerator.generateKeyPair(); let keyGenPromise = eccGenerator.generateKeyPair();
keyGenPromise.then( keyPair => { keyGenPromise.then(keyPair => {
globalKeyPair = keyPair; globalKeyPair = keyPair;
return eccKeyAgreement.generateSecret(keyPair.priKey, keyPair.pubKey); return eccKeyAgreement.generateSecret(keyPair.priKey, keyPair.pubKey);
}).then((secret) => { }).then((secret) => {
...@@ -1806,10 +2233,10 @@ async function doLoopMdPromise() { ...@@ -1806,10 +2233,10 @@ async function doLoopMdPromise() {
for (let i = 0; i <= messageText.length; i++) { for (let i = 0; i <= messageText.length; i++) {
if ((i % updateLength == 0 || i == messageText.length) && messageArr.length != 0) { if ((i % updateLength == 0 || i == messageText.length) && messageArr.length != 0) {
let message = new Uint8Array(messageArr); let message = new Uint8Array(messageArr);
let messageBlob = { data : message }; let messageBlob = { data: message };
// Use await to process the update() in the for() loop. // Use await to process the update() in the for() loop.
try { try {
await md.update (messageBlob); // Call update() to process data by segment. await md.update (messageBlob); // Use update() to process data by segment.
} catch (error) { } catch (error) {
console.error("await update error code: " + error.code + ", message is: " + error.message); console.error("await update error code: " + error.code + ", message is: " + error.message);
return; return;
...@@ -1852,11 +2279,11 @@ For details about the APIs, see [Crypto Framework](../reference/apis/js-apis-cry ...@@ -1852,11 +2279,11 @@ For details about the APIs, see [Crypto Framework](../reference/apis/js-apis-cry
| Instance | API | Description | | Instance | API | Description |
| --------------- | ------------------------------------------------------------ | --------------------------------------------------- | | --------------- | ------------------------------------------------------------ | --------------------------------------------------- |
| cryptoFramework | function createMac(algName : string) : Mac; | Creates a **Mac** instance. | | cryptoFramework | function createMac(algName : string) : Mac; | Creates a **Mac** instance. |
| Mac | init(key : SymKey, callback : AsyncCallback\<void>) : void; | Initializes the **Mac** instance. This API uses an asynchronous callback to return the result. | | Mac | init(key : SymKey, callback : AsyncCallback\<void>) : void; | Initializes the **Mac** instance. This API uses an asynchronous callback to return the result.|
| Mac | init(key : SymKey) : Promise\<void>; | Initializes the **Mac** instance. This API uses a promise to return the result. | | Mac | init(key : SymKey) : Promise\<void>; | Initializes the **Mac** instance. This API uses a promise to return the result. |
| Mac | update(input : DataBlob, callback : AsyncCallback\<void>) : void; | Updates the data for the MAC operation. This API uses an asynchronous callback to return the result. | | Mac | update(input : DataBlob, callback : AsyncCallback\<void>) : void; | Updates the data for the MAC operation. This API uses an asynchronous callback to return the result. |
| Mac | update(input : DataBlob) : Promise\<void>; | Updates the data for the MAC operation. This API uses a promise to return the result. | | Mac | update(input : DataBlob) : Promise\<void>; | Updates the data for the MAC operation. This API uses a promise to return the result. |
| Mac | doFinal(callback : AsyncCallback\<DataBlob>) : void; | Finalizes the MAC operation to generate a MAC. This API uses an asynchronous callback to return the result. | | Mac | doFinal(callback : AsyncCallback\<DataBlob>) : void; | Finalizes the MAC operation to generate a MAC. This API uses an asynchronous callback to return the result. |
| Mac | doFinal() : Promise\<DataBlob>; | Finalizes the MAC operation to generate a MAC. This API uses a promise to return the result. | | Mac | doFinal() : Promise\<DataBlob>; | Finalizes the MAC operation to generate a MAC. This API uses a promise to return the result. |
| Mac | getMacLength() : number; | Obtains the length of the MAC based on the specified algorithm. | | Mac | getMacLength() : number; | Obtains the length of the MAC based on the specified algorithm. |
| Mac | readonly algName : string; | Obtains the digest algorithm. | | Mac | readonly algName : string; | Obtains the digest algorithm. |
...@@ -1895,7 +2322,7 @@ function doHmacByPromise() { ...@@ -1895,7 +2322,7 @@ function doHmacByPromise() {
console.info("[Promise]: Mac algName is: " + mac.algName); console.info("[Promise]: Mac algName is: " + mac.algName);
let KeyBlob = { let KeyBlob = {
// 128-bit key // 128-bit key
data : stringToUint8Array("12345678abcdefgh") data: stringToUint8Array("12345678abcdefgh")
} }
let symKeyGenerator = cryptoFramework.createSymKeyGenerator("AES128"); let symKeyGenerator = cryptoFramework.createSymKeyGenerator("AES128");
// Convert the binary data into a key. // Convert the binary data into a key.
...@@ -1935,7 +2362,7 @@ function doHmacByCallback() { ...@@ -1935,7 +2362,7 @@ function doHmacByCallback() {
console.info("[Promise]: Mac algName is: " + mac.algName); console.info("[Promise]: Mac algName is: " + mac.algName);
let KeyBlob = { let KeyBlob = {
// 128-bit key // 128-bit key
data : stringToUint8Array("12345678abcdefgh") data: stringToUint8Array("12345678abcdefgh")
} }
let symKeyGenerator = cryptoFramework.createSymKeyGenerator("AES128"); let symKeyGenerator = cryptoFramework.createSymKeyGenerator("AES128");
// Convert the binary data into a key. // Convert the binary data into a key.
...@@ -1970,7 +2397,7 @@ function doHmacByCallback() { ...@@ -1970,7 +2397,7 @@ function doHmacByCallback() {
### Generating an HMAC by Segment ### Generating an HMAC by Segment
Generate an HMAC by segment. Generate an HMAC by segment.
1. Use **createMac()** to create a **Mac** instance. 1. Use **createMac()** to create a **Mac** instance.
2. Use **init()** to initialize the **Mac** instance with the symmetric key passed in. 2. Use **init()** to initialize the **Mac** instance with the symmetric key passed in.
...@@ -2018,7 +2445,7 @@ function doLoopHmacPromise() { ...@@ -2018,7 +2445,7 @@ function doLoopHmacPromise() {
for (let i = 0; i <= messageText.length; i++) { for (let i = 0; i <= messageText.length; i++) {
if ((i % updateLength == 0 || i == messageText.length) && messageArr.length != 0) { if ((i % updateLength == 0 || i == messageText.length) && messageArr.length != 0) {
let message = new Uint8Array(messageArr); let message = new Uint8Array(messageArr);
let messageBlob = { data : message }; let messageBlob = { data: message };
// Use await to process the update() in the for() loop. // Use await to process the update() in the for() loop.
try { try {
promiseMacUpdate = await mac.update(messageBlob); // Invoke update() multiple times. promiseMacUpdate = await mac.update(messageBlob); // Invoke update() multiple times.
...@@ -2079,29 +2506,31 @@ For more information about the APIs, see [Crypto Framework](../reference/apis/js ...@@ -2079,29 +2506,31 @@ For more information about the APIs, see [Crypto Framework](../reference/apis/js
import cryptoFramework from "@ohos.security.cryptoFramework" import cryptoFramework from "@ohos.security.cryptoFramework"
// Generate a random number in promise mode. // Generate a random number in promise mode.
function doRandByPromise(len) { function doRandByPromise() {
var rand; let rand;
let len = 4; // Generate a 4-byte random number.
try { try {
rand = cryptoFramework.createRandom(); rand = cryptoFramework.createRandom();
} catch (error) { } catch (error) {
console.error("[Promise]: error code: " + error.code + ", message is: " + error.message); console.error("[Promise]: error code: " + error.code + ", message is: " + error.message);
} }
var promiseGenerateRand = rand.generateRandom(len); let promiseGenerateRand = rand.generateRandom(len);
promiseGenerateRand.then(randData => { promiseGenerateRand.then(randData => {
console.error("[Promise]: rand result: " + randData.data); console.info("[Promise]: rand result: " + randData.data);
try { try {
rand.setSeed(randData); rand.setSeed(randData);
} catch (error) { } catch (error) {
console.log("setSeed failed, errCode: " + error.code + ", errMsg: " + error.message); console.error("setSeed failed, errCode: " + error.code + ", errMsg: " + error.message);
} }
}).catch(error => { }).catch(error => {
console.error("[Promise]: error: " + error.message); console.error("[Promise]: error: " + error.message);
}); });
} }
// Generate a random number in callback mode. // Generate a random number in callback mode.
function doRandByCallback(len) { function doRandByCallback() {
var rand; let rand;
let len = 4; // Generate a 4-byte random number.
try { try {
rand = cryptoFramework.createRandom(); rand = cryptoFramework.createRandom();
} catch (error) { } catch (error) {
...@@ -2111,19 +2540,20 @@ function doRandByCallback(len) { ...@@ -2111,19 +2540,20 @@ function doRandByCallback(len) {
if (err) { if (err) {
console.error("[Callback]: err: " + err.code); console.error("[Callback]: err: " + err.code);
} else { } else {
console.error("[Callback]: generate random result: " + randData.data); console.info("[Callback]: generate random result: " + randData.data);
try { try {
rand.setSeed(randData); rand.setSeed(randData);
} catch (error) { } catch (error) {
console.log("setSeed failed, errCode: " + error.code + ", errMsg: " + error.message); console.error("setSeed failed, errCode: " + error.code + ", errMsg: " + error.message);
} }
} }
}); });
} }
// Generate a random number synchronously. // Generate a random number synchronously.
function doRandBySync(len) { function doRandBySync() {
var rand; let rand;
let len = 24; // Generate a 24-byte random number.
try { try {
rand = cryptoFramework.createRandom(); rand = cryptoFramework.createRandom();
} catch (error) { } catch (error) {
......
...@@ -158,7 +158,8 @@ ...@@ -158,7 +158,8 @@
- [Linux Kernel Overview](kernel-standard-overview.md) - [Linux Kernel Overview](kernel-standard-overview.md)
- [Applying Patches on Development Boards](kernel-standard-patch.md) - [Applying Patches on Development Boards](kernel-standard-patch.md)
- [Compiling and Building the Linux Kernel](kernel-standard-build.md) - [Compiling and Building the Linux Kernel](kernel-standard-build.md)
- Enhanced Kernel Features - [HCK](kernel-standard-hck.md)
- Enhanced kernel features
- [Enhanced Swap](kernel-standard-mm-eswap.md) - [Enhanced Swap](kernel-standard-mm-eswap.md)
- [NewIP Kernel Protocol Stack](kernel-standard-newip.md) - [NewIP Kernel Protocol Stack](kernel-standard-newip.md)
- Task Scheduling - Task Scheduling
......
# HCK
## Background
Vendors have different kernel requirements and feature solutions when adapting their platforms to OpenHarmony.
If their code is directly integrated into the kernel repository or patches are applied, problems such as low build and development efficiency, complex routine maintenance, and poor feature portability will be caused.
To incorporate third-party kernel features into OpenHarmony without or with little interference with the kernel repository, OpenHarmony provides the OpenHarmony Common Kernel (HCK) framework, which provides a complete set of instrumentation, registration, and invoking interfaces to reduce modification to the kernel. The HCK provides instrumentation interfaces applicable to multiple platforms, implementing decoupling of the kernel.
This document describes how to implement the HCK based on the native hook mode.
## Application Scope
You can use the HCK framework when your Linux kernel patches, features, and modules require modification on the native kernel.
## Available APIs
**DECLARE_HCK_LITE_HOOK**: macro used to declare an instrumentation interface. It generates the registration and call interfaces.
**REGISTER_HCK_LITE_HOOK**: macro used to register an instrumentation interface instance. The HCK supports only the registration of a singleton for a singleton, and the registered interfaces cannot be deregistered.
**REGISTER_HCK_LITE_DATA_HOOK**: macro used to register an instrumentation interface instance with parameters. Only a singleton can be registered, and the registered interfaces cannot be deregistered.
**CALL_HCK_LITE_HOOK**: macro used to call an instrumentation interface, which replaces the code that intrudes into the native kernel code.
**Figure 1** HCK interface definition, registration, and invocation
![](figures/hck.png)
## Usage Guide
### Configuration
Before using the HCK, enable the following settings in the **config** file of your platform in the **kernel_linux_config** repository:
```c
CONFIG_HCK=y
CONFIG_HCK_VENDOR_HOOKS=y
```
### Interface Definition
Define the hook interface in **kernel-x.x/include/linux/hck/lite_hck_xxx.h** by using the following macro:
```c
#include <linux/hck/lite_vendor_hooks.h> // Include HCK header file.
// You can customize data types for passing in parameters in interface registration.
struct hck_data {
int stat;
char* name;
};
// Declare the hook interface. The declaration macro contains the EXPORT interface, and you do not need to call EXPORT separately to declare the interface.
DECLARE_HCK_LITE_HOOK(boot_config_info0_lhck, TP_PROTO(Type Parameter, ...), TP_ARGS(Parameter, ...));
```
### Interface Declaration
Add the interface declaration header file to the new vendor hck module. The file path is **drivers/hck/vendor_hooks.c**.
```c
#define CREATE_LITE_VENDOR_HOOK
#include <linux/hck/lite_hck_sample.h> // Add the interface declaration header file after the macro definition.
```
### Interface Registration
Define the instrumentation function and register the hook functions in the hook module.
```c
// Include the interface declaration header file.
#include <linux/hck/lite_hck_sample.h>
// Implement the interfaces:
boot_config_info0([void* data], ...)
{
// Call the input parameters for the registration.
((struct hck_data*)data)->stat ... ;
}
// Register the interface. Generally, register the interface in the init() function of the module. Ensure that the registration is completed before the interface is called.
xxx_init()
{
REGISTER_HCK_LITE_HOOK(boot_config_info0_lhck, boot_config_info0);
// Register the interface with parameters. The input parameters can be obtained and used in the interface instance.
REGISTER_HCK_LITE_DATA_HOOK(boot_config_info1_lhck, boot_config_info1, data);
}
```
### Interface Calling
Add the hook function in other kernel modules.
For example, add the previously defined interface to **linux-x.y/drivers/xxx/xxx.c**.
```c
#include <linux/hck/lite_hck_sample.h>
...
int foo(...)
{
CALL_HCK_LITE_HOOK(boot_config_info0_lhck, parameter...);
}
```
### Example
The sample code is located in **kernel/linux/linux-5.10/samples/hck**.
To enable the sample code, enable the following settings in **config** (do not enable the settings in the official version):
```c
CONFIG_SAMPLES=y
CONFIG_SAMPLE_HCK=y
CONFIG_SAMPLE_HCK_CALL=y
CONFIG_SAMPLE_HCK_REGISTER=y
```
## Specifications
### Naming Rules
1. Comply with the kernel interface naming rules. The interface names should be descriptive.
2. Add **suffix _lhck** to the end of the interface names of lite hck interfaces. For example:
```c
DECLARE_HCK_LITE_HOOK(boot_config_info0_lhck, TP_PROTO(int* s ), TP_ARGS(s));
```
3. Use English words and grammar, not Hanyu Pinyin.
4. Use commonly accepted abbreviations. Do not create abbreviations.
5. Avoid using negative expressions.
For example, name the interface for obtaining the boot configuration as follows:get_boot_config_lhck
### File Directory Specifications
1. Place the interface definition header file in **kernel-x.x/include/linux/hck/**.
2. Name header files in the **lite_hck_*module-description*.h** format, for example, **lite_hck_mmc.h**.
...@@ -7,7 +7,8 @@ OpenHarmony is an open OS that allows you to easily develop services and applica ...@@ -7,7 +7,8 @@ OpenHarmony is an open OS that allows you to easily develop services and applica
This environment combines chip security and system security features with upper-layer security services to secure hardware, the system, data, device interconnection, applications, and updates. This environment combines chip security and system security features with upper-layer security services to secure hardware, the system, data, device interconnection, applications, and updates.
**Figure 1** Security assurance
**Figure 1** Security assurance
![](figures/security-assurance-framework.png) ![](figures/security-assurance-framework.png)
...@@ -18,32 +19,36 @@ This environment combines chip security and system security features with upper- ...@@ -18,32 +19,36 @@ This environment combines chip security and system security features with upper-
### Security Mechanism ### Security Mechanism
- Boot root of trust - Boot root of trust
OpenHarmony devices use the public key infrastructure (PKI) to protect software integrity and ensure that the software source is valid and the software is not tampered with. OpenHarmony devices use the public key infrastructure (PKI) to protect software integrity and ensure that the software source is valid and the software is not tampered with.
In the device boot process, software signature verification is performed at each phase to form a secure boot chain. If signature verification fails at any phase, the device boot will be terminated. The hardware or software entity that initially performs signature verification in the secure boot chain is the boot root of trust. It must be valid and should not be tampered with. The boot root of trust can be a built-in code segment in the read-only memory (ROM). This code segment is written in the chip during the chip manufacturing and cannot be modified later. When the device is powered on and initialized, this code segment is executed first and used to verify software signatures. In the device boot process, software signature verification is performed at each phase to form a secure boot chain. If signature verification fails at any phase, the device boot will be terminated. The hardware or software entity that initially performs signature verification in the secure boot chain is the boot root of trust. It must be valid and should not be tampered with. The boot root of trust can be a built-in code segment in the read-only memory (ROM). This code segment is written in the chip during the chip manufacturing and cannot be modified later. When the device is powered on and initialized, this code segment is executed first and used to verify software signatures.
When you use the code for signature verification, ensure the validity of the PKI public keys. OpenHarmony devices use a storage medium such as eFUSE and one-time password (OTP) to store the public keys (for example, their hash values) and guarantee their validity. A public key is usually programed into the eFuse or OTP of a device during device manufacturing. When you use the code for signature verification, ensure the validity of the PKI public keys. OpenHarmony devices use a storage medium such as eFUSE and one-time password (OTP) to store the public keys (for example, their hash values) and guarantee their validity. A public key is usually programed into the eFuse or OTP of a device during device manufacturing.
- Hardware-isolated trusted environment - Hardware-isolated trusted environment
The hardware-isolated trusted environment complies with the design concept of the trusted computing system. There is a clear boundary between the trusted environment and untrusted one. OpenHarmony devices protect core sensitive data in the trusted environment. Even if OS vulnerabilities in the untrusted environment are exploited, sensitive data in the trusted environment is secure. The hardware-isolated trusted environment complies with the design concept of the trusted computing system. There is a clear boundary between the trusted environment and untrusted one. OpenHarmony devices protect core sensitive data in the trusted environment. Even if OS vulnerabilities in the untrusted environment are exploited, sensitive data in the trusted environment is secure.
The trusted environment of OpenHarmony devices is built based on a hardware security isolation mechanism. The chip isolation mechanism varies slightly on different OpenHarmony devices, and the most common mechanism is Arm TrustZone. On some RISC-V chip platforms, independent security cores may also be used to build a trusted environment. The trusted environment of OpenHarmony devices is built based on a hardware security isolation mechanism. The chip isolation mechanism varies slightly on different OpenHarmony devices, and the most common mechanism is Arm TrustZone. On some RISC-V chip platforms, independent security cores may also be used to build a trusted environment.
A specific, simplified OS iTrustee lite runs in the trusted environment to manage resources and schedule tasks in the environment and provide security services for OpenHarmony devices. Key management and data security are the most common security services in the trusted environment. A device has a unique hardware root key in the eFuse/OTP. Based on this root key and service context, the trusted environment generates multiple keys that provide key management and data encryption/decryption services for applications. During their whole lifecycle, core keys of devices stay in the trusted environment. The trusted environment also provides security services such as identity authentication, system status monitoring, and secure data storage to enhance device security. A specific, simplified OS iTrustee lite runs in the trusted environment to manage resources and schedule tasks in the environment and provide security services for OpenHarmony devices. Key management and data security are the most common security services in the trusted environment. A device has a unique hardware root key in the eFuse/OTP. Based on this root key and service context, the trusted environment generates multiple keys that provide key management and data encryption/decryption services for applications. During their whole lifecycle, core keys of devices stay in the trusted environment. The trusted environment also provides security services such as identity authentication, system status monitoring, and secure data storage to enhance device security.
- Hardware key engine - Hardware key engine
Cryptography is the basis of information security. Data encryption/decryption requires high efficiency and security of computing devices. Hardware encryption/decryption technologies use computer hardware to assist or even replace software to encrypt or decrypt data. Hardware-based encryption/decryption is more efficient and secure than software-based encryption/decryption. Cryptography is the basis of information security. Data encryption/decryption requires high efficiency and security of computing devices. Hardware encryption/decryption technologies use computer hardware to assist or even replace software to encrypt or decrypt data. Hardware-based encryption/decryption is more efficient and secure than software-based encryption/decryption.
Since some dedicated hardware resources are used for data encryption/decryption, the CPU can concurrently execute other computing tasks, which greatly improves performance and reduces the CPU load. In addition, a well-designed hardware key engine protects keys from leak even if the software is cracked and even defends against electromagnetic and radiation attacks from physical channels. Since some dedicated hardware resources are used for data encryption/decryption, the CPU can concurrently execute other computing tasks, which greatly improves performance and reduces the CPU load. In addition, a well-designed hardware key engine protects keys from leak even if the software is cracked and even defends against electromagnetic and radiation attacks from physical channels.
OpenHarmony devices support the hardware key engine, which allows OpenHarmony to perform computing tasks such as data encryption and decryption, certificate signature verification, and hash value calculation. The hardware key engine supports popular algorithms such as Advanced Encryption Standard (AES) and Rivest-Shamir-Adleman (RSA). OpenHarmony devices support the hardware key engine, which allows OpenHarmony to perform computing tasks such as data encryption and decryption, certificate signature verification, and hash value calculation. The hardware key engine supports popular algorithms such as Advanced Encryption Standard (AES) and Rivest-Shamir-Adleman (RSA).
### Recommended Practices ### Recommended Practices
- The boot root of trust consists of a built-in code segment in the chip and the root key of the device. The root of trust is written into the chip during manufacturing and cannot be modified in the device lifecycle. It is used to verify software certificates in the device boot process. The root key is the public key matching the private key of the device certificate signature. The private key is maintained on the PKI signature server and the public key is written to the device. To prevent attackers from tampering with the public key to bypass signature authentication, you can write the public key to media such as fuses on OpenHarmony devices. Considering that the fuse space is limited, you can store only the hash value of the public key in the fuse and verify the validity of the public key using the boot code. - The boot root of trust consists of a built-in code segment in the chip and the root key of the device. The root of trust is written into the chip during manufacturing and cannot be modified in the device lifecycle. It is used to verify software certificates in the device boot process. The root key is the public key matching the private key of the device certificate signature. The private key is maintained on the PKI signature server and the public key is written to the device. To prevent attackers from tampering with the public key to bypass signature authentication, you can write the public key to media such as fuses on OpenHarmony devices. Considering that the fuse space is limited, you can store only the hash value of the public key in the fuse and verify the validity of the public key using the boot code.
- Generally, a trusted execution environment (TEE) is built based on the Arm TrustZone technology, and can also adopt other isolation mechanisms, such as TrustZone-M and independent security cores, depending on the device form. A TEE OS must be deployed in the TEE to manage resources and schedule tasks. OpenHarmony provides iTrustee as the TEE OS. You can develop and deploy security services based on iTrustee. - Generally, a trusted execution environment (TEE) is built based on the Arm TrustZone technology, and can also adopt other isolation mechanisms, such as TrustZone-M and independent security cores, depending on the device form. A TEE OS must be deployed in the TEE to manage resources and schedule tasks. OpenHarmony provides iTrustee as the TEE OS. You can develop and deploy security services based on iTrustee.
Not all OpenHarmony devices need to support the TEE, for example, some devices with thin resources that run less sensitive services may not need the TEE. You can choose whether to support the TEE and how to implement the TEE based on service requirements. Not all OpenHarmony devices need to support the TEE, for example, some devices with thin resources that run less sensitive services may not need the TEE. You can choose whether to support the TEE and how to implement the TEE based on service requirements.
- The hardware key engine must provide key algorithms related to true random numbers, public keys, symmetric keys, and hash values. By deploying required drivers in OpenHarmony, you can provide unified key management and key algorithms for applications. - The hardware key engine must provide key algorithms related to true random numbers, public keys, symmetric keys, and hash values. By deploying required drivers in OpenHarmony, you can provide unified key management and key algorithms for applications.
...@@ -72,8 +77,8 @@ For device with 128 KB to 128 MB of memory, the OpenHarmony lite kernel is recom ...@@ -72,8 +77,8 @@ For device with 128 KB to 128 MB of memory, the OpenHarmony lite kernel is recom
The following figure shows how DAC works when a process accesses a file. The DAC first matches the process UID with the file UID, and then the process GID with the file GID. If the UID and GID both fail to match, DAC checks the **other** attribute of the file to determine whether the process is allowed to read, write, or execute the file. In addition, the system supports a privileged capability that is not subject to DAC mechanism (read, write, and execute) and can access files directly. Services with high permissions (such as system services) can manage files of applications with low permissions (such as third-party applications). The following figure shows how DAC works when a process accesses a file. The DAC first matches the process UID with the file UID, and then the process GID with the file GID. If the UID and GID both fail to match, DAC checks the **other** attribute of the file to determine whether the process is allowed to read, write, or execute the file. In addition, the system supports a privileged capability that is not subject to DAC mechanism (read, write, and execute) and can access files directly. Services with high permissions (such as system services) can manage files of applications with low permissions (such as third-party applications).
**Figure 2** How DAC works **Figure 2** How DAC works
![](figures/how-dac-works.png) ![](figures/how-dac-works.png)
- Capability mechanism - Capability mechanism
...@@ -94,14 +99,14 @@ For device with 128 KB to 128 MB of memory, the OpenHarmony lite kernel is recom ...@@ -94,14 +99,14 @@ For device with 128 KB to 128 MB of memory, the OpenHarmony lite kernel is recom
- Secure boot must be enabled, and the trusted root must be in the chip and cannot be modified. In addition, you must consider the impact of secure upgrade (if available) on secure boot, that is, the signature or hash value of an image file must be updated after a secure upgrade. - Secure boot must be enabled, and the trusted root must be in the chip and cannot be modified. In addition, you must consider the impact of secure upgrade (if available) on secure boot, that is, the signature or hash value of an image file must be updated after a secure upgrade.
## Data Security ## Data security
### Security Mechanism ### Security Mechanism
OpenHarmony Universal KeyStore (HUKS) provides key and certificate management. For OpenHarmony, it mainly provides key management for HiChain (device identity authentication platform). The figure below shows the HUKS functions. OpenHarmony Universal KeyStore (HUKS) provides key and certificate management. For OpenHarmony, it mainly provides key management for HiChain (device identity authentication platform). The figure below shows the HUKS functions.
**Figure 3** HUKS functions **Figure 3** HUKS functions
![](figures/huks-functions.png) ![](figures/huks-functions.png)
...@@ -143,39 +148,35 @@ To use the device certification function, it is recommended that you use HiChain ...@@ -143,39 +148,35 @@ To use the device certification function, it is recommended that you use HiChain
To ensure secure transmit of user data between devices, a trust relationship and a secure data transmission channel must be established between the devices. The following figure shows how an IoT controller and an IoT device establish a trust relationship. To ensure secure transmit of user data between devices, a trust relationship and a secure data transmission channel must be established between the devices. The following figure shows how an IoT controller and an IoT device establish a trust relationship.
**Figure 4** Process of establishing a trust relationship between devices
**Figure 4** Process of establishing a trust relationship between devices
![](figures/how-an-iot-controller-and-an-iot-device-establish-a-trust-relationship.png) ![](figures/how-an-iot-controller-and-an-iot-device-establish-a-trust-relationship.png)
- IoT device interconnection security - IoT device interconnection security
A trust relationship can be established between an IoT device that runs OpenHarmony (such as an AI speaker, smart home device, and wearable) and an IoT controller. Encrypted user data is transmitted between the IoT device and IoT controller through a secure connection.
A trust relationship can be established between an IoT device that runs OpenHarmony (such as an AI speaker, smart home device, and wearable) and an IoT controller. Encrypted user data is transmitted between the IoT device and IoT controller through a secure connection.
- IoT service identifier of the IoT controller - IoT service identifier of the IoT controller
An IoT controller generates different identifiers for different IoT device management services to isolate these services. The identifier can be used for authentication and communication between an IoT controller and an IoT device. It is an Ed25519 public/private key pair generated using the elliptic curve cryptography.
An IoT controller generates different identifiers for different IoT device management services to isolate these services. The identifier can be used for authentication and communication between an IoT controller and an IoT device. It is an Ed25519 public/private key pair generated using the elliptic curve cryptography.
- IoT device identifier - IoT device identifier
An IoT device can generate its own device identifier for communicating with the IoT controller. It is also an Ed25519 public/private key pair generated using elliptic curve cryptography, with the private key stored on the IoT device. Each time the device is restored to factory settings, the public/private key pair will be reset. An IoT device can generate its own device identifier for communicating with the IoT controller. It is also an Ed25519 public/private key pair generated using elliptic curve cryptography, with the private key stored on the IoT device. Each time the device is restored to factory settings, the public/private key pair will be reset.
The identifier can be used for secure communication between the IoT controller and IoT device. After the devices exchange the service identifier or device identifier, they can negotiate the key and establish a secure communication channel.
The identifier can be used for secure communication between the IoT controller and IoT device. After the devices exchange the service identifier or device identifier, they can negotiate the key and establish a secure communication channel.
- P2P trusted binding between devices - P2P trusted binding between devices
When an IoT controller and an IoT device establish a trust relationship, they exchange identifiers. When an IoT controller and an IoT device establish a trust relationship, they exchange identifiers.
During this process, the user needs to enter or scan the PIN provided by the IoT device on the IoT controller. PIN is either dynamically generated if the IoT device has a screen, or preset by the manufacturer if it does not have a screen. A PIN can be a number or a QR code. Then the IoT controller and IoT device perform authentication and session key exchange based on password authenticated key exchange (PAKE), and use the session key to encrypt the channel for exchanging identity public keys.
During this process, the user needs to enter or scan the PIN provided by the IoT device on the IoT controller. PIN is either dynamically generated if the IoT device has a screen, or preset by the manufacturer if it does not have a screen. A PIN can be a number or a QR code. Then the IoT controller and IoT device perform authentication and session key exchange based on password authenticated key exchange (PAKE), and use the session key to encrypt the channel for exchanging identity public keys.
- Secure communication between the IoT controller and IoT device - Secure communication between the IoT controller and IoT device
When an IoT controller and an IoT device communicate with each other after establishing a trust relationship, they authenticate each other by using the locally stored identity public key of the peer. Bidirectional identity authentication and session key exchange are performed using the Station-to-Station (STS) protocol during each communication. The session key is used to encrypt the data transmission channel between the devices.
When an IoT controller and an IoT device communicate with each other after establishing a trust relationship, they authenticate each other by using the locally stored identity public key of the peer. Bidirectional identity authentication and session key exchange are performed using the Station-to-Station (STS) protocol during each communication. The session key is used to encrypt the data transmission channel between the devices.
## Application Security ## Application Security
...@@ -183,32 +184,30 @@ To ensure secure transmit of user data between devices, a trust relationship and ...@@ -183,32 +184,30 @@ To ensure secure transmit of user data between devices, a trust relationship and
### Security Mechanism ### Security Mechanism
- Application signature management - Application signature management
After developing and debugging an OpenHarmony application, sign the application installation package using a private key, which matches a public key. Generally, the OEM generates a public/private key pair, presets the public key in the device, and stores the private key on a local server that is not connected to the Internet to prevent private key leakage. After you finish developing an application, you can use an external device (such as a USB flash drive) to upload the installation package to the server where the private key is stored, calculate the signature, and download the signature to the external device. During application installation, the hash value of the bundle is calculated using the SHA-256 algorithm. The hash value, together with the signature and preset public key, is used for authentication. The application can be installed only after the authentication is successful. After developing and debugging an OpenHarmony application, sign the application installation package using a private key, which matches a public key. Generally, the OEM generates a public/private key pair, presets the public key in the device, and stores the private key on a local server that is not connected to the Internet to prevent private key leakage. After you finish developing an application, you can use an external device (such as a USB flash drive) to upload the installation package to the server where the private key is stored, calculate the signature, and download the signature to the external device. During application installation, the hash value of the bundle is calculated using the SHA-256 algorithm. The hash value, together with the signature and preset public key, is used for authentication. The application can be installed only after the authentication is successful.
In addition, the application source must be verified to ensure that the application is from a valid developer. As a developer, you must apply for a development certificate and use it to sign the application you have developed. During application installation, the upper-level certificate stored on the device is used to verify the signature to ensure validity of the developer.
In addition, the application source must be verified to ensure that the application is from a valid developer. As a developer, you must apply for a development certificate and use it to sign the application you have developed. During application installation, the upper-level certificate stored on the device is used to verify the signature to ensure validity of the developer.
- Application permission control
OpenHarmony allows users to install third-party applications and controls calls made by third-party applications to sensitive permissions. When developing an application, you need to declare the sensitive permissions that the application may require in the application configuration file. The permissions can be static or dynamic. Static permissions need to be registered during application installation, and dynamic permissions can be obtained only upon user authorization. Authorization modes include system settings, manual authorization by applications, and others. In addition, application signature control is used to ensure that the application installation package has been confirmed by the device vendor.
- Application permission control
**Table 1** OpenHarmony system permissions OpenHarmony allows users to install third-party applications and controls calls made by third-party applications to sensitive permissions. When developing an application, you need to declare the sensitive permissions that the application may require in the application configuration file. The permissions can be static or dynamic. Static permissions need to be registered during application installation, and dynamic permissions can be obtained only upon user authorization. Authorization modes include system settings, manual authorization by applications, and others. In addition, application signature control is used to ensure that the application installation package has been confirmed by the device vendor.
| Permission| **Authorization Mode**| **Description**| **Table 1** OpenHarmony system permissions
| -------- | -------- | -------- |
| ohos.permission.LISTEN_BUNDLE_CHANGE | system_grant (static permission)| Allows an application to listen for application changes.| | Permission| **Authorization Mode**| **Description**|
| ohos.permission.GET_BUNDLE_INFO | system_grant (static permission)| Allows an application to obtain information about other applications.| | -------- | -------- | -------- |
| ohos.permission.INSTALL_BUNDLE | system_grant (static permission)| Allows an application to install other applications.| | ohos.permission.LISTEN_BUNDLE_CHANGE | system_grant (static permission)| Allows an application to listen for application changes.|
| ohos.permission.CAMERA | user_grant (dynamic permission)| Allows an application to use the camera to take photos and record videos at any time.| | ohos.permission.GET_BUNDLE_INFO | system_grant (static permission)| Allows an application to obtain information about other applications.|
| ohos.permission.MODIFY_AUDIO_SETTINGS | system_grant (static permission)| Allows an application to modify global audio settings, such as the volume and speaker for output.| | ohos.permission.INSTALL_BUNDLE | system_grant (static permission)| Allows an application to install other applications.|
| ohos.permission.READ_MEDIA | user_grant (dynamic permission)| Allows an application to read users' favorite videos.| | ohos.permission.CAMERA | user_grant (dynamic permission)| Allows an application to use the camera to take photos and record videos at any time.|
| ohos.permission.MICROPHONE | user_grant (dynamic permission)| Allows an application to use the microphone for audio recording at any time.| | ohos.permission.MODIFY_AUDIO_SETTINGS | system_grant (static permission)| Allows an application to modify global audio settings, such as the volume and speaker for output.|
| ohos.permission.WRITE_MEDIA | user_grant (dynamic permission)| Allows an application to write users' favorite music.| | ohos.permission.READ_MEDIA | user_grant (dynamic permission)| Allows an application to read users' favorite videos.|
| ohos.permission.DISTRIBUTED_DATASYNC | user_grant (dynamic permission)| Allows an application to manage distributed data transmission.| | ohos.permission.MICROPHONE | user_grant (dynamic permission)| Allows an application to use the microphone for audio recording at any time.|
| ohos.permission.DISTRIBUTED_VIRTUALDEVICE | user_grant (dynamic permission)| Allows an application to use distributed virtualization features.| | ohos.permission.WRITE_MEDIA | user_grant (dynamic permission)| Allows an application to write users' favorite music.|
| ohos.permission.DISTRIBUTED_DATASYNC | user_grant (dynamic permission)| Allows an application to manage distributed data transmission.|
| ohos.permission.DISTRIBUTED_VIRTUALDEVICE | user_grant (dynamic permission)| Allows an application to use distributed virtualization features.|
### Recommended Practices ### Recommended Practices
...@@ -217,4 +216,3 @@ When developing an application, determine the permissions required by your appli ...@@ -217,4 +216,3 @@ When developing an application, determine the permissions required by your appli
> **NOTE** > **NOTE**
> >
> The application configuration file varies depending on the application model. It is **config.json** for the application based on the FA model and **module.json5** for the application based on the stage mode. For details about the application models, see [Interpretation of the Application Model](../../application-dev/application-models/application-model-description.md). > The application configuration file varies depending on the application model. It is **config.json** for the application based on the FA model and **module.json5** for the application based on the stage mode. For details about the application models, see [Interpretation of the Application Model](../../application-dev/application-models/application-model-description.md).
# Privacy Protection # Privacy protection
## Overview ## Overview
Personal data plays an increasingly important role in social economy and daily life along with the development of the Internet and informatization. Meanwhile, personal data leakage risks are increasing. As consumer product developers, you shall take more effective measures to protect users' personal data and improve their trust in your products. To protect consumers' privacy and improve their experience on privacy, you should set high-level privacy protection policies for your product. Personal data plays an increasingly important role in social economy and daily life along with the development of the Internet and informatization. Meanwhile, personal data leakage risks are increasing. As a consumer product developer, you shall take more effective measures to protect users' personal data and improve their trust in your products. To protect consumers' privacy and improve their experience on privacy, you should set high-level privacy protection policies for your product.
**Basic Concepts** **Basic Concepts**
- **Personal data** - **Personal data**
Any information relating to an identified or identifiable natural person \("Data Subject"\) who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, commercial, cultural, or social identity of that natural person. Personal data includes a natural person's email address, phone number, biometric information \(such as a fingerprint\), location data, IP address, healthcare information, religious belief, social security number, marital status, and so on. Any information relating to an identified or identifiable natural person \("Data Subject"\) who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location data, an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, commercial, cultural, or social identity of that natural person. Personal data includes a natural person's email address, phone number, biometric information \(such as a fingerprint\), location data, IP address, healthcare information, religious belief, social security number, marital status, and so on.
- **Sensitive personal data** - **Sensitive personal data**
Sensitive personal data, a critical subset of personal data, refers to the most private information of a data subject or information that may cause severe adverse impacts on a data subject once disclosed. Sensitive personal data defined in laws and regulations of EU and its members includes personal data revealing racial or ethnic origin, political opinions, religious or philosophical beliefs, trade-union membership, genetic data, biometric information, and data concerning health or sex life and sexual orientation. Sensitive personal data, as an important subset of personal data, refers to the most confidential information of a data subject or information that may adversely affect the data subject upon leakage. Sensitive personal data defined in laws and regulations of EU and its members includes personal data revealing racial or ethnic origin, political opinions, religious or philosophical beliefs, trade-union membership, genetic data, biometric information, and data concerning health or sex life and sexual orientation.
With reference to industry best practices, we also define the following data related to a natural person's identity as sensitive: bank card number, identification number, passport number, and passwords. More strict protection measures are usually required for processing sensitive personal data. With reference to industry best practices, we also define the following data related to a natural person's identity as sensitive: bank card number, identification number, passport number, and passwords. More strict protection measures are usually required for processing sensitive personal data.
- **Public available personal data** - **Public available personal data**
Personal data that is proactively disclosed by a data subject or that can be accessed on public web pages or applications, including posts and comments made on forums. Personal data that is proactively disclosed by a data subject or that can be accessed on public web pages or applications, including posts and comments made on forums.
- **User profile** - **User profile**
Any form of automated processing of personal data to assess a natural person in specific aspects, and in particular to analyze and predict the natural person's work performance, financial situation, health, personal preference, interest, creditability, behavior, and location or trace. Any form of automated processing of personal data to assess a natural person in specific aspects, and in particular to analyze and predict the natural person's work performance, financial situation, health, personal preference, interest, creditability, behavior, and location or trace.
- **Data controller** - **Data controller**
A natural or legal person, public authority, agency, or any other body that, alone or jointly with others, determines the purposes and means of personal data processing. A natural or legal person, public authority, agency, or any other body that, alone or jointly with others, determines the purposes and means of personal data processing.
- **Data processor** - **Data processor**
A natural or legal person, public authority, agency, or any other body that processes personal data on behalf of a data controller. A data processor must provide adequate protection following the data controller's requirements. A natural or legal person, public authority, agency, or any other body that processes personal data on behalf of a data controller. A data processor must provide adequate protection following the data controller's requirements.
- **Explicit consent** - **Explicit consent**
Explicit consent applies to the following scenarios where the General Data Protection Regulation \(GDPR\) allows the legitimate processing of personal data based on data subjects' explicit consent: Explicit consent applies to the following scenarios where the General Data Protection Regulation (GDPR) allows the legitimate processing of personal data based on data subjects' explicit consent:
- Processing of sensitive personal data - Processing of sensitive personal data
- Automated decision-making, including user profiles - Automated decision-making, including user profiles
- Transfer of personal data to countries without an adequate level of protection, which uses consent as the legal basis - Transfer of personal data to countries without an adequate level of protection, which uses consent as the legal basis
Explicit consent can be implemented as follows: Explicit consent can be implemented as follows:
- In the collection of specific data, display a privacy statement to notify data subjects of matters related to the processing of personal data, provide a check box which is deselected by default, and prompt data subjects to proactively select the option indicating that "I agree to process my personal data in the above manner" or click the "I agree" button. - In the collection of specific data, display a privacy statement to notify data subjects of matters related to the processing of personal data, provide a check box which is deselected by default, and prompt data subjects to proactively select the option indicating that "I agree to process my personal data in the above manner" or click the "I agree" button.
- Expressly present consent in writing and request data subjects to sign it. - Expressly present consent in writing and request data subjects to sign it.
- Ask data subjects to upload an electronic form with their signature in the system. - Ask data subjects to upload an electronic form with their signature in the system.
- Adopt the double verification method by requesting data subjects to consent via an email and then re-click the email link for verification or enter the SMS verification code. - Adopt the double verification method by requesting data subjects to consent via an email and then re-click the email link for verification or enter the SMS verification code.
- Users provide information proactively, such as scenarios where a user enters their identification number and bank card number to bind the bank card. - Users provide information proactively, such as scenarios where a user enters their identification number and bank card number to bind the bank card.
## Data Classification ## Data Classification
Data is classified into five levels: very high, high, moderate, low, and public based on the data protection objectives and consequences \(the impact of legal risks caused by data leakage or damage on individuals, organizations, or the public\). Data is classified into five levels: very high, high, moderate, low, and public based on the data protection objectives and consequences (the impact of legal risks caused by data leakage or damage on individuals, organizations, or the public).
**Table 1** Standards for data classification **Table 1** Standards for data classification
<a name="table1536524010291"></a> | **Level**| **Privacy Risk**| **Privacy Attribute**| **Typical Example**|
<table><thead align="left"><tr id="row444844052917"><th class="cellrowborder" valign="top" width="13.239999999999998%" id="mcps1.1.5.1.1"><p id="p2448740182911"><a name="p2448740182911"></a><a name="p2448740182911"></a><strong id="b2079915010139"><a name="b2079915010139"></a><a name="b2079915010139"></a>Level</strong></p> | -------- | -------- | -------- | -------- |
</th> | Very high| Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a catastrophic negative impact on that individual or group of individuals.| Sensitive personal data| DNA, race, religious belief, and sexual orientation; biometric information; original communication content; bank account password and magnetic track data|
<th class="cellrowborder" valign="top" width="26.05%" id="mcps1.1.5.1.2"><p id="p58515187465"><a name="p58515187465"></a><a name="p58515187465"></a><strong id="b52571858131320"><a name="b52571858131320"></a><a name="b52571858131320"></a>Privacy Risk</strong></p> | High| Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a severe negative impact on that individual or group of individuals.| Sensitive personal data| Social identity (such as ID card and passport number); web browsing history; tracks; content (such as images, audio, and video) uploaded to the cloud|
</th> | Moderate| Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a significant negative impact on that individual or group of individuals.| General personal data| Device ID (IMEI, SN, or OAID), user ID, basic personal information (name and address), mobile number, and email address.|
<th class="cellrowborder" valign="top" width="11.91%" id="mcps1.1.5.1.3"><p id="p2920544155210"><a name="p2920544155210"></a><a name="p2920544155210"></a><strong id="b104071711171410"><a name="b104071711171410"></a><a name="b104071711171410"></a>Privacy Attribute</strong></p> | Low| Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a limited negative impact on that individual or group of individuals.| General personal data| OS settings (including the OS version and country/region); device hardware information (device model, screen size, and screen resolution); network information (network connection status and access network information); device status (login time/duration)|
</th> | Public| Public data has no adverse impact on individuals or organizations.| Non-personal data| Publicly released product introduction, public meeting information, and external open-source code|
<th class="cellrowborder" valign="top" width="48.8%" id="mcps1.1.5.1.4"><p id="p174482040112912"><a name="p174482040112912"></a><a name="p174482040112912"></a><strong id="b192611421412"><a name="b192611421412"></a><a name="b192611421412"></a>Typical Example</strong></p>
</th> > **NOTE**
</tr> >
</thead> > For details about the definitions of privacy protection and data classification, see GDPR.
<tbody><tr id="row1844904062919"><td class="cellrowborder" valign="top" width="13.239999999999998%" headers="mcps1.1.5.1.1 "><p id="p1444934002910"><a name="p1444934002910"></a><a name="p1444934002910"></a>Very high</p>
</td>
<td class="cellrowborder" valign="top" width="26.05%" headers="mcps1.1.5.1.2 "><p id="p1013011148467"><a name="p1013011148467"></a><a name="p1013011148467"></a>Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a catastrophic negative impact on that individual or group of individuals.</p>
</td>
<td class="cellrowborder" valign="top" width="11.91%" headers="mcps1.1.5.1.3 "><p id="p1992019442526"><a name="p1992019442526"></a><a name="p1992019442526"></a>Sensitive personal data</p>
</td>
<td class="cellrowborder" valign="top" width="48.8%" headers="mcps1.1.5.1.4 "><p id="p544974014295"><a name="p544974014295"></a><a name="p544974014295"></a>DNA, race, religious belief, and sexual orientation; biometric information; original communication content; bank account password and magnetic track data</p>
</td>
</tr>
<tr id="row8449164062911"><td class="cellrowborder" valign="top" width="13.239999999999998%" headers="mcps1.1.5.1.1 "><p id="p24491040192916"><a name="p24491040192916"></a><a name="p24491040192916"></a>High</p>
</td>
<td class="cellrowborder" valign="top" width="26.05%" headers="mcps1.1.5.1.2 "><p id="p101301214144615"><a name="p101301214144615"></a><a name="p101301214144615"></a>Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a severe negative impact on that individual or group of individuals.</p>
</td>
<td class="cellrowborder" valign="top" width="11.91%" headers="mcps1.1.5.1.3 "><p id="p1592084417525"><a name="p1592084417525"></a><a name="p1592084417525"></a>Sensitive personal data</p>
</td>
<td class="cellrowborder" valign="top" width="48.8%" headers="mcps1.1.5.1.4 "><p id="p74501400297"><a name="p74501400297"></a><a name="p74501400297"></a>Social identity (such as ID card and passport number); web browsing history; tracks; content (such as images, audio, and video) uploaded to the cloud</p>
</td>
</tr>
<tr id="row1745074020293"><td class="cellrowborder" valign="top" width="13.239999999999998%" headers="mcps1.1.5.1.1 "><p id="p1145074072915"><a name="p1145074072915"></a><a name="p1145074072915"></a>Moderate</p>
</td>
<td class="cellrowborder" valign="top" width="26.05%" headers="mcps1.1.5.1.2 "><p id="p31311514124618"><a name="p31311514124618"></a><a name="p31311514124618"></a>Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a significant negative impact on that individual or group of individuals.</p>
</td>
<td class="cellrowborder" valign="top" width="11.91%" headers="mcps1.1.5.1.3 "><p id="p1092116445529"><a name="p1092116445529"></a><a name="p1092116445529"></a>General personal data</p>
</td>
<td class="cellrowborder" valign="top" width="48.8%" headers="mcps1.1.5.1.4 "><p id="p945144022910"><a name="p945144022910"></a><a name="p945144022910"></a>Device ID (such as IMEI, SN, and OAID) and user ID; basic personal information (name and address); mobile number and email address</p>
</td>
</tr>
<tr id="row144512407291"><td class="cellrowborder" valign="top" width="13.239999999999998%" headers="mcps1.1.5.1.1 "><p id="p145110408294"><a name="p145110408294"></a><a name="p145110408294"></a>Low</p>
</td>
<td class="cellrowborder" valign="top" width="26.05%" headers="mcps1.1.5.1.2 "><p id="p61311514154618"><a name="p61311514154618"></a><a name="p61311514154618"></a>Once data is identified or associated with an individual or group of individuals, its disclosure or improper use may have a limited negative impact on that individual or group of individuals.</p>
</td>
<td class="cellrowborder" valign="top" width="11.91%" headers="mcps1.1.5.1.3 "><p id="p29212442526"><a name="p29212442526"></a><a name="p29212442526"></a>General personal data</p>
</td>
<td class="cellrowborder" valign="top" width="48.8%" headers="mcps1.1.5.1.4 "><p id="p945217401298"><a name="p945217401298"></a><a name="p945217401298"></a>OS settings (including the OS version and country/region); device hardware information (device model, screen size, and screen resolution); network information (network connection status and access network information); device status (login time/duration)</p>
</td>
</tr>
<tr id="row135851553268"><td class="cellrowborder" valign="top" width="13.239999999999998%" headers="mcps1.1.5.1.1 "><p id="p55864518261"><a name="p55864518261"></a><a name="p55864518261"></a>Public</p>
</td>
<td class="cellrowborder" valign="top" width="26.05%" headers="mcps1.1.5.1.2 "><p id="p135861254267"><a name="p135861254267"></a><a name="p135861254267"></a>Public data has no adverse impact on individuals or organizations.</p>
</td>
<td class="cellrowborder" valign="top" width="11.91%" headers="mcps1.1.5.1.3 "><p id="p1921144115218"><a name="p1921144115218"></a><a name="p1921144115218"></a>Non-personal data</p>
</td>
<td class="cellrowborder" valign="top" width="48.8%" headers="mcps1.1.5.1.4 "><p id="p1058612512613"><a name="p1058612512613"></a><a name="p1058612512613"></a>Publicly released product introduction, public meeting information, and external open-source code</p>
</td>
</tr>
</tbody>
</table>
Note: For details about the definitions of privacy protection and data classification, see GDPR.
## General Privacy Design Rules ## General Privacy Design Rules
To help you better complete privacy design for OpenHarmony products, we sort out general privacy design requirements. Observe the following general privacy design requirements when designing your OpenHarmony products:
**Openness and Transparency in Data Collection and Use** **Openness and Transparency in Data Collection and Use**
When collecting personal data, clearly and explicitly notify users of the data to collect and how their personal data will be used. When collecting personal data, clearly and explicitly notify users of the data to collect and how their personal data will be used.
- Develop specific privacy processing policies for personal data at different levels. - Develop specific privacy processing policies for personal data at different levels.
- Explicit consent shall be obtained from the data subject before your product attempts to collect sensitive personal data.
- Generally, the collection of personal data requires the consent of the data subject or other legal authorizations.
- If non-personal data is to be collected in association with personal data at the moderate, high, or very high level, the data subject's consent or other legal authorization is required, and the consent or authorization shall be presented in the privacy statement.
- Develop and follow appropriate privacy policies. Comply with all applicable laws, policies, and regulations when collecting, using, retaining, and sharing users' personal data with any third parties. For example, prior to data collection, fully inform users of the types, purposes, processing methods, and retention periods of personal data to meet the requirements of data subjects' rights. - Explicit consent shall be obtained from the data subject before your product attempts to collect sensitive personal data.
- Generally, the collection of personal data requires the consent of the data subject or other legal authorizations.
- If non-personal data is to be collected in association with personal data at the moderate, high, or very high level, the data subject's consent or other legal authorization is required, and the consent or authorization shall be presented in the privacy statement.
- Develop and follow appropriate privacy policies. Comply with all applicable laws, policies, and regulations when collecting, using, retaining, and sharing users' personal data with any third parties. For example, prior to data collection, fully inform users of the types, purposes, processing methods, and retention periods of personal data to meet the requirements of data subjects' rights.
Guided by the preceding principles, we have designed some examples for your reference. The figures below are examples of a privacy notice and a privacy statement, respectively. Guided by the preceding principles, we have designed some examples for your reference. The figures below are examples of a privacy notice and a privacy statement, respectively.
**Figure 1** Examples of a privacy notice and a privacy statement **Figure 1** Example of a privacy notice
![](figures/privacy-notice.png)![](figures/privacy-statement.png) ![](figures/privacy-notice.png)
- Personal data shall be collected for specified, explicit, and legitimate purposes and not further processed in a manner that is incompatible with those purposes. If the purposes are changed or a user withdraws their consent, you shall obtain user consent again before using the data. The figures below are examples of a privacy statement update and content withdrawal, respectively.
**Figure 2** Example dialog showing a privacy statement update<a name="fig3591610523"></a> **Figure 2** Example of a privacy statement
![](figures/privacy-statement.png)
- Personal data shall be collected for specified, explicit, and legitimate purposes and not further processed in a manner that is incompatible with those purposes. If the purposes are changed or a user withdraws their consent, you shall obtain user consent again before using the data. The figures below are examples of a privacy statement update and content withdrawal, respectively.
**Figure 3** Example dialog showing a privacy notice or statement update
![](figures/privacy-statement-update.png) ![](figures/privacy-statement-update.png)
**Figure 3** Example dialog showing consent withdrawal<a name="fig12802152515583"></a> **Figure 4** Example dialog showing consent withdrawal
![](figures/consent-withdrawal-1.png)![](figures/consent-withdrawal-2.png) ![](figures/consent-withdrawal-1.png)
- You shall provide an entry for users to view the privacy statement. For example, you can provide an entry on the **About** page of your application to view the privacy statement, as shown in the following figure. **Figure 5** Example dialog showing consent withdrawal
**Figure 4** Example of About page providing an entry to the privacy statement<a name="fig11392538162011"></a> ![](figures/consent-withdrawal-2.png)
- You shall provide an entry for users to view the privacy statement. For example, you can provide an entry on the **About** page of your application to view the privacy statement, as shown in the following figure.
![](figures/privacy-statement-entry.png) **Figure 6** Example of About page providing an entry to the privacy statement
![](figures/privacy-statement-entry.png)
**Minimization in Data Collection and Use** **Minimization in Data Collection and Use**
Collect personal data only when they are adequate, relevant, and limited to what is necessary in relation to the purposes for which they are processed. Apply anonymization or pseudonymization to personal data if possible to reduce the risks to the data subjects concerned. Data shall only be collected and processed for a specified purpose, and no further unnecessary operations shall be conducted on them. Collect personal data only when they are adequate, relevant, and limited to what is necessary in relation to the purposes for which they are processed. Apply anonymization or pseudonymization to personal data if possible to reduce the risks to the data subjects concerned. Data shall only be collected and processed for a specified purpose, and no further unnecessary operations shall be conducted on them.
- When applying for sensitive permissions, adhere to permission minimization and apply for only the permissions required for obtaining necessary information or resources. For example, if your application can implement its functions without access to the camera, then it shall not request the user for the camera permission. - When applying for sensitive permissions, adhere to permission minimization and apply for only the permissions required for obtaining necessary information or resources. For example, if your application can implement its functions without access to the camera, then it shall not request the user for the camera permission.
- Comply with data collection minimization, and do not collect data irrelevant to services provided by the product. For example, a product that provides location services shall not collect users' web browsing history.
- The functions that use personal data shall be able to benefit users. The collected data shall not be used for functions irrelevant to users' normal use. No data shall be collected for any functions irrelevant to user operations. For example, sensitive personal data, such as biometric features and health data, shall not be used for non-core service functions like service improvement, advertising, and marketing.
- Printing sensitive personal data in logs is prohibited. If common personal data needs to be printed in logs, make sure the data is anonymized or pseudonymized.
Preferentially use identifiers that are resettable. For example, use the NetworkID and DVID as the device identifier in the distributed scenario; use the [OAID](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/oaid-0000001050783198) in the advertising scenario; use the [ODID](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/odid-0000001051063255) and [AAID](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/aaid-0000001051142988) in the application-based analysis scenario; and use the UUID in other scenarios where a unique identifier is required. Use permanent identifiers such as the sequence number and MAC address only when resettable identifiers cannot meet your service requirements. - Comply with data collection minimization, and do not collect data irrelevant to services provided by the product. For example, a product that provides location services shall not collect users' web browsing history.
- The functions that use personal data shall be able to benefit users. The collected data shall not be used for functions irrelevant to users' normal use. No data shall be collected for any function irrelevant to user operations. For example, sensitive personal data, such as biometric features and health data, shall not be used for non-core service functions like service improvement, advertising, and marketing.
- Printing sensitive personal data in logs is prohibited. If common personal data needs to be printed in logs, make sure the data is anonymized or pseudonymized.
Preferentially use identifiers that are resettable. For example, use the NetworkID and DVID as the device identifier in the distributed scenario; use the OAID in the advertising scenario; use the ODID and AAID in the application-based analysis scenario; and use the UUID in other scenarios where a unique identifier is required. Use permanent identifiers such as the sequence number and MAC address only when resettable identifiers cannot meet your service requirements.
**Data Processing Selection and Control** **Data Processing Selection and Control**
You shall obtain consent from users and comply with applicable laws and regulations for processing personal data and give users full control over their data. You shall obtain consent from users and comply with applicable laws and regulations for processing personal data and give users full control over their data.
- When applying for a certain sensitive permission, your product shall display a pop-up dialog to notify the user of the requested permission and the purpose of using the permission. The user shall be able to choose whether to grant the permission and how they would like to grant the permission. This ensures that permission granting and use are transparent, perceivable, and controllable. The following figure is an example dialog for requesting a sensitive permission. - When applying for a certain sensitive permission, your product shall display a pop-up dialog to notify the user of the requested permission and the purpose of using the permission. The user shall be able to choose whether to grant the permission and how they would like to grant the permission. This ensures that permission granting and use are transparent, perceivable, and controllable. The following figure is an example dialog for requesting a sensitive permission.
**Figure 5** Example dialog for requesting a sensitive permission<a name="fig20776134521514"></a> **Figure 7** Example dialog for requesting a sensitive permission
![](figures/sensitive-permission-request.png) ![](figures/sensitive-permission-request.png)
- Users shall have the right to modify or withdraw the permissions granted to your product. When a user does not agree to a permission or data collection, the user shall be allowed to use the functions irrelevant to the permission or data collection. For example, the user can refuse to grant the camera permission to social or communication apps on Smart TVs, when using product functions irrelevant to the camera, such as voice calls. - Users shall have the right to modify or withdraw the permissions granted to your product. When a user does not agree to a permission or data collection, the user shall be allowed to use the functions irrelevant to the permission or data collection. For example, the user can refuse to grant the camera permission to social or communication apps on Smart TVs, when using product functions irrelevant to the camera, such as voice calls.
- In scenarios where personal data is recorded, users shall be provided with the functions of adding, deleting, modifying, and viewing personal data.
- Your products shall provide a mechanism or method for securely deleting personal data when hardware is recycled or returned to the factory. - In scenarios where personal data is recorded, users shall be provided with the functions of adding, deleting, modifying, and viewing personal data.
- The download or upgrade of user system software or application software may involve the modification of users' private space. Users shall have the right to know and control such behavior. They shall be informed of such behavior and be given the option to agree or disagree with such behavior.
- Your products shall provide a mechanism or method for securely deleting personal data when hardware is recycled or returned to the factory.
- The download or upgrade of user system software or application software may involve the modification of users' private space. Users shall have the right to know and control such behavior. They shall be informed of such behavior and be given the option to agree or disagree with such behavior.
**Data Security** **Data Security**
Data processing security shall be ensured in technical terms, which include encrypted data storage and secure data transfer. Security mechanisms or measures shall be enabled by default for a system. Data processing security shall be ensured in technical terms, which include encrypted storage and secure transmission. Security mechanisms or measures shall be enabled by default for a system.
- A protection mechanism shall be available for personal data access, including identity authentication and access control. Identity authentication (such as user name and password) allows only authenticated users to access data in multi-user scenarios. Access control, such as [permission control](../security/security-guidelines-overall.md) can be used to restrict certain applications.
- Secure storage of personal data on distributed devices must meet Huawei Universal KeyStore (HUKS) requirements, including secure storage of keys and data.
- A protection mechanism shall be available for personal data access, including identity authentication and access control. Identity authentication \(such as username and password\) allows only authenticated users to access data in multi-user scenarios. Access control \(for example, [permission control](../security/security-guidelines-overall.md)\) can be applied to restrict access to applications. - The transfer of personal data between distributed devices must meet the trust binding relationship between devices and security requirements of data transmission channels. For details, see [Device Interconnection Security](../security/security-guidelines-overall.md#device-interconnection-security).
- Secure storage of personal data on distributed devices must meet Huawei Universal Keystore \(HUKS\) requirements, including secure storage of keys and data.
- The transfer of personal data between distributed devices must meet the trust binding relationship between devices and security requirements of data transmission channels. For details, see [Device Interconnection Security](../security/security-guidelines-overall.md#device-interconnection-security). - Authentication data (such as passwords and fingerprints) shall be encrypted before being stored.
- Authentication data \(such as passwords and fingerprints\) shall be encrypted before being stored.
**Localization** **Localization**
User data shall be preferentially processed on the local device. Data that cannot be processed on the local device shall be preferentially processed on Device+ \(super device in the distributed scenario\). If any data cannot be processed on Device+, the data shall be anonymized before being transferred out of Device+ for processing. User data shall be preferentially processed on the local device. Data that cannot be processed on the local device shall be preferentially processed on a device of the Super Device. If any data cannot be processed on the Super Device, the data shall be anonymized before being transferred out of Super Device for processing.
**Minors' Data Protection** **Minors' Data Protection**
If your product is designed for minors or you can identify, based on the collected user age data, that the end user is a minor, you shall particularly analyze issues related to minors' personal data protection based on relevant national laws in the target market. Your product shall obtain explicit consent from the holders of parental responsibility over minors. If your product is designed for minors or you can identify, based on the collected user age data, that the end user is a minor, you shall particularly analyze issues related to minors' personal data protection based on relevant local laws in the target market. Your product shall obtain explicit consent from the holders of parental responsibility over minors.
## **Privacy Protection Requirements for Special Categories** ## **Privacy Protection Requirements for Special Categories**
In addition to these general privacy requirements, consumer hardware products have the following requirements for special categories. You shall comply with these requirements during product design. In addition to these general privacy requirements, consumer hardware products have the following requirements for special categories. You shall comply with these requirements during product design.
**Table 2** Privacy protection requirements for special categories **Table 2** Privacy protection requirements for special categories
<a name="table17143732123612"></a>
<table><thead align="left"><tr id="row1943617174010"><th class="cellrowborder" valign="top" width="23%" id="mcps1.1.3.1.1"><p id="p1194461794016"><a name="p1194461794016"></a><a name="p1194461794016"></a>Product Category</p>
</th>
<th class="cellrowborder" valign="top" width="77%" id="mcps1.1.3.1.2"><p id="p1894481764012"><a name="p1894481764012"></a><a name="p1894481764012"></a>Privacy Protection Requirements</p>
</th>
</tr>
</thead>
<tbody><tr id="row918893210367"><td class="cellrowborder" valign="top" width="23%" headers="mcps1.1.3.1.1 "><p id="p718853216369"><a name="p718853216369"></a><a name="p718853216369"></a><strong id="b1150122319282"><a name="b1150122319282"></a><a name="b1150122319282"></a>Smart home</strong></p>
</td>
<td class="cellrowborder" valign="top" width="77%" headers="mcps1.1.3.1.2 "><p id="p81884329366"><a name="p81884329366"></a><a name="p81884329366"></a>Biometric information (such as fingerprints, voiceprints, facial recognition, and irises) and user passwords involved in security products are sensitive personal data. They shall be processed using technical measures (for example, extracting the digest of biometric information) before being encrypted and stored in the products.</p>
</td>
</tr>
<tr id="row19189103223618"><td class="cellrowborder" valign="top" width="23%" headers="mcps1.1.3.1.1 "><p id="p201891632163610"><a name="p201891632163610"></a><a name="p201891632163610"></a><strong id="b16184636153611"><a name="b16184636153611"></a><a name="b16184636153611"></a>Smart home</strong></p>
</td>
<td class="cellrowborder" valign="top" width="77%" headers="mcps1.1.3.1.2 "><p id="p18189173243610"><a name="p18189173243610"></a><a name="p18189173243610"></a>For security products that involve audio, video, and images, their manufacturers, functioning as the data controller, shall provide an independent privacy notification and a brand log on their application UI. Transfer and storage of audio and video data shall be encrypted. Access to audio and video data of security products is permitted only after being authorized by the data subject.</p>
</td>
</tr>
<tr id="row121891432163614"><td class="cellrowborder" valign="top" width="23%" headers="mcps1.1.3.1.1 "><p id="p121892320363"><a name="p121892320363"></a><a name="p121892320363"></a><strong id="b670294616282"><a name="b670294616282"></a><a name="b670294616282"></a>Smart home/Entertainment</strong></p>
</td>
<td class="cellrowborder" valign="top" width="77%" headers="mcps1.1.3.1.2 "><p id="p51891432183615"><a name="p51891432183615"></a><a name="p51891432183615"></a>Cameras on products should be able to be physically disabled. For example, cameras can be hidden, shuttered, or re-oriented so that consumers can perceive that the cameras are disabled.</p>
</td>
</tr>
<tr id="row1218903210364"><td class="cellrowborder" valign="top" width="23%" headers="mcps1.1.3.1.1 "><p id="p5189632163616"><a name="p5189632163616"></a><a name="p5189632163616"></a><strong id="b1067022117448"><a name="b1067022117448"></a><a name="b1067022117448"></a>Smart home/Entertainment</strong></p>
</td>
<td class="cellrowborder" valign="top" width="77%" headers="mcps1.1.3.1.2 "><p id="p111891632153617"><a name="p111891632153617"></a><a name="p111891632153617"></a>Products with a microphone should provide an explicit display of the recording status. For example, the products can provide a status indicator that blinks when recording is started and turns off when recording is stopped.</p>
</td>
</tr>
<tr id="row31891932173619"><td class="cellrowborder" valign="top" width="23%" headers="mcps1.1.3.1.1 "><p id="p11891132163618"><a name="p11891132163618"></a><a name="p11891132163618"></a><strong id="b12223353122815"><a name="b12223353122815"></a><a name="b12223353122815"></a>Mobile office</strong></p>
</td>
<td class="cellrowborder" valign="top" width="77%" headers="mcps1.1.3.1.2 "><p id="p10189632183612"><a name="p10189632183612"></a><a name="p10189632183612"></a>In scenarios such as cross-device display and transfer of user data, your products shall obtain explicit consent from users and give them full control over their personal data.</p>
</td>
</tr>
<tr id="row1189232103617"><td class="cellrowborder" valign="top" width="23%" headers="mcps1.1.3.1.1 "><p id="p16189632183619"><a name="p16189632183619"></a><a name="p16189632183619"></a><strong id="b1225245962810"><a name="b1225245962810"></a><a name="b1225245962810"></a>In-vehicle infotainment (IVI)</strong></p>
</td>
<td class="cellrowborder" valign="top" width="77%" headers="mcps1.1.3.1.2 "><p id="p1018983263615"><a name="p1018983263615"></a><a name="p1018983263615"></a>1. Privacy notice and permission settings</p>
<p id="p1618914325366"><a name="p1618914325366"></a><a name="p1618914325366"></a>Do not let users read privacy policies and permission settings in the driving state.</p>
<p id="p1818973263617"><a name="p1818973263617"></a><a name="p1818973263617"></a>IVI applications shall consider the safety of vehicle use. The applications shall not require complex permission settings or reading of privacy policies when users are driving. For example, HiCar is usable only after users have set basic permissions and read privacy policies on their mobile phone.</p>
<p id="p518973210368"><a name="p518973210368"></a><a name="p518973210368"></a>The privacy statement shall be notified after the user identity is confirmed.</p>
<p id="p6189132113615"><a name="p6189132113615"></a><a name="p6189132113615"></a>Vehicle data involves vehicle owners, drivers, and passengers. The data subject shall be notified of the privacy statement. The recommended practice is to make a privacy statement after confirming the user identity. For an application that requires login, the privacy statement should be displayed after, instead of before, a user is logged in.</p>
<p id="p1818914329368"><a name="p1818914329368"></a><a name="p1818914329368"></a>2. Personal data protection for sharing applications</p>
<p id="p191892032103620"><a name="p191892032103620"></a><a name="p191892032103620"></a>Shared applications shall exit after the IVI is restarted, and the personal data of the current user shall be cleared or encrypted. The applications shall also provide the function to permanently delete historical data.</p>
<p id="p4189153213611"><a name="p4189153213611"></a><a name="p4189153213611"></a>3. Notifications</p>
<p id="p2189432103613"><a name="p2189432103613"></a><a name="p2189432103613"></a>As the IVI is used in an open environment, applications shall not directly display the message content on the IVI. Instead, the applications shall only notify users that there is an incoming message.</p>
</td>
</tr>
</tbody>
</table>
| **Product Category**| Privacy Protection Requirements|
| -------- | -------- |
| **Smart home**| Biometric information (such as fingerprints, voiceprints, facial recognition, and irises) and user passwords involved in security products are sensitive personal data. They shall be processed using technical measures (for example, extracting the digest of biometric information) before being encrypted and stored in the products.|
| **Smart home**| For security products that involve audio, video, and images, their manufacturers, functioning as the data controller, shall provide an independent privacy notification and a brand log on their application UI. Transfer and storage of audio and video data shall be encrypted. Access to audio and video data of security products is permitted only after being authorized by the data subject.|
| **Smart home/Entertainment**| Cameras on products should be able to be physically disabled. For example, cameras can be hidden, shuttered, or re-oriented so that consumers can perceive that the cameras are disabled.|
| **Smart home/Entertainment**| Products with a microphone should provide an explicit display of the recording status. For example, the products can provide a status indicator that blinks when recording is started and turns off when recording is stopped.|
| **Mobile office**| In scenarios such as cross-device display and transfer of user data, your products shall obtain explicit consent from users and give them full control over their personal data.|
| **Head unit**| 1. Privacy notice and permission settings<br>Do not let users read privacy policies and permission settings in the driving state.<br>IVI applications shall consider the safety of vehicle use. The applications shall not require complex permission settings or reading of privacy policies when users are driving. For example, HiCar is usable only after users have set basic permissions and read privacy policies on their mobile phone.<br>The privacy statement shall be notified after the user identity is confirmed.<br>Vehicle data involves vehicle owners, drivers, and passengers. The data subject shall be notified of the privacy statement. The recommended practice is to make a privacy statement after confirming the user identity. For an application that requires login, the privacy statement should be displayed after, instead of before, a user is logged in.<br>2. Personal data protection for sharing applications<br>Shared applications shall exit after the IVI is restarted, and the personal data of the current user shall be cleared or encrypted. The applications shall also provide the function to permanently delete historical data.<br>3. Notifications<br>As the IVI is used in an open environment, applications shall not directly display the message content on the IVI. Instead, the applications shall only notify users that there is an incoming message.|
...@@ -35,7 +35,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A ...@@ -35,7 +35,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A
``` ```
2. 对XML文件编码后调用XmlPullParser。 2. 对XML文件编码后调用XmlPullParser。
可以基于Arraybuffer构造XmlPullParser对象, 也可以基于DataView构造XmlPullParser对象。 可以基于ArrayBuffer构造XmlPullParser对象, 也可以基于DataView构造XmlPullParser对象。
```js ```js
...@@ -47,7 +47,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A ...@@ -47,7 +47,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A
'</note>'; '</note>';
let textEncoder = new util.TextEncoder(); let textEncoder = new util.TextEncoder();
let arrBuffer = textEncoder.encodeInto(strXml); // 对数据编码,防止包含中文字符乱码 let arrBuffer = textEncoder.encodeInto(strXml); // 对数据编码,防止包含中文字符乱码
// 1.基于Arraybuffer构造XmlPullParser对象 // 1.基于ArrayBuffer构造XmlPullParser对象
let that = new xml.XmlPullParser(arrBuffer.buffer, 'UTF-8'); let that = new xml.XmlPullParser(arrBuffer.buffer, 'UTF-8');
// 2.基于DataView构造XmlPullParser对象 // 2.基于DataView构造XmlPullParser对象
...@@ -62,7 +62,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A ...@@ -62,7 +62,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A
function func(name, value){ function func(name, value){
str = name + value; str = name + value;
console.info(str); console.info(str);
return true; //true:继续解析 flase:停止解析 return true; //true:继续解析 false:停止解析
} }
``` ```
...@@ -118,7 +118,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A ...@@ -118,7 +118,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A
let str = ''; let str = '';
function func(name, value){ function func(name, value){
str += name + ' ' + value + ' '; str += name + ' ' + value + ' ';
return true; // true:继续解析 flase:停止解析 return true; // true:继续解析 false:停止解析
} }
``` ```
...@@ -167,7 +167,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A ...@@ -167,7 +167,7 @@ XML模块提供XmlPullParser类对XML文件解析,输入为含有XML文本的A
function func(name, value){ function func(name, value){
str = name + ' ' + value.getDepth(); // getDepth 获取元素的当前深度 str = name + ' ' + value.getDepth(); // getDepth 获取元素的当前深度
console.info(str) console.info(str)
return true; //true:继续解析 flase:停止解析 return true; //true:继续解析 false:停止解析
} }
``` ```
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
return; return;
} }
console.info('Succeeded in getting preferences.'); console.info('Succeeded in getting preferences.');
// 进行相关数据操作 // 请确保获取到Preferences实例后,再进行相关数据操作
}) })
} catch (err) { } catch (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`); console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
return; return;
} }
console.info('Succeeded in getting preferences.'); console.info('Succeeded in getting preferences.');
// 进行相关数据操作 // 请确保获取到Preferences实例后,再进行相关数据操作
}) })
} catch (err) { } catch (err) {
console.error(`Failed to get preferences. Code is ${err.code},message:${err.message}`); console.error(`Failed to get preferences. Code is ${err.code},message:${err.message}`);
......
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
store.version = 3; store.version = 3;
} }
// 这里执行数据库的增、删、改、查等操作 // 请确保获取到RdbStore实例后,再进行数据库的增、删、改、查等操作
}); });
} }
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
store.version = 3; store.version = 3;
} }
// 这里执行数据库的增、删、改、查等操作 // 请确保获取到RdbStore实例后,再进行数据库的增、删、改、查等操作
}); });
``` ```
......
...@@ -171,7 +171,7 @@ ...@@ -171,7 +171,7 @@
return; return;
} }
console.info('Succeeded in getting KVStore.'); console.info('Succeeded in getting KVStore.');
// 进行相关数据操作 // 请确保获取到键值数据库实例后,再进行相关数据操作
}); });
} catch (e) { } catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`); console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
......
...@@ -125,9 +125,9 @@ if (isSupported) { ...@@ -125,9 +125,9 @@ if (isSupported) {
| 接口 | 说明 | | 接口 | 说明 |
| ---- | ---- | | ---- | ---- |
| isPreLaunchSupported(camera: CameraDevice) : boolean | 判断指定cameraDevice是否支持预热启动。 | | isPrelaunchSupported(camera: CameraDevice) : boolean | 判断指定cameraDevice是否支持预热启动。 |
| setPreLaunchConfig(preLaunchConfig: PreLaunchConfig) : void | 配置相机预热参数。 | | setPrelaunchConfig(prelaunchConfig: PrelaunchConfig) : void | 配置相机预热参数。 |
| preLaunch() : void | 用户点击系统相机图标,拉起相机应用的同时调用,下发预热请求,使能相机预热启动。 | | prelaunch() : void | 用户点击系统相机图标,拉起相机应用的同时调用,下发预热请求,使能相机预热启动。 |
### 开发示例 ### 开发示例
...@@ -142,7 +142,7 @@ if (isSupported) { ...@@ -142,7 +142,7 @@ if (isSupported) {
this.cameraManager = camera.getCameraManager(globalThis.abilityContext); this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
try { try {
this.cameraManager.preLaunch(); this.cameraManager.prelaunch();
} catch (error) { } catch (error) {
console.error(`catch error: Code: ${error.code}, message: ${error.message}`) console.error(`catch error: Code: ${error.code}, message: ${error.message}`)
} }
...@@ -159,9 +159,9 @@ if (isSupported) { ...@@ -159,9 +159,9 @@ if (isSupported) {
this.cameraManager = camera.getCameraManager(globalThis.abilityContext); this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
let cameras = this.cameraManager.getSupportedCameras() let cameras = this.cameraManager.getSupportedCameras()
if(this.cameraManager.isPreLaunchSupported(cameras[0])) { if(this.cameraManager.isPrelaunchSupported(cameras[0])) {
try { try {
this.cameraManager.setPreLaunchConfig({cameraDevice: cameras[0]}); this.cameraManager.setPrelaunchConfig({cameraDevice: cameras[0]});
} catch (error) { } catch (error) {
console.error(`catch error: Code: ${error.code}, message: ${error.message}`) console.error(`catch error: Code: ${error.code}, message: ${error.message}`)
} }
......
...@@ -74,8 +74,11 @@ ...@@ -74,8 +74,11 @@
- [其他状态管理概述](arkts-other-state-mgmt-functions-overview.md) - [其他状态管理概述](arkts-other-state-mgmt-functions-overview.md)
- [\@Watch装饰器:状态变量更改通知](arkts-watch.md) - [\@Watch装饰器:状态变量更改通知](arkts-watch.md)
- [$$语法:内置组件双向同步](arkts-two-way-sync.md) - [$$语法:内置组件双向同步](arkts-two-way-sync.md)
- [MVVM模式](arkts-mvvm.md)
- [状态管理优秀实践](arkts-state-management-best-practices.md)
- 渲染控制 - 渲染控制
- [渲染控制概述](arkts-rendering-control-overview.md) - [渲染控制概述](arkts-rendering-control-overview.md)
- [if/else:条件渲染](arkts-rendering-control-ifelse.md) - [if/else:条件渲染](arkts-rendering-control-ifelse.md)
- [ForEach:循环渲染](arkts-rendering-control-foreach.md) - [ForEach:循环渲染](arkts-rendering-control-foreach.md)
- [LazyForEach:数据懒加载](arkts-rendering-control-lazyforeach.md) - [LazyForEach:数据懒加载](arkts-rendering-control-lazyforeach.md)
- [渲染控制优秀实践](arkts-rendering-control-best-practices.md)
# MVVM模式
应用通过状态去渲染更新UI是程序设计中相对复杂,但又十分重要的,往往决定了应用程序的性能。程序的状态数据通常包含了数组、对象,或者是嵌套对象组合而成。在这些情况下,ArkUI采取MVVM = Model + View + ViewModel模式,其中状态管理模块起到的就是ViewModel的作用,将数据与视图绑定在一起,更新数据的时候直接更新视图。
- Model层:存储数据和相关逻辑的模型。它表示组件或其他相关业务逻辑之间传输的数据。Model是对原始数据的进一步处理。
- View层:在ArkUI中通常是\@Components修饰组件渲染的UI。
- ViewModel层:在ArkUI中,ViewModel是存储在自定义组件的状态变量、LocalStorage和AppStorage中的数据。
- 自定义组件通过执行其build()方法或者\@Builder装饰的方法来渲染UI,即ViewModel可以渲染View。
- View可以通过相应event handler来改变ViewModel,即事件驱动ViewModel的改变,另外ViewModel提供了\@Watch回调方法用于监听状态数据的改变。
- 在ViewModel被改变时,需要同步回Model层,这样才能保证ViewModel和Model的一致性,即应用自身数据的一致性。
- ViewModel结构设计应始终为了适配自定义组件的构建和更新,这也是将Model和ViewModel分开的原因。
目前很多关于UI构造和更新的问题,都是由于ViewModel的设计并没有很好的支持自定义组件的渲染,或者试图去让自定义组件强行适配Model层,而中间没有用ViewModel来进行分离。例如,一个应用程序直接将SQL数据库中的数据读入内存,这种数据模型不能很好的直接适配自定义组件的渲染,所以在应用程序开发中需要适配ViewModel层。
![zh-cn_image_0000001653986573](figures/zh-cn_image_0000001653986573.png)
根据上面涉及SQL数据库的示例,应用程序应设计为:
- Model:针对数据库高效操作的数据模型。
- ViewModel:针对ArkUI状态管理功能进行高效的UI更新的视图模型。
- 部署 converters/adapters: converters/adapters作用于Model和ViewModel的相互转换。
- converters/adapters可以转换最初从数据库读取的Model,来创建并初始化ViewModel。
- 在应用的使用场景中,UI会通过event handler改变ViewModel,此时converters/adapters需要将ViewModel的更新数据同步回Model。
虽然与强制将UI拟合到SQL数据库模式(MV模式)相比,MVVM的设计比较复杂,但应用程序开发人员可以通过ViewModel层的隔离,来简化UI的设计和实现,以此来收获更好的UI性能。
## ViewModel的数据源
ViewModel通常包含多个顶层数据源。\@State和\@Provide装饰的变量以及LocalStorage和AppStorage都是顶层数据源,其余装饰器都是与数据源做同步的数据。装饰器的选择取决于状态需要在自定义组件之间的共享范围。共享范围从小到大的排序是:
- \@State:组件级别的共享,通过命名参数机制传递,例如:CompA: ({ aProp: this.aProp }),表示传递层级(共享范围)是父子之间的传递。
- \@Provide:组件级别的共享,可以通过key和\@Consume绑定,因此不用参数传递,实现多层级的数据共享,共享范围大于\@State。
- LocalStorage:页面级别的共享,可以通过\@Entry在当前组件树上共享LocalStorage实例。
- AppStorage:应用全局的UI状态存储,和应用进程绑定,在整个应用内的状态数据的共享。
### \@State装饰的变量与一个或多个子组件共享状态数据
\@State可以初始化多种状态变量,\@Prop、\@Link和\@ObjectLink可以和其建立单向或双向同步,详情见[@State使用规范](arkts-state.md)
1. 使用Parent根节点中\@State装饰的testNum作为ViewModel数据项。将testNum传递给其子组件LinkChild和Sibling。
```ts
// xxx.ets
@Entry
@Component
struct Parent {
@State @Watch("testNumChange1") testNum: number = 1;
testNumChange1(propName: string): void {
console.log(`Parent: testNumChange value ${this.testNum}`)
}
build() {
Column() {
LinkChild({ testNum: $testNum })
Sibling({ testNum: $testNum })
}
}
}
```
2. LinkChild和Sibling中用\@Link和父组件的数据源建立双向同步。其中LinkChild中创建了LinkLinkChild和PropLinkChild。
```ts
@Component
struct Sibling {
@Link @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`Sibling: testNumChange value ${this.testNum}`);
}
build() {
Text(`Sibling: ${this.testNum}`)
}
}
@Component
struct LinkChild {
@Link @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`LinkChild: testNumChange value ${this.testNum}`);
}
build() {
Column() {
Button('incr testNum')
.onClick(() => {
console.log(`LinkChild: before value change value ${this.testNum}`);
this.testNum = this.testNum + 1
console.log(`LinkChild: after value change value ${this.testNum}`);
})
Text(`LinkChild: ${this.testNum}`)
LinkLinkChild({ testNumGrand: $testNum })
PropLinkChild({ testNumGrand: this.testNum })
}
.height(200).width(200)
}
}
```
3. LinkLinkChild和PropLinkChild声明如下,PropLinkChild中的\@Prop和其父组件建立单向同步关系。
```ts
@Component
struct LinkLinkChild {
@Link @Watch("testNumChange") testNumGrand: number;
testNumChange(propName: string): void {
console.log(`LinkLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`LinkLinkChild: ${this.testNumGrand}`)
}
}
@Component
struct PropLinkChild {
@Prop @Watch("testNumChange") testNumGrand: number;
testNumChange(propName: string): void {
console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`PropLinkChild: ${this.testNumGrand}`)
.height(70)
.backgroundColor(Color.Red)
.onClick(() => {
this.testNumGrand += 1;
})
}
}
```
![zh-cn_image_0000001638250945](figures/zh-cn_image_0000001638250945.png)
当LinkChild中的\@Link testNum更改时。
1. 更改首先同步到其父组件Parent,然后更改从Parent同步到Siling。
2. LinkChild中的\@Link testNum更改也同步给子组件LinkLinkChild和PropLinkChild。
\@State装饰器与\@Provide、LocalStorage、AppStorage的区别:
- \@State如果想要将更改传递给孙子节点,需要先将更改传递给子组件,再从子节点传递给孙子节点。
- 共享只能通过构造函数的参数传递,即命名参数机制CompA: ({ aProp: this.aProp })。
完整的代码示例如下:
```ts
@Component
struct LinkLinkChild {
@Link @Watch("testNumChange") testNumGrand: number;
testNumChange(propName: string): void {
console.log(`LinkLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`LinkLinkChild: ${this.testNumGrand}`)
}
}
@Component
struct PropLinkChild {
@Prop @Watch("testNumChange") testNumGrand: number;
testNumChange(propName: string): void {
console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`PropLinkChild: ${this.testNumGrand}`)
.height(70)
.backgroundColor(Color.Red)
.onClick(() => {
this.testNumGrand += 1;
})
}
}
@Component
struct Sibling {
@Link @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`Sibling: testNumChange value ${this.testNum}`);
}
build() {
Text(`Sibling: ${this.testNum}`)
}
}
@Component
struct LinkChild {
@Link @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`LinkChild: testNumChange value ${this.testNum}`);
}
build() {
Column() {
Button('incr testNum')
.onClick(() => {
console.log(`LinkChild: before value change value ${this.testNum}`);
this.testNum = this.testNum + 1
console.log(`LinkChild: after value change value ${this.testNum}`);
})
Text(`LinkChild: ${this.testNum}`)
LinkLinkChild({ testNumGrand: $testNum })
PropLinkChild({ testNumGrand: this.testNum })
}
.height(200).width(200)
}
}
@Entry
@Component
struct Parent {
@State @Watch("testNumChange1") testNum: number = 1;
testNumChange1(propName: string): void {
console.log(`Parent: testNumChange value ${this.testNum}`)
}
build() {
Column() {
LinkChild({ testNum: $testNum })
Sibling({ testNum: $testNum })
}
}
}
```
### \@Provide装饰的变量与任何后代组件共享状态数据
\@Provide装饰的变量可以与任何后代组件共享状态数据,其后代组件使用\@Consume创建双向同步,详情见[@Provide和@Consume](arkts-provide-and-consume.md)
因此,\@Provide-\@Consume模式比使用\@State-\@Link-\@Link从父组件将更改传递到孙子组件更方便。\@Provide-\@Consume适合在单个页面UI组件树中共享状态数据。
使用\@Provide-\@Consume模式时,\@Consume和其祖先组件中的\@Provide通过绑定相同的key连接,而不是在组件的构造函数中通过参数来进行传递。
以下示例通过\@Provide-\@Consume模式,将更改从父组件传递到孙子组件。
```ts
@Component
struct LinkLinkChild {
@Consume @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`LinkLinkChild: testNum value ${this.testNum}`);
}
build() {
Text(`LinkLinkChild: ${this.testNum}`)
}
}
@Component
struct PropLinkChild {
@Prop @Watch("testNumChange") testNumGrand: number;
testNumChange(propName: string): void {
console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`PropLinkChild: ${this.testNumGrand}`)
.height(70)
.backgroundColor(Color.Red)
.onClick(() => {
this.testNumGrand += 1;
})
}
}
@Component
struct Sibling {
@Consume @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`Sibling: testNumChange value ${this.testNum}`);
}
build() {
Text(`Sibling: ${this.testNum}`)
}
}
@Component
struct LinkChild {
@Consume @Watch("testNumChange") testNum: number;
testNumChange(propName: string): void {
console.log(`LinkChild: testNumChange value ${this.testNum}`);
}
build() {
Column() {
Button('incr testNum')
.onClick(() => {
console.log(`LinkChild: before value change value ${this.testNum}`);
this.testNum = this.testNum + 1
console.log(`LinkChild: after value change value ${this.testNum}`);
})
Text(`LinkChild: ${this.testNum}`)
LinkLinkChild({ /* empty */ })
PropLinkChild({ testNumGrand: this.testNum })
}
.height(200).width(200)
}
}
@Entry
@Component
struct Parent {
@Provide @Watch("testNumChange1") testNum: number = 1;
testNumChange1(propName: string): void {
console.log(`Parent: testNumChange value ${this.testNum}`)
}
build() {
Column() {
LinkChild({ /* empty */ })
Sibling({ /* empty */ })
}
}
}
```
### 给LocalStorage实例中对应的属性建立双向或单向同步
通过\@LocalStorageLink和\@LocalStorageProp,给LocalStorage实例中的属性建立双向或单向同步。可以将LocalStorage实例视为\@State变量的Map,使用详情参考LocalStorage。
LocalStorage对象可以在ArkUI应用程序的几个页面上共享。因此,使用\@LocalStorageLink、\@LocalStorageProp和LocalStorage可以在应用程序的多个页面上共享状态。
以下示例中:
1. 创建一个LocalStorage实例,并通过\@Entry(storage)将其注入根节点。
2. 在Parent组件中初始化\@LocalStorageLink("testNum")变量时,将在LocalStorage实例中创建testNum属性,并设置指定的初始值为1,即\@LocalStorageLink("testNum") testNum: number = 1。
3. 在其子组件中,都使用\@LocalStorageLink或\@LocalStorageProp绑定同一个属性名key来传递数据。
LocalStorage可以被认为是\@State变量的Map,属性名作为Map中的key。
\@LocalStorageLink和LocalStorage中对应的属性的同步行为,和\@State和\@Link一致,都为双向数据同步。
以下为组件的状态更新图:
![zh-cn_image_0000001588450934](figures/zh-cn_image_0000001588450934.png)
```ts
@Component
struct LinkLinkChild {
@LocalStorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
testNumChange(propName: string): void {
console.log(`LinkLinkChild: testNum value ${this.testNum}`);
}
build() {
Text(`LinkLinkChild: ${this.testNum}`)
}
}
@Component
struct PropLinkChild {
@LocalStorageProp("testNum") @Watch("testNumChange") testNumGrand: number = 1;
testNumChange(propName: string): void {
console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`PropLinkChild: ${this.testNumGrand}`)
.height(70)
.backgroundColor(Color.Red)
.onClick(() => {
this.testNumGrand += 1;
})
}
}
@Component
struct Sibling {
@LocalStorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
testNumChange(propName: string): void {
console.log(`Sibling: testNumChange value ${this.testNum}`);
}
build() {
Text(`Sibling: ${this.testNum}`)
}
}
@Component
struct LinkChild {
@LocalStorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
testNumChange(propName: string): void {
console.log(`LinkChild: testNumChange value ${this.testNum}`);
}
build() {
Column() {
Button('incr testNum')
.onClick(() => {
console.log(`LinkChild: before value change value ${this.testNum}`);
this.testNum = this.testNum + 1
console.log(`LinkChild: after value change value ${this.testNum}`);
})
Text(`LinkChild: ${this.testNum}`)
LinkLinkChild({ /* empty */ })
PropLinkChild({ /* empty */ })
}
.height(200).width(200)
}
}
// create LocalStorage object to hold the data
const storage = new LocalStorage();
@Entry(storage)
@Component
struct Parent {
@LocalStorageLink("testNum") @Watch("testNumChange1") testNum: number = 1;
testNumChange1(propName: string): void {
console.log(`Parent: testNumChange value ${this.testNum}`)
}
build() {
Column() {
LinkChild({ /* empty */ })
Sibling({ /* empty */ })
}
}
}
```
### 给AppStorage中对应的属性建立双向或单向同步
AppStorage是LocalStorage的单例对象,ArkUI在应用程序启动时创建该对象,在页面中使用\@StorageLink和\@StorageProp为多个页面之间共享数据,具体使用方法和LocalStorage类似。
也可以使用PersistentStorage将AppStorage中的特定属性持久化到本地磁盘的文件中,再次启动的时候\@StorageLink和\@StorageProp会恢复上次应用退出的数据。详情请参考[PersistentStorage文档](arkts-persiststorage.md)
示例如下:
```ts
@Component
struct LinkLinkChild {
@StorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
testNumChange(propName: string): void {
console.log(`LinkLinkChild: testNum value ${this.testNum}`);
}
build() {
Text(`LinkLinkChild: ${this.testNum}`)
}
}
@Component
struct PropLinkChild {
@StorageProp("testNum") @Watch("testNumChange") testNumGrand: number = 1;
testNumChange(propName: string): void {
console.log(`PropLinkChild: testNumGrand value ${this.testNumGrand}`);
}
build() {
Text(`PropLinkChild: ${this.testNumGrand}`)
.height(70)
.backgroundColor(Color.Red)
.onClick(() => {
this.testNumGrand += 1;
})
}
}
@Component
struct Sibling {
@StorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
testNumChange(propName: string): void {
console.log(`Sibling: testNumChange value ${this.testNum}`);
}
build() {
Text(`Sibling: ${this.testNum}`)
}
}
@Component
struct LinkChild {
@StorageLink("testNum") @Watch("testNumChange") testNum: number = 1;
testNumChange(propName: string): void {
console.log(`LinkChild: testNumChange value ${this.testNum}`);
}
build() {
Column() {
Button('incr testNum')
.onClick(() => {
console.log(`LinkChild: before value change value ${this.testNum}`);
this.testNum = this.testNum + 1
console.log(`LinkChild: after value change value ${this.testNum}`);
})
Text(`LinkChild: ${this.testNum}`)
LinkLinkChild({ /* empty */
})
PropLinkChild({ /* empty */
})
}
.height(200).width(200)
}
}
@Entry
@Component
struct Parent {
@StorageLink("testNum") @Watch("testNumChange1") testNum: number = 1;
testNumChange1(propName: string): void {
console.log(`Parent: testNumChange value ${this.testNum}`)
}
build() {
Column() {
LinkChild({ /* empty */
})
Sibling({ /* empty */
})
}
}
}
```
## ViewModel的嵌套场景
大多数情况下,ViewModel数据项都是复杂类型的,例如,对象数组、嵌套对象或者这些类型的组合。对于嵌套场景,可以使用\@Observed搭配\@Prop或者\@ObjectLink来观察变化。
### \@Prop和\@ObjectLink嵌套数据结构
推荐设计单独的\@Component来渲染每一个数组或对象。此时,对象数组或嵌套对象(属性是对象的对象称为嵌套对象)需要两个\@Component,一个\@Component呈现外部数组/对象,另一个\@Component呈现嵌套在数组/对象内的类对象。 \@Prop、\@Link、\@ObjectLink修饰的变量只能观察到第一层的变化。
- 对于类:
- 可以观察到赋值的变化:this.obj=new ClassObj(...)
- 可以观察到对象属性的更改:this.obj.a=new ClassA(...)
- 不能观察更深层级的属性更改:this.obj.a.b = 47
- 对于数组:
- 可以观察到数组的整体赋值:this.arr=[...]
- 可以观察到数据项的删除、插入和替换:this.arr[1] = new ClassA(); this.arr.pop(); this.arr.push(new ClassA(...)))、this.arr.sort(...)
- 不能观察更深层级的数组变化:this.arr[1].b = 47
如果要观察嵌套类的内部对象的变化,可以使用\@ObjectLink或\@Prop。优先考虑\@ObjectLink,其通过嵌套对象内部属性的引用初始化自身。\@Prop会对嵌套在内部的对象的深度拷贝来进行初始化,以实现单向同步。在性能上\@Prop的深度拷贝比\@ObjectLink的引用拷贝慢很多。
\@ObjectLink或\@Prop可以用来存储嵌套内部的类对象,该类必须用\@Observed类装饰器装饰,否则类的属性改变并不会触发更新UI并不会刷新。\@Observed为其装饰的类实现自定义构造函数,此构造函数创建了一个类的实例,并使用ES6代理包装(由ArkUI框架实现),拦截修饰class属性的所有“get”和“set”。“set”观察属性值,当发生赋值操作时,通知ArkUI框架更新。“get”收集哪些UI组件依赖该状态变量,实现最小化UI更新。
如果嵌套场景中,嵌套数据内部是数组或者class时,需根据以下场景使用\@Observed类装饰器。
- 如果嵌套数据内部是class,直接被\@Observed装饰。
- 如果嵌套数据内部是数组,可以通过以下方式来观察数组变化。
```ts
@Observed class ObservedArray<T> extends Array<T> {
constructor(args: any[]) {
super(...args);
}
/* otherwise empty */
}
```
ViewModel为外层class。
```ts
class Outer {
innerArrayProp : ObservedArray<string>;
...
}
```
### 嵌套数据结构中\@Prop和\@ObjectLink之的区别
以下示例中:
- 父组件ViewB渲染\@State arrA:Array&lt;ClassA&gt;\@State可以观察新数组的分配、数组项插入、删除和替换。
- 子组件ViewA渲染每一个ClassA的对象。
- 类装饰器\@Observed ClassA与\@ObjectLink a: ClassA。
- 可以观察嵌套在Array内的ClassA对象的变化。
- 不使用\@Observed时:
ViewB中的this.arrA[Math.floor(this.arrA.length/2)].c=10将不会被观察到,相应的ViewA组件也不会更新。
对于数组中的第一个和第二个数组项,每个数组项都初始化了两个ViewA的对象,渲染了同一个ViewA实例。在一个ViewA中的属性赋值this.a.c += 1;时不会引发另外一个使用同一个ClassA初始化的ViewA的渲染更新。
![zh-cn_image_0000001588610894](figures/zh-cn_image_0000001588610894.png)
```ts
let NextID: number = 1;
// 类装饰器@Observed装饰ClassA
@Observed
class ClassA {
public id: number;
public c: number;
constructor(c: number) {
this.id = NextID++;
this.c = c;
}
}
@Component
struct ViewA {
@ObjectLink a: ClassA;
label: string = "ViewA1";
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
.onClick(() => {
// 改变对象属性
this.a.c += 1;
})
}
}
}
@Entry
@Component
struct ViewB {
@State arrA: ClassA[] = [new ClassA(0), new ClassA(0)];
build() {
Column() {
ForEach(this.arrA,
(item) => {
ViewA({ label: `#${item.id}`, a: item })
},
(item) => item.id.toString()
)
Divider().height(10)
if (this.arrA.length) {
ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })
ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })
}
Divider().height(10)
Button(`ViewB: reset array`)
.onClick(() => {
// 替换整个数组,会被@State this.arrA观察到
this.arrA = [new ClassA(0), new ClassA(0)];
})
Button(`array push`)
.onClick(() => {
// 数组中插入数据,会被@State this.arrA观察到
this.arrA.push(new ClassA(0))
})
Button(`array shift`)
.onClick(() => {
// 数组中移除数据,会被@State this.arrA观察到
this.arrA.shift()
})
Button(`ViewB: chg item property in middle`)
.onClick(() => {
// 替换数组中的某个元素,会被@State this.arrA观察到
this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11);
})
Button(`ViewB: chg item property in middle`)
.onClick(() => {
// 改变数组中某个元素的属性c,会被ViewA中的@ObjectLink观察到
this.arrA[Math.floor(this.arrA.length / 2)].c = 10;
})
}
}
}
```
在ViewA中,将\@ObjectLink替换为\@Prop。
```ts
@Component
struct ViewA {
@Prop a: ClassA;
label : string = "ViewA1";
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
.onClick(() => {
// change object property
this.a.c += 1;
})
}
}
}
```
与用\@ObjectLink修饰不同,用\@ObjectLink修饰时,点击数组的第一个或第二个元素,后面两个ViewA会发生同步的变化。
\@Prop是单向数据同步,ViewA内的Button只会触发Button自身的刷新,不会传播到其他的ViewA实例中。在ViewA中的ClassA只是一个副本,并不是其父组件中\@State arrA : Array&lt;ClassA&gt;中的对象,也不是其他ViewA的ClassA,这使得数组的元素和ViewA中的元素表面是传入的同一个对象,实际上在UI上渲染使用的是两个互不相干的对象。
需要注意\@Prop和\@ObjectLink还有一个区别:\@ObjectLink装饰的变量是仅可读的,不能被赋值;\@Prop装饰的变量可以被赋值。
- \@ObjectLink实现双向同步,因为它是通过数据源的引用初始化的。
- \@Prop是单向同步,需要深拷贝数据源。
- 对于\@Prop赋值新的对象,就是简单地将本地的值覆写,但是对于实现双向数据同步的\@ObjectLink,覆写新的对象相当于要更新数据源中的数组项或者class的属性,这个对于 TypeScript/JavaScript是不能实现的。
## MVVM应用示例
以下示例深入探讨了嵌套ViewModel的应用程序设计,特别是自定义组件如何渲染一个嵌套的Object,该场景在实际的应用开发中十分常见。
开发一个电话簿应用,实现功能如下:
- 显示联系人和本机("Me")电话号码 。
- 选中联系人时,进入可编辑态”Edit“,可以更新该联系人详细信息,包括电话号码,住址。
- 在更新联系人信息时,只有在单击保存“Save Changes”之后,才会保存更改。
- 可以点击删除联系人”Delete Contact“,可以在联系人列表删除该联系人。
ViewModel需要包括:
- AddressBook(class)
- me (本机): 存储一个Person类。
- contacts(本机联系人):存储一个Person类数组。
AddressBook类声明如下:
```ts
export class AddressBook {
me: Person;
contacts: ObservedArray<Person>;
constructor(me: Person, contacts: Person[]) {
this.me = me;
this.contacts = new ObservedArray<Person>(contacts);
}
}
```
- Person (class)
- name : string
- address : Address
- phones: ObservedArray&lt;string&gt;
- Address (class)
- street : string
- zip : number
- city : string
Address类声明如下:
```ts
@Observed
export class Address {
street: string;
zip: number;
city: string;
constructor(street: string,
zip: number,
city: string) {
this.street = street;
this.zip = zip;
this.city = city;
}
}
```
Person类声明如下:
```ts
@Observed
export class Person {
id_: string;
name: string;
address: Address;
phones: ObservedArray<string>;
constructor(name: string,
street: string,
zip: number,
city: string,
phones: string[]) {
this.id_ = `${nextId}`;
nextId++;
this.name = name;
this.address = new Address(street, zip, city);
this.phones = new ObservedArray<string>(phones);
}
}
```
需要注意的是,因为phones是嵌套属性,如果要观察到phones的变化,需要extends array,并用\@Observed修饰它。ObservedArray类的声明如下。
```ts
@Observed
export class ObservedArray<T> extends Array<T> {
constructor(args?: any[]) {
console.log(`ObservedArray: ${JSON.stringify(args)} `)
if (Array.isArray(args)) {
super(...args);
} else {
super(args)
}
}
}
```
- selected : 对Person的引用。
更新流程如下:
1. 在根节点PageEntry中初始化所有的数据,将me和contacts和其子组件AddressBookView建立双向数据同步,selectedPerson默认为me,需要注意,selectedPerson并不是PageEntry数据源中的数据,而是数据源中,对某一个Person的引用。
PageEntry和AddressBookView声明如下:
```ts
@Component
struct AddressBookView {
@ObjectLink me : Person;
@ObjectLink contacts : ObservedArray<Person>;
@State selectedPerson: Person = undefined;
aboutToAppear() {
this.selectedPerson = this.me;
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start}) {
Text("Me:")
PersonView({person: this.me, phones: this.me.phones, selectedPerson: this.$selectedPerson})
Divider().height(8)
ForEach(this.contacts,
contact => {
PersonView({person: contact, phones: contact.phones, selectedPerson: this.$selectedPerson})
},
contact => contact.id_
)
Divider().height(8)
Text("Edit:")
PersonEditView({ selectedPerson: this.$selectedPerson, name: this.selectedPerson.name, address: this.selectedPerson.address, phones: this.selectedPerson.phones })
}
.borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)
}
}
@Entry
@Component
struct PageEntry {
@Provide addrBook: AddressBook = new AddressBook(
new Person("Gigi", "Itamerenkatu 9", 180, "Helsinki", ["+358441234567", "+35891234567", "+49621234567889"]),
[
new Person("Oly", "Itamerenkatu 9", 180, "Helsinki", ["+358449876543", "+3589456789"]),
new Person("Sam", "Itamerenkatu 9", 180, "Helsinki", ["+358509876543", "+358910101010"]),
new Person("Vivi", "Itamerenkatu 9", 180, "Helsinki", ["+358400908070", "+35894445555"]),
]);
build() {
Column() {
AddressBookView({ me: this.addrBook.me, contacts: this.addrBook.contacts, selectedPerson: this.addrBook.me })
}
}
}
```
2. PersonView,即电话簿中联系人姓名和首选电话的View,当用户选中,即高亮当前Person,需要同步回其父组件AddressBookView的selectedPerson,所以需要通过\@Link建立双向同步。
PersonView声明如下:
```ts
// 显示联系人姓名和首选电话
// 为了更新电话号码,这里需要@ObjectLink person和@ObjectLink phones,
// 显示首选号码不能使用this.person.phones[0],因为@ObjectLink person只代理了Person的属性,数组内部的变化观察不到
// 触发onClick事件更新selectedPerson
@Component
struct PersonView {
@ObjectLink person : Person;
@ObjectLink phones : ObservedArray<string>;
@Link selectedPerson : Person;
build() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
Text(this.person.name)
if (this.phones.length) {
Text(this.phones[0])
}
}
.height(55)
.backgroundColor(this.selectedPerson.name == this.person.name ? "#ffa0a0" : "#ffffff")
.onClick(() => {
this.selectedPerson = this.person;
})
}
}
```
3. 选中的Person会在PersonEditView中显示详细信息,对于PersonEditView的数据同步分为以下三种方式:
- 在Edit状态通过Input.onChange回调事件接受用户的键盘输入时,在点击“Save Changes”之前,这个修改是不希望同步会数据源的,但又希望刷新在当前的PersonEditView中,所以\@Prop深拷贝当前Person的详细信息;
- PersonEditView通过\@Link seletedPerson: Person和AddressBookView的``selectedPerson建立双向同步,当用户点击“Save Changes”的时候,\@Prop的修改将被赋值给\@Link seletedPerson: Person,这就意味这,数据将被同步回数据源。
- PersonEditView中通过\@Consume addrBook: AddressBook和根节点PageEntry建立跨组件层级的直接的双向同步关系,当用户在PersonEditView界面删除某一个联系人时,会直接同步回PageEntry,PageEntry的更新会通知AddressBookView刷新contracts的列表页。 PersonEditView声明如下:
```ts
// 渲染Person的详细信息
// @Prop装饰的变量从父组件AddressBookView深拷贝数据,将变化保留在本地, TextInput的变化只会在本地副本上进行修改。
// 点击 "Save Changes" 会将所有数据的复制通过@Prop到@Link, 同步到其他组件
@Component
struct PersonEditView {
@Consume addrBook : AddressBook;
/* 指向父组件selectedPerson的引用 */
@Link selectedPerson: Person;
/*在本地副本上编辑,直到点击保存*/
@Prop name: string;
@Prop address : Address;
@Prop phones : ObservedArray<string>;
selectedPersonIndex() : number {
return this.addrBook.contacts.findIndex((person) => person.id_ == this.selectedPerson.id_);
}
build() {
Column() {
TextInput({ text: this.name})
.onChange((value) => {
this.name = value;
})
TextInput({text: this.address.street})
.onChange((value) => {
this.address.street = value;
})
TextInput({text: this.address.city})
.onChange((value) => {
this.address.city = value;
})
TextInput({text: this.address.zip.toString()})
.onChange((value) => {
const result = parseInt(value);
this.address.zip= isNaN(result) ? 0 : result;
})
if(this.phones.length>0) {
ForEach(this.phones,
(phone, index) => {
TextInput({text: phone})
.width(150)
.onChange((value) => {
console.log(`${index}. ${value} value has changed`)
this.phones[index] = value;
})
},
(phone, index) => `${index}-${phone}`
)
}
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
Text("Save Changes")
.onClick(() => {
// 将本地副本更新的值赋值给指向父组件selectedPerson的引用
// 避免创建新对象,在现有属性上进行修改
this.selectedPerson.name = this.name;
this.selectedPerson.address.street = this.address.street
this.selectedPerson.address.city = this.address.city
this.selectedPerson.address.zip = this.address.zip
this.phones.forEach((phone : string, index : number) => { this.selectedPerson.phones[index] = phone } );
})
if (this.selectedPersonIndex()!=-1) {
Text("Delete Contact")
.onClick(() => {
let index = this.selectedPersonIndex();
console.log(`delete contact at index ${index}`);
// 删除当前联系人
this.addrBook.contacts.splice(index, 1);
// 删除当前selectedPerson,选中态前移一位
index = (index < this.addrBook.contacts.length) ? index : index-1;
// 如果contract被删除完,则设置me为选中态
this.selectedPerson = (index>=0) ? this.addrBook.contacts[index] : this.addrBook.me;
})
}
}
}
}
}
```
其中在关于\@ObjectLink和\@Link的区别要注意以下几点:
1. 在AddressBookView中实现和父组件PageView的双向同步,需要用\@ObjectLink me : Person和\@ObjectLink contacts : ObservedArray&lt;Person&gt;,而不能用\@Link,原因如下:
- \@Link需要和其数据源类型完全相同,且仅能观察到第一层的变化;
- \@ObjectLink可以被数据源的属性初始化,且代理了\@Observed装饰类的属性,可以观察到被装饰类属性的变化。
2. 当 联系人姓名 (Person.name) 或者首选电话号码 (Person.phones[0]) 发生更新时,PersonView也需要同步刷新,其中Person.phones[0]属于第二层的更新,如果使用\@Link将无法观察到,而且\@Link需要和其数据源类型完全相同。所以在PersonView中也需要使用\@ObjectLink,即\@ObjectLink person : Person和\@ObjectLink phones : ObservedArray&lt;string&gt;。
![zh-cn_image_0000001605293914](figures/zh-cn_image_0000001605293914.png)
在这个例子中,我们可以大概了解到如何构建ViewModel,在应用的根节点中,ViewModel的数据可能是可以巨大的嵌套数据,但是在ViewModel和View的适配和渲染中,我们尽可能将ViewModel的数据项和View相适配,这样的话在针对每一层的View,都是一个相对“扁平”的数据,仅观察当前层就可以了。
在应用实际开发中,也许我们无法避免去构建一个十分庞大的Model,但是我们可以在UI树状结构中合理地去拆分数据,使得ViewModel和View更好的适配,从而搭配最小化更新来实现高性能开发。
完整应用代码如下:
```ts
// ViewModel classes
let nextId = 0;
@Observed
export class ObservedArray<T> extends Array<T> {
constructor(args?: any[]) {
console.log(`ObservedArray: ${JSON.stringify(args)} `)
if (Array.isArray(args)) {
super(...args);
} else {
super(args)
}
}
}
@Observed
export class Address {
street: string;
zip: number;
city: string;
constructor(street: string,
zip: number,
city: string) {
this.street = street;
this.zip = zip;
this.city = city;
}
}
@Observed
export class Person {
id_: string;
name: string;
address: Address;
phones: ObservedArray<string>;
constructor(name: string,
street: string,
zip: number,
city: string,
phones: string[]) {
this.id_ = `${nextId}`;
nextId++;
this.name = name;
this.address = new Address(street, zip, city);
this.phones = new ObservedArray<string>(phones);
}
}
export class AddressBook {
me: Person;
contacts: ObservedArray<Person>;
constructor(me: Person, contacts: Person[]) {
this.me = me;
this.contacts = new ObservedArray<Person>(contacts);
}
}
//渲染出Person对象的名称和手机Observed数组<string>中的第一个号码
//为了更新电话号码,这里需要@ObjectLink person和@ObjectLink phones,
//不能使用this.person.phones,内部数组的更改不会被观察到。
// 在AddressBookView、PersonEditView中的onClick更新selectedPerson
@Component
struct PersonView {
@ObjectLink person: Person;
@ObjectLink phones: ObservedArray<string>;
@Link selectedPerson: Person;
build() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
Text(this.person.name)
if (this.phones.length) {
Text(this.phones[0])
}
}
.height(55)
.backgroundColor(this.selectedPerson.name == this.person.name ? "#ffa0a0" : "#ffffff")
.onClick(() => {
this.selectedPerson = this.person;
})
}
}
// 渲染Person的详细信息
// @Prop装饰的变量从父组件AddressBookView深拷贝数据,将变化保留在本地, TextInput的变化只会在本地副本上进行修改。
// 点击 "Save Changes" 会将所有数据的复制通过@Prop到@Link, 同步到其他组件
@Component
struct PersonEditView {
@Consume addrBook: AddressBook;
/* 指向父组件selectedPerson的引用 */
@Link selectedPerson: Person;
/*在本地副本上编辑,直到点击保存*/
@Prop name: string;
@Prop address: Address;
@Prop phones: ObservedArray<string>;
selectedPersonIndex(): number {
return this.addrBook.contacts.findIndex((person) => person.id_ == this.selectedPerson.id_);
}
build() {
Column() {
TextInput({ text: this.name })
.onChange((value) => {
this.name = value;
})
TextInput({ text: this.address.street })
.onChange((value) => {
this.address.street = value;
})
TextInput({ text: this.address.city })
.onChange((value) => {
this.address.city = value;
})
TextInput({ text: this.address.zip.toString() })
.onChange((value) => {
const result = parseInt(value);
this.address.zip = isNaN(result) ? 0 : result;
})
if (this.phones.length > 0) {
ForEach(this.phones,
(phone, index) => {
TextInput({ text: phone })
.width(150)
.onChange((value) => {
console.log(`${index}. ${value} value has changed`)
this.phones[index] = value;
})
},
(phone, index) => `${index}-${phone}`
)
}
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
Text("Save Changes")
.onClick(() => {
// 将本地副本更新的值赋值给指向父组件selectedPerson的引用
// 避免创建新对象,在现有属性上进行修改
this.selectedPerson.name = this.name;
this.selectedPerson.address.street = this.address.street
this.selectedPerson.address.city = this.address.city
this.selectedPerson.address.zip = this.address.zip
this.phones.forEach((phone: string, index: number) => {
this.selectedPerson.phones[index] = phone
});
})
if (this.selectedPersonIndex() != -1) {
Text("Delete Contact")
.onClick(() => {
let index = this.selectedPersonIndex();
console.log(`delete contact at index ${index}`);
// 删除当前联系人
this.addrBook.contacts.splice(index, 1);
// 删除当前selectedPerson,选中态前移一位
index = (index < this.addrBook.contacts.length) ? index : index - 1;
// 如果contract被删除完,则设置me为选中态
this.selectedPerson = (index >= 0) ? this.addrBook.contacts[index] : this.addrBook.me;
})
}
}
}
}
}
@Component
struct AddressBookView {
@ObjectLink me: Person;
@ObjectLink contacts: ObservedArray<Person>;
@State selectedPerson: Person = undefined;
aboutToAppear() {
this.selectedPerson = this.me;
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) {
Text("Me:")
PersonView({ person: this.me, phones: this.me.phones, selectedPerson: this.$selectedPerson })
Divider().height(8)
ForEach(this.contacts,
contact => {
PersonView({ person: contact, phones: contact.phones, selectedPerson: this.$selectedPerson })
},
contact => contact.id_
)
Divider().height(8)
Text("Edit:")
PersonEditView({
selectedPerson: this.$selectedPerson,
name: this.selectedPerson.name,
address: this.selectedPerson.address,
phones: this.selectedPerson.phones
})
}
.borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)
}
}
@Entry
@Component
struct PageEntry {
@Provide addrBook: AddressBook = new AddressBook(
new Person("Gigi", "Itamerenkatu 9", 180, "Helsinki", ["+358441234567", "+35891234567", "+49621234567889"]),
[
new Person("Oly", "Itamerenkatu 9", 180, "Helsinki", ["+358449876543", "+3589456789"]),
new Person("Sam", "Itamerenkatu 9", 180, "Helsinki", ["+358509876543", "+358910101010"]),
new Person("Vivi", "Itamerenkatu 9", 180, "Helsinki", ["+358400908070", "+35894445555"]),
]);
build() {
Column() {
AddressBookView({ me: this.addrBook.me, contacts: this.addrBook.contacts, selectedPerson: this.addrBook.me })
}
}
}
```
\ No newline at end of file
# 渲染控制优秀实践
为了帮助应用程序开发人员提高其应用程序质量,本章节面向开发者提供了多个在开发ArkUI应用中常见场景和易错问题,并给出了对应的解决方案。此外,还提供了同一场景下,推荐用法和不推荐用法的对比和解释说明,更直观地展示两者区别,从而帮助开发者学习如果正确地在应用开发中使用渲染控制,进行高性能开发。
## 在ForEach数据源中添加元素
在ForEach数据源中添加元素导致数组项ID重复。
### 不推荐用法
下面示例使用ForEach方法迭代数组this.arr的每个元素,在Text组件进行显示,并在单击Text('Add arr element')时添加新的数组元素。
```ts
@Entry
@Component
struct Index {
@State arr: number[] = [1,2,3];
build() {
Column() {
ForEach(this.arr,
(item) => {
Text(`Item ${item}`)
},
item => item.toString())
Text('Add arr element')
.fontSize(20)
.onClick(()=>{
this.arr.push(4); // arr新增的元素,其在ForEach内的键值均为'4'
console.log("Arr elements: ", this.arr);
})
}
}
}
```
点击两次Text('Add arr element')时,数组this.arr每次都会添加新元素 4。但是在ForEach循环渲染中,第三个参数(item)=&gt; item.toString()需要生成Array每一个item对应的id值。该Array Id被要求是唯一的和稳定的。
- 唯一性:键值生成函数生成的每个数组项的id是不同的。
- 稳定性:当数组项ID发生变化时,ArkUI框架认为该数组项被替换或更改。
- ArkUI框架会对重复的ID告警,这种情况下框架的行为是未知的,特别是UI的更新在该场景下可能不起作用。
因此上述示例中,框架不会显示第二次及以后新添加的文本元素。因为这个元素不再是唯一的,他们都含有相同的id4。 但是如果删除ForEach第三个键值生成函数(item) =&gt; item.toString(),则触发onClick事件后每一个新添加的Text元素都会得到更新。这是因为框架使用了默认的Array id生成函数,即(item: any, index : number) =&gt; '${index}__${JSON.stringify(item)}'。它的兼容性更好但可能会导致不必要的UI更新,因此仍建议应用定义自己的键值生成函数。
## ForEach数据源更新
ForEach数据源更新时,数组项ID与原数组项ID重复不会重新创建该数组项。
### 不推荐用法
下面的示例定义了Index和Child两个组件。父组件Index有arr数组成员变量,初始值包含数字1、2、3。Child定义\@Prop value,接收父组件中arr数组中的一个元素。
```ts
@Component
struct Child {
@Prop value: number;
build() {
Text(`${this.value}`)
.fontSize(50)
.onClick(() => {
this.value++ // 点击改变@Prop的值
})
}
}
@Entry
@Component
struct Index {
@State arr: number[] = [1, 2, 3];
build() {
Row() {
Column() {
// 对照组
Child({ value: this.arr[0] })
Child({ value: this.arr[1] })
Child({ value: this.arr[2] })
Divider().height(5)
ForEach(this.arr,
item => {
Child({ value: item })
},
item => item.toString() // 键值,标识id
)
Text('Parent: replace entire arr')
.fontSize(50)
.onClick(() => {
// 两个数组项内均含有'3',ForEach内的id没有发生变化
// 意味着ForEach不会更新该Child实例,@Prop也不会在父组件处被更新
this.arr = (this.arr[0] == 1) ? [3, 4, 5] : [1, 2, 3];
})
}
}
}
}
```
当触发文本组件Parent: replace entire arr的onClick事件时,状态变量数组arr根据自身第一个元素的值将被[3, 4, 5]或[1, 2, 3]替换,但是ForEach里初始被创建的\@Prop传入值为3的Child组件并不会更新。
因为,老数组和新数组初始均含有同一个值的元素(数字3),而且该元素生成的标识id在父组件里没有变化。因此ForEach没有识别出对应的Child实例需要被新的输入值更新,对应的子组件内\@Prop也没有更新。
![zh-cn_image_0000001604900446](figures/zh-cn_image_0000001604900446.png)
可以在arr中用一个唯一的元素代替重复的元素3来观察本事件的行为表现。当恰当的替换掉数组时,接下来应用的表现才是符合预期的。
\ No newline at end of file
# 状态管理优秀实践
为了帮助应用程序开发人员提高其应用程序质量,特别是在高效的状态管理方面。本章节面向开发者提供了多个在开发ArkUI应用中常见场景和易错问题,并给出了对应的解决方案。此外,还提供了同一场景下,推荐用法和不推荐用法的对比和解释说明,更直观地展示两者区别,从而帮助开发者学习如果正确地在应用开发中使用状态变量,进行高性能开发。
## 基础示例
下面的例子是关于\@Prop,\@Link,\@ObjectLink的初始化规则的,在学习下面这个例子前,我们首先需要了解:
- \@Prop:可以被父组件的\@State初始化,或者\@State是复杂类型Object和class时的属性,或者是数组时的数组项。
- \@ObjectLink:初始化规则和\@Prop相同,但需要被\@Observed装饰class的实例初始化。
- \@Link:必须和\@State或其他数据源类型完全相同。
### 不推荐用法
```ts
@Observed
class ClassA {
public c: number = 0;
constructor(c: number) {
this.c = c;
}
}
@Component
struct LinkChild {
@Link testNum: number;
build() {
Text(`LinkChild testNum ${this.testNum}`)
}
}
@Component
struct PropChild2 {
@Prop testNum: ClassA;
build() {
Text(`PropChild2 testNum ${this.testNum.c}`)
.onClick(() => {
this.testNum.c += 1;
})
}
}
@Component
struct PropChild3 {
@Prop testNum: ClassA;
build() {
Text(`PropChild3 testNum ${this.testNum.c}`)
}
}
@Component
struct ObjectLinkChild {
@ObjectLink testNum: ClassA;
build() {
Text(`ObjectLinkChild testNum ${this.testNum.c}`)
.onClick(() => {
// 问题4:ObjectLink不能被赋值
this.testNum = new ClassA(47);
})
}
}
@Entry
@Component
struct Parent {
@State testNum: ClassA[] = [new ClassA(1)];
build() {
Column() {
Text(`Parent testNum ${this.testNum.c}`)
.onClick(() => {
this.testNum[0].c += 1;
})
// 问题1:@Link装饰的变量需要和数据源@State类型一致
LinkChild({ testNum: this.testNum.c })
// 问题2:@Prop本地没有初始化,也没有从父组件初始化
PropChild2()
// 问题3:PropChild3没有改变@Prop testNum: ClassA的值,所以这时最优的选择是使用@ObjectLink
PropChild3({ testNum: this.testNum[0] })
ObjectLinkChild({ testNum: this.testNum[0] })
}
}
}
```
上面的例子有以下几个错误:
1. \@Component LinkChild:\@Link testNum: number从父组件的LinkChild({testNum:this.testNum.c})。\@Link的数据源必须是装饰器装饰的状态变量,简而言之,\@Link装饰的数据必须和数据源类型相同,比如\@Link: T和\@State : T。所以,这里应该改为\@Link testNum: ClassA,从父组件初始化的方式为LinkChild({testNum: $testNum})。
2. \@Component PropChild2:\@Prop可以本地初始化,也可以从父组件初始化,但是必须初始化,对于\@Prop testNum: ClassA没有本地初始化,所以必须从父组件初始化PropChild1({testNum: this.testNum})。
3. \@Component PropChild3:没有改变\@Prop testNum: ClassA的值,所以这时较优的选择是使用\@ObjectLink,因为\@Prop是会深拷贝数据,具有拷贝的性能开销,所以这个时候\@ObjectLink是比\@Link和\@Prop更优的选择。
4. 点击ObjectLinkChild给\@ObjectLink装饰的变量赋值:this.testNum = new ClassA(47); 也是不允许的,对于实现双向数据同步的\@ObjectLink,赋值相当于要更新父组件中的数组项或者class的属性,这个对于 TypeScript/JavaScript是不能实现的。框架对于这种行为会发生运行时报错。
5. 如果是非嵌套场景,比如Parent里声明的变量为 \@State testNum: ClassA = new ClassA(1),ClassA就不需要被\@Observed修饰,因为\@State已经具备了观察第一层变化的能力,不需要再使用\@Observed来加一层代理。
### 推荐用法
```ts
@Observed
class ClassA {
public c: number = 0;
constructor(c: number) {
this.c = c;
}
}
@Component
struct LinkChild {
@Link testNum: ClassA;
build() {
Text(`LinkChild testNum ${this.testNum?.c}`)
}
}
@Component
struct PropChild1 {
@Prop testNum: ClassA = new ClassA(1);
build() {
Text(`PropChild1 testNum ${this.testNum?.c}`)
.onClick(() => {
this.testNum = new ClassA(48);
})
}
}
@Component
struct ObjectLinkChild {
@ObjectLink testNum: ClassA;
build() {
Text(`ObjectLinkChild testNum ${this.testNum.c}`)
// @ObjectLink装饰的变量可以更新属性
.onClick(() => {
this.testNum.c += 1;
})
}
}
@Entry
@Component
struct Parent {
@State testNum: ClassA[] = [new ClassA(1)];
build() {
Column() {
Text(`Parent testNum ${this.testNum.c}`)
.onClick(() => {
this.testNum[0].c += 1;
})
// @Link装饰的变量需要和数据源@State类型一致
LinkChild({ testNum: this.testNum[0] })
// @Prop本地有初始化,不需要再从父组件初始化
PropChild1()
// 当子组件不需要发生本地改变时,优先使用@ObjectLink,因为@Prop是会深拷贝数据,具有拷贝的性能开销,所以这个时候@ObjectLink是比@Link和@Prop更优的选择
ObjectLinkChild({ testNum: this.testNum[0] })
}
}
}
```
## 基础嵌套对象属性更改失效
在应用开发中,有很多嵌套对象场景,例如,开发者更新了某个属性,但UI没有进行对应的更新。
每个装饰器都有自己可以观察的能力,并不是所有的改变都可以被观察到,只有可以被观察到的变化才会进行UI更新。\@Observed装饰器可以观察到嵌套对象的属性变化,其他装饰器仅能观察到第二层的变化。
### 不推荐用法
下面的例子中,一些UI组件并不会更新。
```ts
class ClassA {
a: number;
constructor(a: number) {
this.a = a;
}
getA(): number {
return this.a;
}
setA(a: number): void {
this.a = a;
}
}
class ClassC {
c: number;
constructor(c: number) {
this.c = c;
}
getC(): number {
return this.c;
}
setC(c: number): void {
this.c = c;
}
}
class ClassB extends ClassA {
b: number = 47;
c: ClassC;
constructor(a: number, b: number, c: number) {
super(a);
this.b = b;
this.c = new ClassC(c);
}
getB(): number {
return this.b;
}
setB(b: number): void {
this.b = b;
}
getC(): number {
return this.c.getC();
}
setC(c: number): void {
return this.c.setC(c);
}
}
@Entry
@Component
struct MyView {
@State b: ClassB = new ClassB(10, 20, 30);
build() {
Column({ space: 10 }) {
Text(`a: ${this.b.a}`)
Button("Change ClassA.a")
.onClick(() => {
this.b.a += 1;
})
Text(`b: ${this.b.b}`)
Button("Change ClassB.b")
.onClick(() => {
this.b.b += 1;
})
Text(`c: ${this.b.c.c}`)
Button("Change ClassB.ClassC.c")
.onClick(() => {
// 点击时上面的Text组件不会刷新
this.b.c.c += 1;
})
}
}
}
```
- 最后一个Text组件Text('c: ${this.b.c.c}'),当点击该组件时UI不会刷新。 因为,\@State b : ClassB 只能观察到this.b属性的变化,比如this.b.a, this.b.b 和this.b.c的变化,但是无法观察嵌套在属性中的属性,即this.b.c.c(属性c是内嵌在b中的对象classC的属性)。
- 为了观察到嵌套与内部的ClassC的属性,需要做如下改变:
- 构造一个子组件,用于单独渲染ClassC的实例。 该子组件可以使用\@ObjectLink c : ClassC或\@Prop c : ClassC。通常会使用\@ObjectLink,除非子组件需要对其ClassC对象进行本地修改。
- 嵌套的ClassC必须用\@Observed修饰。当在ClassB中创建ClassC对象时(本示例中的ClassB(10, 20, 30)),它将被包装在ES6代理中,当ClassC属性更改时(this.b.c.c += 1),该代码将修改通知到\@ObjectLink变量。
### 推荐用法
以下示例使用\@Observed/\@ObjectLink来观察嵌套对象的属性更改。
```ts
class ClassA {
a: number;
constructor(a: number) {
this.a = a;
}
getA() : number {
return this.a; }
setA( a: number ) : void {
this.a = a; }
}
@Observed
class ClassC {
c: number;
constructor(c: number) {
this.c = c;
}
getC() : number {
return this.c; }
setC(c : number) : void {
this.c = c; }
}
class ClassB extends ClassA {
b: number = 47;
c: ClassC;
constructor(a: number, b: number, c: number) {
super(a);
this.b = b;
this.c = new ClassC(c);
}
getB() : number {
return this.b; }
setB(b : number) : void {
this.b = b; }
getC() : number {
return this.c.getC(); }
setC(c : number) : void {
return this.c.setC(c); }
}
@Component
struct ViewClassC {
@ObjectLink c : ClassC;
build() {
Column({space:10}) {
Text(`c: ${this.c.getC()}`)
Button("Change C")
.onClick(() => {
this.c.setC(this.c.getC()+1);
})
}
}
}
@Entry
@Component
struct MyView {
@State b : ClassB = new ClassB(10, 20, 30);
build() {
Column({space:10}) {
Text(`a: ${this.b.a}`)
Button("Change ClassA.a")
.onClick(() => {
this.b.a +=1;
})
Text(`b: ${this.b.b}`)
Button("Change ClassB.b")
.onClick(() => {
this.b.b += 1;
})
ViewClassC({c: this.b.c}) // Text(`c: ${this.b.c.c}`)的替代写法
Button("Change ClassB.ClassC.c")
.onClick(() => {
this.b.c.c += 1;
})
}
}
}
```
## 复杂嵌套对象属性更改失效
### 不推荐用法
以下示例创建了一个带有\@ObjectLink装饰变量的子组件,用于渲染一个含有嵌套属性的ParentCounter,用\@Observed装饰嵌套在ParentCounter中的SubCounter。
```ts
let nextId = 1;
@Observed
class SubCounter {
counter: number;
constructor(c: number) {
this.counter = c;
}
}
@Observed
class ParentCounter {
id: number;
counter: number;
subCounter: SubCounter;
incrCounter() {
this.counter++;
}
incrSubCounter(c: number) {
this.subCounter.counter += c;
}
setSubCounter(c: number): void {
this.subCounter.counter = c;
}
constructor(c: number) {
this.id = nextId++;
this.counter = c;
this.subCounter = new SubCounter(c);
}
}
@Component
struct CounterComp {
@ObjectLink value: ParentCounter;
build() {
Column({ space: 10 }) {
Text(`${this.value.counter}`)
.fontSize(25)
.onClick(() => {
this.value.incrCounter();
})
Text(`${this.value.subCounter.counter}`)
.onClick(() => {
this.value.incrSubCounter(1);
})
Divider().height(2)
}
}
}
@Entry
@Component
struct ParentComp {
@State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
build() {
Row() {
Column() {
CounterComp({ value: this.counter[0] })
CounterComp({ value: this.counter[1] })
CounterComp({ value: this.counter[2] })
Divider().height(5)
ForEach(this.counter,
item => {
CounterComp({ value: item })
},
item => item.id.toString()
)
Divider().height(5)
// 第一个点击事件
Text('Parent: incr counter[0].counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].incrCounter();
// 每次触发时自增10
this.counter[0].incrSubCounter(10);
})
// 第二个点击事件
Text('Parent: set.counter to 10')
.fontSize(20).height(50)
.onClick(() => {
// 无法将value设置为10,UI不会刷新
this.counter[0].setSubCounter(10);
})
Text('Parent: reset entire counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
})
}
}
}
}
```
对于Text('Parent: incr counter[0].counter')的onClick事件,this.counter[0].incrSubCounter(10)调用incrSubCounter方法使SubCounter的counter值增加10,UI同步刷新。
但是,在Text('Parent: set.counter to 10')的onClick中调用this.counter[0].setSubCounter(10),SubCounter的counter值却无法重置为10。
incrSubCounter和setSubCounter都是同一个SubCounter的函数。在第一个点击处理时调用incrSubCounter可以正确更新UI,而第二个点击处理调用setSubCounter时却没有更新UI。实际上incrSubCounter和setSubCounter两个函数都不能触发Text('${this.value.subCounter.counter}')的更新,因为\@ObjectLink value : ParentCounter仅能观察其代理ParentCounter的属性,对于this.value.subCounter.counter是SubCounter的属性,无法观察到嵌套类的属性。
但是,第一个click事件调用this.counter[0].incrCounter()将CounterComp自定义组件中\@ObjectLink value: ParentCounter标记为已更改。此时触发Text('${this.value.subCounter.counter}')的更新。 如果在第一个点击事件中删除this.counter[0].incrCounter(),也无法更新UI。
### 推荐用法
对于上述问题,为了直接观察SubCounter中的属性,以便this.counter[0].setSubCounter(10)操作有效,可以利用下面的方法:
```ts
@ObjectLink valueParentCounter;
@ObjectLink subValueSubCounter;
```
该方法使得\@ObjectLink分别代理了ParentCounter和SubCounter的属性,这样对于这两个类的属性的变化都可以观察到,即都会对UI视图进行刷新。即使删除了上面所说的this.counter[0].incrCounter(),UI也会进行正确的刷新。
该方法可用于实现“两个层级”的观察,即外部对象和内部嵌套对象的观察。但是该方法只能用于\@ObjectLink装饰器,无法作用于\@Prop(\@Prop通过深拷贝传入对象)。详情参考@Prop与@ObjectLink的差异。
```ts
let nextId = 1;
@Observed
class SubCounter {
counter: number;
constructor(c: number) {
this.counter = c;
}
}
@Observed
class ParentCounter {
id: number;
counter: number;
subCounter: SubCounter;
incrCounter() {
this.counter++;
}
incrSubCounter(c: number) {
this.subCounter.counter += c;
}
setSubCounter(c: number): void {
this.subCounter.counter = c;
}
constructor(c: number) {
this.id = nextId++;
this.counter = c;
this.subCounter = new SubCounter(c);
}
}
@Component
struct CounterComp {
@ObjectLink value: ParentCounter;
@ObjectLink subValue: SubCounter;
build() {
Column({ space: 10 }) {
Text(`${this.value.counter}`)
.fontSize(25)
.onClick(() => {
this.value.incrCounter();
})
Text(`${this.subValue.counter}`)
.onClick(() => {
this.subValue.counter += 1;
})
Divider().height(2)
}
}
}
@Entry
@Component
struct ParentComp {
@State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
build() {
Row() {
Column() {
CounterComp({ value: this.counter[0], subValue: this.counter[0].subCounter })
CounterComp({ value: this.counter[1], subValue: this.counter[1].subCounter })
CounterComp({ value: this.counter[2], subValue: this.counter[2].subCounter })
Divider().height(5)
ForEach(this.counter,
item => {
CounterComp({ value: item, subValue: item.subCounter })
},
item => item.id.toString()
)
Divider().height(5)
Text('Parent: reset entire counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
})
Text('Parent: incr counter[0].counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].incrCounter();
this.counter[0].incrSubCounter(10);
})
Text('Parent: set.counter to 10')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].setSubCounter(10);
})
}
}
}
}
```
## \@Prop与\@ObjectLink的差异
在下面的示例代码中,\@ObjectLink修饰的变量是对数据源的引用,即在this.value.subValue和this.subValue都是同一个对象的不同引用,所以在点击CounterComp的click handler,改变this.value.subCounter.counter,this.subValue.counter也会改变,对应的组件Text(`this.subValue.counter: ${this.subValue.counter}`)会刷新。
```ts
let nextId = 1;
@Observed
class SubCounter {
counter: number;
constructor(c: number) {
this.counter = c;
}
}
@Observed
class ParentCounter {
id: number;
counter: number;
subCounter: SubCounter;
incrCounter() {
this.counter++;
}
incrSubCounter(c: number) {
this.subCounter.counter += c;
}
setSubCounter(c: number): void {
this.subCounter.counter = c;
}
constructor(c: number) {
this.id = nextId++;
this.counter = c;
this.subCounter = new SubCounter(c);
}
}
@Component
struct CounterComp {
@ObjectLink value: ParentCounter;
@ObjectLink subValue: SubCounter;
build() {
Column({ space: 10 }) {
Text(`this.subValue.counter: ${this.subValue.counter}`)
.fontSize(30)
Text(`this.value.counter:increase 7 `)
.fontSize(30)
.onClick(() => {
// click handler, Text(`this.subValue.counter: ${this.subValue.counter}`) will update
this.value.incrSubCounter(7);
})
Divider().height(2)
}
}
}
@Entry
@Component
struct ParentComp {
@State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
build() {
Row() {
Column() {
CounterComp({ value: this.counter[0], subValue: this.counter[0].subCounter })
CounterComp({ value: this.counter[1], subValue: this.counter[1].subCounter })
CounterComp({ value: this.counter[2], subValue: this.counter[2].subCounter })
Divider().height(5)
ForEach(this.counter,
item => {
CounterComp({ value: item, subValue: item.subCounter })
},
item => item.id.toString()
)
Divider().height(5)
Text('Parent: reset entire counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
})
Text('Parent: incr counter[0].counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].incrCounter();
this.counter[0].incrSubCounter(10);
})
Text('Parent: set.counter to 10')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].setSubCounter(10);
})
}
}
}
}
```
\@ObjectLink图示如下:
![zh-cn_image_0000001651665921](figures/zh-cn_image_0000001651665921.png)
### 不推荐用法
如果用\@Prop替代\@ObjectLink。点击第一个click handler,UI刷新正常。但是点击第二个onClick事件,\@Prop 对变量做了一个本地拷贝,CounterComp的第一个Text并不会刷新。
this.value.subCounter和this.subValue并不是同一个对象。所以this.value.subCounter的改变,并没有改变this.subValue的拷贝对象,Text(`this.subValue.counter: ${this.subValue.counter}`)不会刷新。
```ts
@Component
struct CounterComp {
@Prop value: ParentCounter;
@Prop subValue: SubCounter;
build() {
Column({ space: 10 }) {
Text(`this.subValue.counter: ${this.subValue.counter}`)
.fontSize(20)
.onClick(() => {
// 1st click handler
this.subValue.counter += 7;
})
Text(`this.value.counter:increase 7 `)
.fontSize(20)
.onClick(() => {
// 2nd click handler
this.value.incrSubCounter(7);
})
Divider().height(2)
}
}
}
```
\@Prop拷贝的关系图示如下:
![zh-cn_image_0000001602146116](figures/zh-cn_image_0000001602146116.png)
### 推荐用法
可以通过从ParentComp到CounterComp仅拷贝一份\@Prop value: ParentCounter,同时必须避免再多拷贝一份SubCounter。
- 在CounterComp组件中只使用一个\@Prop counter:Counter。
- 添加另一个子组件SubCounterComp,其中包含\@ObjectLink subCounter: SubCounter。此\@ObjectLink可确保观察到SubCounter对象属性更改,并且UI更新正常。
- \@ObjectLink subCounter: SubCounter与CounterComp中的\@Prop counter:Counter的this.counter.subCounter共享相同的SubCounter对象。
```ts
@Component
struct SubCounterComp {
@ObjectLink subValue: SubCounter;
build() {
Text(`SubCounterComp: this.subValue.counter: ${this.subValue.counter}`)
.onClick(() => {
// 2nd click handler
this.subValue.incrSubCounter(7);
})
}
}
@Component
struct CounterComp {
@Prop value: ParentCounter;
build() {
Column({ space: 10 }) {
Text(`this.value.incrCounter(): this.value.counter: ${this.value.counter}`)
.fontSize(20)
.onClick(() => {
// 1st click handler
this.value.incrCounter();
})
SubCounterComp({ subValue: this.value.subCounter })
Text(`this.value.incrSubCounter()`)
.onClick(() => {
// 3rd click handler
this.value.incrSubCounter(77);
})
Divider().height(2)
}
}
}
@Entry
@Component
struct ParentComp {
@State counter: ParentCounter[] = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
build() {
Row() {
Column() {
CounterComp({ value: this.counter[0] })
CounterComp({ value: this.counter[1] })
CounterComp({ value: this.counter[2] })
Divider().height(5)
ForEach(this.counter,
item => {
CounterComp({ value: item })
},
item => item.id.toString()
)
Divider().height(5)
Text('Parent: reset entire counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter = [new ParentCounter(1), new ParentCounter(2), new ParentCounter(3)];
})
Text('Parent: incr counter[0].counter')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].incrCounter();
this.counter[0].incrSubCounter(10);
})
Text('Parent: set.counter to 10')
.fontSize(20).height(50)
.onClick(() => {
this.counter[0].setSubCounter(10);
})
}
}
}
}
```
拷贝关系图示如下:
![zh-cn_image_0000001653949465](figures/zh-cn_image_0000001653949465.png)
## 应用在渲染期间禁止改变状态变量
在学习本示例之前,我们要先明确一个概念,在ArkUI状态管理中,状态驱动UI更新。
![zh-cn_image_0000001651365257](figures/zh-cn_image_0000001651365257.png)
所以,不能在自定义组件的build()或\@Builder方法里直接改变状态变量,这可能会造成循环渲染的风险,下面以build()方法举例示意。
### 不推荐用法
在下面的示例中,Text('${this.count++}')在build渲染方法里直接改变了状态变量。
```ts
@Entry
@Component
struct CompA {
@State col1: Color = Color.Yellow;
@State col2: Color = Color.Green;
@State count: number = 1;
build() {
Column() {
// 应避免直接在Text组件内改变count的值
Text(`${this.count++}`)
.width(50)
.height(50)
.fontColor(this.col1)
.onClick(() => {
this.col2 = Color.Red;
})
Button("change col1").onClick(() =>{
this.col1 = Color.Pink;
})
}
.backgroundColor(this.col2)
}
}
```
在ArkUI中,Text('${this.count++}')在全量更新或最小化更新会产生不同的影响:
- 全量更新: ArkUI可能会陷入一个无限的重渲染的循环里,因为Text组件的每一次渲染都会改变应用的状态,就会再引起下一轮渲染的开启。 当 this.col2 更改时,都会执行整个build构建函数,因此,Text(`${this.count++}`)绑定的文本也会更改,每次重新渲染Text(`${this.count++}`),又会使this.count状态变量更新,导致新一轮的build执行,从而陷入无限循环。
- 最小化更新: 当 this.col2 更改时,只有Column组件会更新,Text组件不会更改。 只当 this.col1 更改时,会去更新整个Text组件,其所有属性函数都会执行,所以会看到Text(`${this.count++}`)自增。因为目前UI以组件为单位进行更新,如果组件上某一个属性发生改变,会更新整体的组件。所以整体的更新链路是:this.col2 = Color.Red -&gt; Text组件整体更新-&gt;this.count++-&gt;Text组件整体更新。
### 推荐用法
建议应用的开发方法在事件处理程序中执行count++操作。
```ts
@Entry
@Component
struct CompA {
@State col1: Color = Color.Yellow;
@State col2: Color = Color.Green;
@State count: number = 1;
build() {
Column() {
Text(`${this.count}`)
.width(50)
.height(50)
.backgroundColor(this.col1)
.onClick(() => {
this.count++;
})
}
.backgroundColor(this.col2)
}
}
```
build函数中更改应用状态的行为可能会比上面的示例更加隐蔽,比如:
-\@Builder,\@Extend或\@Styles方法内改变状态变量 。
- 在计算参数时调用函数中改变应用状态变量,例如 Text('${this.calcLabel()}')。
- 对当前数组做出修改,sort()改变了数组this.arr,随后的filter方法会返回一个新的数组。
```ts
@State arr : Array<..> = [ ... ];
ForEach(this.arr.sort().filter(....),
item => {
...
})
```
正确的执行方式为:filter返回一个新数组,后面的sort方法才不会改变原数组this.arr,示例:
```ts
ForEach(this.arr.filter(....).sort(),
item => {
...
})
```
## 使用状态变量强行更新
### 不推荐用法
```ts
@Entry
@Component
struct CompA {
@State needsUpdate: boolean = true;
realState1: Array<number> = [4, 1, 3, 2]; // 未使用状态变量装饰器
realState2: Color = Color.Yellow;
updateUI(param: any): any {
const triggerAGet = this.needsUpdate;
return param;
}
build() {
Column({ space: 20 }) {
ForEach(this.updateUI(this.realState1),
item => {
Text(`${item}`)
})
Text("add item")
.onClick(() => {
// 改变realState1不会触发UI视图更新
this.realState1.push(this.realState1[this.realState1.length-1] + 1);
// 触发UI视图更新
this.needsUpdate = !this.needsUpdate;
})
Text("chg color")
.onClick(() => {
// 改变realState2不会触发UI视图更新
this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
// 触发UI视图更新
this.needsUpdate = !this.needsUpdate;
})
}.backgroundColor(this.updateUI(this.realState2))
.width(200).height(500)
}
}
```
上述示例存在以下问题:
- 应用程序希望控制UI更新逻辑,但在ArkUI中,UI更新的逻辑应该是由框架来检测应用程序状态变量的更改去实现。
- this.needsUpdate是一个自定义的UI状态变量,应该仅应用于其绑定的UI组件。变量this.realState1、this.realState2没有被装饰,他们的变化将不会触发UI刷新。
- 但是在该应用中,用户试图通过this.needsUpdate的更新来带动常规变量this.realState1、this.realState2的更新。此方法不合理且更新性能较差,如果只想更新背景颜色,且不需要更新ForEach,但this.needsUpdate值的变化也会带动ForEach更新。
### 推荐用法
要解决此问题,应将realState1和realState2成员变量用\@State装饰。一旦完成此操作,就不再需要变量needsUpdate。
```ts
@Entry
@Component
struct CompA {
@State realState1: Array<number> = [4, 1, 3, 2];
@State realState2: Color = Color.Yellow;
build() {
Column({ space: 20 }) {
ForEach(this.realState1,
item => {
Text(`${item}`)
})
Text("add item")
.onClick(() => {
// 改变realState1触发UI视图更新
this.realState1.push(this.realState1[this.realState1.length-1] + 1);
})
Text("chg color")
.onClick(() => {
// 改变realState2触发UI视图更新
this.realState2 = this.realState2 == Color.Yellow ? Color.Red : Color.Yellow;
})
}.backgroundColor(this.realState2)
.width(200).height(500)
}
}
```
\ No newline at end of file
...@@ -582,11 +582,11 @@ cameraManager.on('cameraMute', (err, curMuetd) => { ...@@ -582,11 +582,11 @@ cameraManager.on('cameraMute', (err, curMuetd) => {
}) })
``` ```
### isPreLaunchSupported ### isPrelaunchSupported
isPreLaunchSupported(camera: CameraDevice): boolean isPrelaunchSupported(camera: CameraDevice): boolean
在setPreLaunchConfig接口使用前调用,用于判断指定cameraDevice是否支持预热启动。 在setPrelaunchConfig接口使用前调用,用于判断指定cameraDevice是否支持预热启动。
**系统接口:** 此接口为系统接口。 **系统接口:** 此接口为系统接口。
...@@ -617,14 +617,14 @@ isPreLaunchSupported(camera: CameraDevice): boolean ...@@ -617,14 +617,14 @@ isPreLaunchSupported(camera: CameraDevice): boolean
```js ```js
this.cameraManager = camera.getCameraManager(globalThis.abilityContext); this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
let cameras = this.cameraManager.getSupportedCameras() let cameras = this.cameraManager.getSupportedCameras()
if(this.cameraManager.isPreLaunchSupported(cameras[0])) { if(this.cameraManager.isPrelaunchSupported(cameras[0])) {
this.cameraManager.setPreLaunchConfig({cameraDevice: cameras[0]}); this.cameraManager.setPrelaunchConfig({cameraDevice: cameras[0]});
} }
``` ```
### setPreLaunchConfig ### setPrelaunchConfig
setPreLaunchConfig(preLaunchConfig: PreLaunchConfig): void setPrelaunchConfig(prelaunchConfig: PrelaunchConfig): void
**系统接口:** 此接口为系统接口。 **系统接口:** 此接口为系统接口。
...@@ -636,7 +636,7 @@ setPreLaunchConfig(preLaunchConfig: PreLaunchConfig): void ...@@ -636,7 +636,7 @@ setPreLaunchConfig(preLaunchConfig: PreLaunchConfig): void
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| -------- | --------------- | ---- | --------- | | -------- | --------------- | ---- | --------- |
| preLaunchConfig | [PreLaunchConfig](#prelaunchconfig) | 是 | 预启动配置参数。| | prelaunchConfig | [PrelaunchConfig](#prelaunchconfig) | 是 | 预启动配置参数。|
**错误码:** **错误码:**
...@@ -652,11 +652,18 @@ setPreLaunchConfig(preLaunchConfig: PreLaunchConfig): void ...@@ -652,11 +652,18 @@ setPreLaunchConfig(preLaunchConfig: PreLaunchConfig): void
```js ```js
this.cameraManager = camera.getCameraManager(globalThis.abilityContext); this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
let cameras = this.cameraManager.getSupportedCameras() let cameras = this.cameraManager.getSupportedCameras()
if(this.cameraManager.isPrelaunchSupported(cameras[0])) {
try {
this.cameraManager.setPrelaunchConfig({cameraDevice: cameras[0]});
} catch (error) {
console.error(`catch error: Code: ${error.code}, message: ${error.message}`);
}
}
``` ```
### preLaunch ### prelaunch
preLaunch(): void prelaunch(): void
用户点击系统相机图标,拉起相机应用同时调用,下发预热请求,使能相机预热启动。 用户点击系统相机图标,拉起相机应用同时调用,下发预热请求,使能相机预热启动。
...@@ -668,6 +675,11 @@ preLaunch(): void ...@@ -668,6 +675,11 @@ preLaunch(): void
```js ```js
this.cameraManager = camera.getCameraManager(globalThis.abilityContext); this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
try {
this.cameraManager.prelaunch();
} catch (error) {
console.error(`catch error: Code: ${error.code}, message: ${error.message}`);
}
``` ```
### createDeferredPreviewOutput ### createDeferredPreviewOutput
...@@ -710,7 +722,7 @@ function getDeferredPreviewOutput(context: Context, previewProfile: camera.Profi ...@@ -710,7 +722,7 @@ function getDeferredPreviewOutput(context: Context, previewProfile: camera.Profi
} }
``` ```
## PreLaunchConfig ## PrelaunchConfig
相机预启动配置参数。 相机预启动配置参数。
......
...@@ -553,12 +553,16 @@ cryptoCert.createX509Cert(encodingBlob, function (error, x509Cert) { ...@@ -553,12 +553,16 @@ cryptoCert.createX509Cert(encodingBlob, function (error, x509Cert) {
}); });
``` ```
### getSerialNumber ### getSerialNumber<sup>(deprecated)</sup>
getSerialNumber() : number getSerialNumber() : number
表示获取X509证书序列号。 表示获取X509证书序列号。
> **说明:**
>
> 从API version 9开始支持,从API version 10开始废弃,建议使用[getCertSerialNumber](#getcertserialnumber10)替代。
**系统能力:** SystemCapability.Security.Cert **系统能力:** SystemCapability.Security.Cert
**返回值** **返回值**
...@@ -589,6 +593,48 @@ cryptoCert.createX509Cert(encodingBlob, function (error, x509Cert) { ...@@ -589,6 +593,48 @@ cryptoCert.createX509Cert(encodingBlob, function (error, x509Cert) {
}); });
``` ```
### getCertSerialNumber<sup>10+</sup>
getCertSerialNumber() : bigint
表示获取X509证书序列号。
**系统能力:** SystemCapability.Security.Cert
**返回值**
| 类型 | 说明 |
| ------ | ------------------ |
| bigint | 表示X509证书序列号 |
**错误码:**
| 错误码ID | 错误信息 |
| -------- | ------------------------------------------------- |
| 19020002 | runtime error. |
**示例:**
```js
import cryptoCert from '@ohos.security.cert';
// 证书二进制数据,需业务自行赋值
let encodingData = null;
let encodingBlob = {
data: encodingData,
// 根据encodingData的格式进行赋值,支持FORMAT_PEM和FORMAT_DER
encodingFormat: cryptoCert.EncodingFormat.FORMAT_PEM
};
cryptoCert.createX509Cert(encodingBlob, function (error, x509Cert) {
if (error != null) {
console.log("createX509Cert failed, errCode: " + error.code + ", errMsg: " + error.message);
} else {
console.log("createX509Cert success");
let serialNumber = x509Cert.getCertSerialNumber();
}
});
```
### getIssuerName ### getIssuerName
getIssuerName() : DataBlob getIssuerName() : DataBlob
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
import cloudData from '@ohos.data.cloudData'; import cloudData from '@ohos.data.cloudData';
``` ```
## Action ## ClearAction
清除本地下载的云端数据的行为枚举。 清除本地下载的云端数据的行为枚举。
...@@ -347,9 +347,9 @@ try { ...@@ -347,9 +347,9 @@ try {
} }
``` ```
### clean ### clear
static clean(accountId: string, appActions: {[bundleName: string]: Action}, callback: AsyncCallback&lt;void&gt;):void static clear(accountId: string, appActions: {[bundleName: string]: ClearAction}, callback: AsyncCallback&lt;void&gt;):void
清除本地下载的云端数据,使用callback异步回调。 清除本地下载的云端数据,使用callback异步回调。
...@@ -361,26 +361,26 @@ static clean(accountId: string, appActions: {[bundleName: string]: Action}, cal ...@@ -361,26 +361,26 @@ static clean(accountId: string, appActions: {[bundleName: string]: Action}, cal
**参数:** **参数:**
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| ---------- | ----------------------------------------- | ---- | -------------------------------- | | ---------- | --------------------------------------------------- | ---- | -------------------------------- |
| accountId | string | 是 | 具体打开的云帐号ID。 | | accountId | string | 是 | 具体打开的云帐号ID。 |
| appActions | {[bundleName: string]: [Action](#action)} | 是 | 要清除数据的应用信息及清除规则。 | | appActions | {[bundleName: string]: [ClearAction](#clearaction)} | 是 | 要清除数据的应用信息及清除规则。 |
| callback | AsyncCallback&lt;void&gt; | 是 | 回调函数。 | | callback | AsyncCallback&lt;void&gt; | 是 | 回调函数。 |
**示例:** **示例:**
```js ```js
let action = cloudData.Action; let action = cloudData.ClearAction;
let account = "test_id"; let account = "test_id";
let bundleName1 = "test_bundleName1"; let bundleName1 = "test_bundleName1";
let bundleName2 = "test_bundleName2"; let bundleName2 = "test_bundleName2";
let appActions = { [bundleName1]: action.CLEAR_CLOUD_INFO, [bundleName2]: action.CLEAR_CLOUD_DATA_AND_INFO }; let appActions = { [bundleName1]: action.CLEAR_CLOUD_INFO, [bundleName2]: action.CLEAR_CLOUD_DATA_AND_INFO };
try { try {
cloudData.Config.clean(account, appActions, function (err) { cloudData.Config.clear(account, appActions, function (err) {
if (err === undefined) { if (err === undefined) {
console.info('Succeeding in cleaning cloud data'); console.info('Succeeding in clearing cloud data');
} else { } else {
console.error(`Failed to clean cloud data. Code: ${err.code}, message: ${err.message}`); console.error(`Failed to clear cloud data. Code: ${err.code}, message: ${err.message}`);
} }
}); });
} catch (error) { } catch (error) {
...@@ -388,9 +388,9 @@ try { ...@@ -388,9 +388,9 @@ try {
} }
``` ```
### clean ### clear
static clean(accountId: string, appActions: {[bundleName: string]: Action}): Promise&lt;void&gt; static clear(accountId: string, appActions: {[bundleName: string]: ClearAction}): Promise&lt;void&gt;
清除本地下载的云端数据,使用Promise异步回调。 清除本地下载的云端数据,使用Promise异步回调。
...@@ -402,10 +402,10 @@ static clean(accountId: string, appActions: {[bundleName: string]: Action}): Pro ...@@ -402,10 +402,10 @@ static clean(accountId: string, appActions: {[bundleName: string]: Action}): Pro
**参数:** **参数:**
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| ---------- | ----------------------------------------- | ---- | -------------------------------- | | ---------- | --------------------------------------------------- | ---- | -------------------------------- |
| accountId | string | 是 | 具体打开的云帐号ID。 | | accountId | string | 是 | 具体打开的云帐号ID。 |
| appActions | {[bundleName: string]: [Action](#action)} | 是 | 要清除数据的应用信息及清除规则。 | | appActions | {[bundleName: string]: [ClearAction](#clearaction)} | 是 | 要清除数据的应用信息及清除规则。 |
**返回值:** **返回值:**
...@@ -416,16 +416,16 @@ static clean(accountId: string, appActions: {[bundleName: string]: Action}): Pro ...@@ -416,16 +416,16 @@ static clean(accountId: string, appActions: {[bundleName: string]: Action}): Pro
**示例:** **示例:**
```js ```js
let action = cloudData.Action; let action = cloudData.ClearAction;
let account = "test_id"; let account = "test_id";
let bundleName1 = "test_bundleName1"; let bundleName1 = "test_bundleName1";
let bundleName2 = "test_bundleName2"; let bundleName2 = "test_bundleName2";
let appActions = { [bundleName1]: action.CLEAR_CLOUD_INFO, [bundleName2]: action.CLEAR_CLOUD_DATA_AND_INFO }; let appActions = { [bundleName1]: action.CLEAR_CLOUD_INFO, [bundleName2]: action.CLEAR_CLOUD_DATA_AND_INFO };
try { try {
cloudData.Config.clean(account, appActions).then(() => { cloudData.Config.clear(account, appActions).then(() => {
console.info('Succeeding in cleaning cloud data'); console.info('Succeeding in clearing cloud data');
}).catch((err) => { }).catch((err) => {
console.error(`Failed to clean cloud data. Code: ${err.code}, message: ${err.message}`); console.error(`Failed to clear cloud data. Code: ${err.code}, message: ${err.message}`);
}); });
} catch (error) { } catch (error) {
console.error(`An unexpected error occurred. Code: ${error.code}, message: ${error.message}`); console.error(`An unexpected error occurred. Code: ${error.code}, message: ${error.message}`);
......
...@@ -704,12 +704,12 @@ class EntryAbility extends UIAbility { ...@@ -704,12 +704,12 @@ class EntryAbility extends UIAbility {
**系统能力:** SystemCapability.DistributedDataManager.RelationalStore.Core **系统能力:** SystemCapability.DistributedDataManager.RelationalStore.Core
| 名称 | 类型 | 必填 | 说明 | | 名称 | 类型 | 必填 | 说明 |
| -------- | ------ | ---- | ---------------------------------------- | | ---------- | ------ | ---- | ---------------------------------------- |
| total | number | 是 | 表示数据库表中需要端云同步的总行数。 | | total | number | 是 | 表示数据库表中需要端云同步的总行数。 |
| success | number | 是 | 表示数据库表中端云同步成功的行数。 | | successful | number | 是 | 表示数据库表中端云同步成功的行数。 |
| failed | number | 是 | 表示数据库表中端云同步失败的行数。 | | failed | number | 是 | 表示数据库表中端云同步失败的行数。 |
| remained | number | 是 | 表示数据库表中端云同步剩余未执行的行数。 | | remained | number | 是 | 表示数据库表中端云同步剩余未执行的行数。 |
## TableDetails<sup>10+</sup> ## TableDetails<sup>10+</sup>
...@@ -3533,7 +3533,48 @@ promise.then(() => { ...@@ -3533,7 +3533,48 @@ promise.then(() => {
### setDistributedTables<sup>10+</sup> ### setDistributedTables<sup>10+</sup>
setDistributedTables(tables: Array&lt;string&gt;, type: number, config: DistributedConfig, callback: AsyncCallback&lt;void&gt;): void setDistributedTables(tables: Array&lt;string&gt;, type: DistributedType, callback: AsyncCallback&lt;void&gt;): void
设置分布式数据库表,使用callback异步回调。
**需要权限:** ohos.permission.DISTRIBUTED_DATASYNC
**系统能力:** SystemCapability.DistributedDataManager.RelationalStore.Core
**参数:**
| 参数名 | 类型 | 必填 | 说明 |
| -------- | ------------------------------------- | ---- | ---------------------------- |
| tables | Array&lt;string&gt; | 是 | 要设置的分布式数据库表表名。 |
| type | [DistributedType](#distributedtype10) | 是 | 表的分布式类型。 |
| callback | AsyncCallback&lt;void&gt; | 是 | 指定callback回调函数。 |
**错误码:**
以下错误码的详细介绍请参见[关系型数据库错误码](../errorcodes/errorcode-data-rdb.md)
| **错误码ID** | **错误信息** |
| ------------ | ------------ |
| 14800000 | Inner error. |
| 14800051 |The type of the distributed table does not match.|
**示例:**
```js
store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIBUTED_CLOUD, function (err) {
if (err) {
console.error(`SetDistributedTables failed, code is ${err.code},message is ${err.message}`);
return;
}
console.info(`SetDistributedTables successfully.`);
})
```
###
### setDistributedTables<sup>10+</sup>
setDistributedTables(tables: Array&lt;string&gt;, type: DistributedType, config: DistributedConfig, callback: AsyncCallback&lt;void&gt;): void
设置分布式数据库表,使用callback异步回调。 设置分布式数据库表,使用callback异步回调。
...@@ -3546,10 +3587,19 @@ setDistributedTables(tables: Array&lt;string&gt;, type: number, config: Distribu ...@@ -3546,10 +3587,19 @@ setDistributedTables(tables: Array&lt;string&gt;, type: number, config: Distribu
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| -------- | ----------------------------------- | --- | --------------- | | -------- | ----------------------------------- | --- | --------------- |
| tables | Array&lt;string&gt; | 是 | 要设置的分布式数据库表表名。 | | tables | Array&lt;string&gt; | 是 | 要设置的分布式数据库表表名。 |
| type | number | 是 | 表的分布式类型。目前支持的入参值为: relationalStore.DistributedType.DISTRIBUTED_DEVICE、relationalStore.DistributedType.DISTRIBUTED_CLOUD。<br> 当type为relationalStore.DistributedType.DISTRIBUTED_DEVICE时,表示表在不同设备之间分布式。<br> 当type为relationalStore.DistributedType.DISTRIBUTED_CLOUD时,表示表在设备和云端之间分布式。 | | type | [DistributedType](#distributedtype10) | 是 | 表的分布式类型。 |
| config | [DistributedConfig](#distributedconfig10) | 是 | 表的分布式配置信息。 | | config | [DistributedConfig](#distributedconfig10) | 是 | 表的分布式配置信息。 |
| callback | AsyncCallback&lt;void&gt; | 是 | 指定callback回调函数。 | | callback | AsyncCallback&lt;void&gt; | 是 | 指定callback回调函数。 |
**错误码:**
以下错误码的详细介绍请参见[关系型数据库错误码](../errorcodes/errorcode-data-rdb.md)
| **错误码ID** | **错误信息** |
| ------------ | ------------------------------------------------- |
| 14800000 | Inner error. |
| 14800051 | The type of the distributed table does not match. |
**示例:** **示例:**
```js ```js
...@@ -3566,7 +3616,7 @@ store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIB ...@@ -3566,7 +3616,7 @@ store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIB
### setDistributedTables<sup>10+</sup> ### setDistributedTables<sup>10+</sup>
setDistributedTables(tables: Array&lt;string>, type?: number, config?: DistributedConfig): Promise&lt;void> setDistributedTables(tables: Array&lt;string>, type?: DistributedType, config?: DistributedConfig): Promise&lt;void>
设置分布式数据库表,使用Promise异步回调。 设置分布式数据库表,使用Promise异步回调。
...@@ -3578,8 +3628,8 @@ store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIB ...@@ -3578,8 +3628,8 @@ store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIB
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| ------ | ----------------------------------------- | ---- | ------------------------------------------------------------ | | ------ | ----------------------------------------- | ---- | ------------------------------------------------------------ |
| tables | Array&lt;string&gt; | 是 | 要设置的分布式数据库表表名。 | | tables | Array&lt;string&gt; | 是 | 要设置的分布式数据库表表名。 |
| type | number | 否 | 表的分布式类型。默认值是relationalStore.DistributedType.DISTRIBUTED_DEVICE。<br> 目前支持的入参值为: relationalStore.DistributedType.DISTRIBUTED_DEVICE、relationalStore.DistributedType.DISTRIBUTED_CLOUD。<br/> 当type为relationalStore.DistributedType.DISTRIBUTED_DEVICE时,表示表在不同设备之间分布式。<br/> 当type为relationalStore.DistributedType.DISTRIBUTED_CLOUD时,表示表在设备和云端之间分布式。 | | type | [DistributedType](#distributedtype10) | 否 | 表的分布式类型。默认值是relationalStore.DistributedType.DISTRIBUTED_DEVICE。 |
| config | [DistributedConfig](#distributedconfig10) | 否 | 表的分布式配置信息。不传入时默认autoSync为false,即只支持手动同步。 | | config | [DistributedConfig](#distributedconfig10) | 否 | 表的分布式配置信息。不传入时默认autoSync为false,即只支持手动同步。 |
**返回值** **返回值**
...@@ -3588,6 +3638,15 @@ store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIB ...@@ -3588,6 +3638,15 @@ store.setDistributedTables(["EMPLOYEE"], relationalStore.DistributedType.DISTRIB
| ------------------- | ------------------------- | | ------------------- | ------------------------- |
| Promise&lt;void&gt; | 无返回结果的Promise对象。 | | Promise&lt;void&gt; | 无返回结果的Promise对象。 |
**错误码:**
以下错误码的详细介绍请参见[关系型数据库错误码](../errorcodes/errorcode-data-rdb.md)
| **错误码ID** | **错误信息** |
| ------------ | ------------------------------------------------- |
| 14800000 | Inner error. |
| 14800051 | The type of the distributed table does not match. |
**示例:** **示例:**
```js ```js
......
...@@ -372,6 +372,8 @@ deleteAsset(uri: string): Promise\<void> ...@@ -372,6 +372,8 @@ deleteAsset(uri: string): Promise\<void>
删除媒体文件资源。 删除媒体文件资源。
调用该接口彻底删除文件前,需要先调用[FileAsset.trash](#trash8)将文件放入回收站,否则文件会删除失败。
> **说明:** > **说明:**
> 此接口从API Version 9开始废弃。请使用[deleteAssets](js-apis-photoAccessHelper.md#deleteassets-3)替代。 > 此接口从API Version 9开始废弃。请使用[deleteAssets](js-apis-photoAccessHelper.md#deleteassets-3)替代。
...@@ -424,6 +426,8 @@ deleteAsset(uri: string, callback: AsyncCallback\<void>): void ...@@ -424,6 +426,8 @@ deleteAsset(uri: string, callback: AsyncCallback\<void>): void
删除媒体文件资源。 删除媒体文件资源。
调用该接口彻底删除文件前,需要先调用[FileAsset.trash](#trash8)将文件放入回收站,否则文件会删除失败。
> **说明:** > **说明:**
> 此接口从API Version 9开始废弃。请使用[deleteAssets](js-apis-photoAccessHelper.md#deleteassets-2)替代。 > 此接口从API Version 9开始废弃。请使用[deleteAssets](js-apis-photoAccessHelper.md#deleteassets-2)替代。
......
...@@ -48,10 +48,10 @@ createData(mimeType: string, value: ValueType): PasteData ...@@ -48,10 +48,10 @@ createData(mimeType: string, value: ValueType): PasteData
**参数:** **参数:**
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- | | -------- | -------- | -------- |--------------------------------------------------------------------------------------------------------|
| mimeType | string | 是 | 自定义数据的MIME类型。 | | mimeType | string | 是 | 剪贴板数据对应的MIME类型,可以是[常量](#常量)中已定义的类型,包括HTML类型,WANT类型,纯文本类型,URI类型,PIXELMAP类型;也可以是自定义的MIME类型,开发者可自定义此参数值。 |
| value | [ValueType](#valuetype9) | 是 | 自定义数据内容。 | | value | [ValueType](#valuetype9) | 是 | 自定义数据内容。 |
**返回值:** **返回值:**
...@@ -59,13 +59,21 @@ createData(mimeType: string, value: ValueType): PasteData ...@@ -59,13 +59,21 @@ createData(mimeType: string, value: ValueType): PasteData
| -------- | -------- | | -------- | -------- |
| [PasteData](#pastedata) | 剪贴板内容对象。 | | [PasteData](#pastedata) | 剪贴板内容对象。 |
**示例:** **示例1:**
```js ```js
let dataXml = new ArrayBuffer(256); let dataXml = new ArrayBuffer(256);
let pasteData = pasteboard.createData('app/xml', dataXml); let pasteData = pasteboard.createData('app/xml', dataXml);
``` ```
**示例2:**
```js
let dataText = 'hello';
let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, dataText);
```
## pasteboard.createRecord<sup>9+</sup> ## pasteboard.createRecord<sup>9+</sup>
createRecord(mimeType: string, value: ValueType):PasteDataRecord; createRecord(mimeType: string, value: ValueType):PasteDataRecord;
...@@ -76,10 +84,10 @@ createRecord(mimeType: string, value: ValueType):PasteDataRecord; ...@@ -76,10 +84,10 @@ createRecord(mimeType: string, value: ValueType):PasteDataRecord;
**参数:** **参数:**
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- | | -------- | -------- | -------- |-------------------|
| mimeType | string | 是 | 自定义数据的MIME类型。 | | mimeType | string | 是 | 剪贴板数据对应的MIME类型,可以是[常量](#常量)中已定义的类型,包括HTML类型,WANT类型,纯文本类型,URI类型,PIXELMAP类型;也可以是自定义的MIME类型,开发者可自定义此参数值。 |
| value | [ValueType](#valuetype9) | 是 | 自定义数据内容。 | | value | [ValueType](#valuetype9) | 是 | 自定义数据内容。 |
**返回值:** **返回值:**
...@@ -87,13 +95,20 @@ createRecord(mimeType: string, value: ValueType):PasteDataRecord; ...@@ -87,13 +95,20 @@ createRecord(mimeType: string, value: ValueType):PasteDataRecord;
| -------- | -------- | | -------- | -------- |
| [PasteDataRecord](#pastedatarecord7) | 一条新建的自定义数据内容条目。 | | [PasteDataRecord](#pastedatarecord7) | 一条新建的自定义数据内容条目。 |
**示例:** **示例1:**
```js ```js
let dataXml = new ArrayBuffer(256); let dataXml = new ArrayBuffer(256);
let pasteDataRecord = pasteboard.createRecord('app/xml', dataXml); let pasteDataRecord = pasteboard.createRecord('app/xml', dataXml);
``` ```
**示例2:**
```js
let dataUri = 'dataability:///com.example.myapplication1/user.txt';
let record = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_URI, dataUri);
```
## pasteboard.getSystemPasteboard ## pasteboard.getSystemPasteboard
getSystemPasteboard(): SystemPasteboard getSystemPasteboard(): SystemPasteboard
......
...@@ -4324,6 +4324,10 @@ export default class EntryAbility extends UIAbility { ...@@ -4324,6 +4324,10 @@ export default class EntryAbility extends UIAbility {
通过WebCookie可以控制Web组件中的cookie的各种行为,其中每个应用中的所有web组件共享一个WebCookieManager实例。 通过WebCookie可以控制Web组件中的cookie的各种行为,其中每个应用中的所有web组件共享一个WebCookieManager实例。
> **说明:**
>
> 目前调用WebCookieManager下的方法,都需要先加载Web组件。
### getCookie ### getCookie
static getCookie(url: string): string static getCookie(url: string): string
...@@ -4785,6 +4789,10 @@ struct WebComponent { ...@@ -4785,6 +4789,10 @@ struct WebComponent {
通过WebStorage可管理Web SQL数据库接口和HTML5 Web存储接口,每个应用中的所有Web组件共享一个WebStorage。 通过WebStorage可管理Web SQL数据库接口和HTML5 Web存储接口,每个应用中的所有Web组件共享一个WebStorage。
> **说明:**
>
> 目前调用WebStorage下的方法,都需要先加载Web组件。
### deleteOrigin ### deleteOrigin
static deleteOrigin(origin : string): void static deleteOrigin(origin : string): void
...@@ -5245,6 +5253,10 @@ struct WebComponent { ...@@ -5245,6 +5253,10 @@ struct WebComponent {
web组件数据库管理对象。 web组件数据库管理对象。
> **说明:**
>
> 目前调用WebDataBase下的方法,都需要先加载Web组件。
### getHttpAuthCredentials ### getHttpAuthCredentials
static getHttpAuthCredentials(host: string, realm: string): Array\<string> static getHttpAuthCredentials(host: string, realm: string): Array\<string>
...@@ -5423,6 +5435,10 @@ struct WebComponent { ...@@ -5423,6 +5435,10 @@ struct WebComponent {
web组件地理位置权限管理对象。 web组件地理位置权限管理对象。
> **说明:**
>
> 目前调用GeolocationPermissions下的方法,都需要先加载Web组件。
### 需要权限 ### 需要权限
访问地理位置时需添加权限:ohos.permission.LOCATION、ohos.permission.APPROXIMATELY_LOCATION、ohos.permission.LOCATION_IN_BACKGROUND,具体权限说明请参考[位置服务](./js-apis-geolocation.md) 访问地理位置时需添加权限:ohos.permission.LOCATION、ohos.permission.APPROXIMATELY_LOCATION、ohos.permission.LOCATION_IN_BACKGROUND,具体权限说明请参考[位置服务](./js-apis-geolocation.md)
......
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
strokeColor: '#0081ff', strokeColor: '#0081ff',
fillColor: '#cce5ff', fillColor: '#cce5ff',
data: [763, 550, 551, 554, 731, 654, 525, 696, 595, 628, 791, 505, 613, 575, 475, 553, 491, 680, 657, 716], data: [763, 550, 551, 554, 731, 654, 525, 696, 595, 628, 791, 505, 613, 575, 475, 553, 491, 680, 657, 716],
gradient: true, gradient: false,
} }
], ],
lineOps: { lineOps: {
......
...@@ -63,10 +63,10 @@ ...@@ -63,10 +63,10 @@
```html ```html
<!-- xxx.hml --> <!-- xxx.hml -->
<div class="container"> <div class="container">
<qrcode value="{{qr_value}}" class="qrCode" style="color: {{qr_color}};background-color: {{qr_bcol}};"></qrcode> <qrcode value="{{qr_value}}" class="qrCode" style="color: {{qr_col}};background-color: {{qr_bcol}};"></qrcode>
<input type="button" onclick="changeColor" class="button">Color</input> <input type="button" onclick="changeColor" class="button">Color</input>
<input type="button" onclick="changeBackgroundColor" class="button">BackgroundColor</input> <input type="button" onclick="changeBackgroundColor" class="button">BackgroundColor</input>
<input type="button" onclick="changeColor" class="button">Value</input> <input type="button" onclick="changeValue" class="button">Value</input>
</div> </div>
``` ```
...@@ -93,32 +93,32 @@ ...@@ -93,32 +93,32 @@
```javascript ```javascript
// xxx.js // xxx.js
export default { export default {
data: { data: {
qr_col: '#87ceeb', qr_col: '#87ceeb',
qr_bcol: '#f0ffff', qr_bcol: '#f0ffff',
qr_value: 'value' qr_value: 'value'
}, },
changeColor() { changeColor() {
if (this.qr_col == '#87ceeb') { if (this.qr_col == '#87ceeb') {
this.qr_col = '#fa8072'; this.qr_col = '#fa8072';
} else { } else {
this.qr_col = '#87ceeb'; this.qr_col = '#87ceeb';
}
},
changeBackgroundColor() {
if (this.qr_bcol == '#f0ffff') {
this.qr_bcol = '#ffffe0';
} else {
this.qr_bcol = '#f0ffff';
}
},
changeValue() {
if (this.qr_value == 'value') {
this.qr_value = 'change';
} else {
this.qr_value = 'value';
}
} }
},
changeBackgroundColor() {
if (this.qr_bcol == '#f0ffff') {
this.qr_bcol = '#ffffe0';
} else {
this.qr_bcol = '#f0ffff';
}
},
changeValue() {
if (this.qr_value == 'value') {
this.qr_value = 'change';
} else {
this.qr_value = 'value';
}
}
} }
``` ```
......
...@@ -45,8 +45,8 @@ Grid(scroller?: Scroller) ...@@ -45,8 +45,8 @@ Grid(scroller?: Scroller)
| 名称 | 参数类型 | 描述 | | 名称 | 参数类型 | 描述 |
| -------- | -------- | -------- | | -------- | -------- | -------- |
| columnsTemplate | string | 设置当前网格布局列的数量,不设置时默认1列。<br/>例如,&nbsp;'1fr&nbsp;1fr&nbsp;2fr'&nbsp;是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。<br/>‘repeat(auto-fit, 90px)’是设置最小列宽值为90,自动计算列数和实际列宽。<br/>**说明:** <br/>设置为'0fr'时,该列的列宽为0,不显示GridItem。设置为其他非法值时,GridItem显示为固定1列。 | | columnsTemplate | string | 设置当前网格布局列的数量或最小列宽值,不设置时默认1列。<br/>例如,&nbsp;'1fr&nbsp;1fr&nbsp;2fr'&nbsp;是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。<br/>‘repeat(auto-fit, 90px)’是设置最小列宽值为90,自动计算列数和实际列宽。<br/>**说明:** <br/>设置为'0fr'时,该列的列宽为0,不显示GridItem。设置为其他非法值时,GridItem显示为固定1列。 |
| rowsTemplate | string | 设置当前网格布局行的数量,不设置时默认1行。<br/>例如,&nbsp;'1fr&nbsp;1fr&nbsp;2fr'是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。<br/>‘repeat(auto-fit, 90px)’是设置最小行高值为90,自动计算行数和实际行高。<br/>**说明:** <br/>设置为'0fr',则这一行的行宽为0,这一行GridItem不显示。设置为其他非法值,按固定1行处理。 | | rowsTemplate | string | 设置当前网格布局行的数量或最小行高值,不设置时默认1行。<br/>例如,&nbsp;'1fr&nbsp;1fr&nbsp;2fr'是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。<br/>‘repeat(auto-fit, 90px)’是设置最小行高值为90,自动计算行数和实际行高。<br/>**说明:** <br/>设置为'0fr',则这一行的行宽为0,这一行GridItem不显示。设置为其他非法值,按固定1行处理。 |
| columnsGap | [Length](ts-types.md#length) | 设置列与列的间距。<br/>默认值:0<br/>**说明:** <br/>设置为小于0的值时,按默认值显示。 | | columnsGap | [Length](ts-types.md#length) | 设置列与列的间距。<br/>默认值:0<br/>**说明:** <br/>设置为小于0的值时,按默认值显示。 |
| rowsGap | [Length](ts-types.md#length) | 设置行与行的间距。<br/>默认值:0<br/>**说明:** <br/>设置为小于0的值时,按默认值显示。 | | rowsGap | [Length](ts-types.md#length) | 设置行与行的间距。<br/>默认值:0<br/>**说明:** <br/>设置为小于0的值时,按默认值显示。 |
| scrollBar | [BarState](ts-appendix-enums.md#barstate) | 设置滚动条状态。<br/>默认值:BarState.Off<br/>**说明:** <br/>API version 9及以下版本默认值为BarState.Off,API version 10的默认值为BarState.Auto。 | | scrollBar | [BarState](ts-appendix-enums.md#barstate) | 设置滚动条状态。<br/>默认值:BarState.Off<br/>**说明:** <br/>API version 9及以下版本默认值为BarState.Off,API version 10的默认值为BarState.Auto。 |
......
...@@ -21,6 +21,9 @@ ...@@ -21,6 +21,9 @@
## AppStorage ## AppStorage
AppStorage具体UI使用说明,详见[AppStorage(应用全局的UI状态存储)](../../quick-start/arkts-appstorage.md)
### link<sup>10+</sup> ### link<sup>10+</sup>
static link<T>(propName: string): SubscribedAbstractProperty<T> static link<T>(propName: string): SubscribedAbstractProperty<T>
...@@ -698,6 +701,9 @@ let res: number = AppStorage.Size(); // 1 ...@@ -698,6 +701,9 @@ let res: number = AppStorage.Size(); // 1
## LocalStorage<sup>9+</sup> ## LocalStorage<sup>9+</sup>
LocalStorage具体UI使用说明,详见[LocalStorage(页面级UI状态存储)](../../quick-start/arkts-localstorage.md)
### constructor<sup>9+</sup> ### constructor<sup>9+</sup>
constructor(initializingProperties?: Object) constructor(initializingProperties?: Object)
...@@ -735,9 +741,7 @@ static getShared(): LocalStorage ...@@ -735,9 +741,7 @@ static getShared(): LocalStorage
| [LocalStorage](#localstorage9) | 返回LocalStorage实例。 | | [LocalStorage](#localstorage9) | 返回LocalStorage实例。 |
```ts getShared具体使用,见[在UI页面通过getShared接口获取在通过loadContent共享的LocalStorage实例](../../quick-start/arkts-localstorage.md#将localstorage实例从uiability共享到一个或多个视图)
let storage: LocalStorage = LocalStorage.getShared();
```
### has<sup>9+</sup> ### has<sup>9+</sup>
...@@ -1161,6 +1165,10 @@ link.set(50); // PropB -> 49, link.get() --> undefined ...@@ -1161,6 +1165,10 @@ link.set(50); // PropB -> 49, link.get() --> undefined
## PersistentStorage ## PersistentStorage
PersistentStorage具体UI使用说明,详见[PersistentStorage(持久化存储UI状态)](../../quick-start/arkts-persiststorage.md)
### PersistPropsOptions ### PersistPropsOptions
| 参数名 | 类型 | 必填 | 参数描述 | | 参数名 | 类型 | 必填 | 参数描述 |
...@@ -1196,9 +1204,7 @@ static persistProp&lt;T&gt;(key: string, defaultValue: T): void ...@@ -1196,9 +1204,7 @@ static persistProp&lt;T&gt;(key: string, defaultValue: T): void
**示例:** **示例:**
```ts persistProp具体使用,见[从AppStorage中访问PersistentStorage初始化的属性](../../quick-start/arkts-persiststorage.md#从appstorage中访问persistentstorage初始化的属性)
PersistentStorage.persistProp('highScore', '0');
```
### deleteProp<sup>10+</sup> ### deleteProp<sup>10+</sup>
...@@ -1352,6 +1358,9 @@ let keys: Array<string> = PersistentStorage.Keys(); ...@@ -1352,6 +1358,9 @@ let keys: Array<string> = PersistentStorage.Keys();
## Environment ## Environment
Environment具体使用说明,详见[Environment(设备环境查询)](../../quick-start/arkts-environment.md)
### EnvPropsOptions ### EnvPropsOptions
| 参数名 | 类型 | 必填 | 参数描述 | | 参数名 | 类型 | 必填 | 参数描述 |
...@@ -1386,21 +1395,7 @@ static envProp&lt;S&gt;(key: string, value: S): boolean ...@@ -1386,21 +1395,7 @@ static envProp&lt;S&gt;(key: string, value: S): boolean
**示例:** **示例:**
```ts envProp具体使用,见[从UI中访问Environment参数](../../quick-start/arkts-environment.md#从ui中访问environment参数)
Environment.envProp('accessibilityEnabled', 'default');
```
### 内置环境变量说明
| key | 类型 | 说明 |
| -------------------- | --------------- | ---------------------------------------- |
| accessibilityEnabled | string | 无障碍屏幕朗读是否启用。 |
| colorMode | ColorMode | 深浅色模式,可选值为:<br/>-&nbsp;ColorMode.LIGHT:浅色模式;<br/>-&nbsp;ColorMode.DARK:深色模式。 |
| fontScale | number | 字体大小比例。 |
| fontWeightScale | number | 字重比例。 |
| layoutDirection | LayoutDirection | 布局方向类型,可选值为:<br/>-&nbsp;LayoutDirection.LTR:从左到右;<br/>-&nbsp;LayoutDirection.RTL:从右到左。 |
| languageCode | string | 当前系统语言,小写字母,例如zh。 |
### envProps<sup>10+</sup> ### envProps<sup>10+</sup>
...@@ -1525,4 +1520,16 @@ Environment.EnvProps([{ key: 'accessibilityEnabled', defaultValue: 'default' }, ...@@ -1525,4 +1520,16 @@ Environment.EnvProps([{ key: 'accessibilityEnabled', defaultValue: 'default' },
}, { key: 'prop', defaultValue: 'hhhh' }]); }, { key: 'prop', defaultValue: 'hhhh' }]);
let keys: Array<string> = Environment.Keys(); // accessibilityEnabled, languageCode, prop let keys: Array<string> = Environment.Keys(); // accessibilityEnabled, languageCode, prop
``` ```
\ No newline at end of file
## 内置环境变量说明
| key | 类型 | 说明 |
| -------------------- | --------------- | ---------------------------------------- |
| accessibilityEnabled | string | 无障碍屏幕朗读是否启用。 |
| colorMode | ColorMode | 深浅色模式,可选值为:<br/>-&nbsp;ColorMode.LIGHT:浅色模式;<br/>-&nbsp;ColorMode.DARK:深色模式。 |
| fontScale | number | 字体大小比例。 |
| fontWeightScale | number | 字重比例。 |
| layoutDirection | LayoutDirection | 布局方向类型,可选值为:<br/>-&nbsp;LayoutDirection.LTR:从左到右;<br/>-&nbsp;LayoutDirection.RTL:从右到左。 |
| languageCode | string | 当前系统语言,小写字母,例如zh。 |
\ No newline at end of file
...@@ -174,3 +174,20 @@ The data group id is not valid. ...@@ -174,3 +174,20 @@ The data group id is not valid.
从应用市场申请dataGroupId,并正确传入该参数。 从应用市场申请dataGroupId,并正确传入该参数。
## 14800051 分布式表类型不匹配
**错误信息**
The type of the distributed table does not match.
**错误描述**
对同一数据库表设置的分布式表类型前后不一致。
**可能原因**
对同一数据库表设置的分布式表类型前后不一致,分布式表类型可见[DistributedType](../apis/js-apis-data-relationalStore.md#distributedtype10)
**处理步骤**
对同一数据库表设置的分布式表类型保持一致,属于端端同步的分布式表不可再设置为用于端云的同步表。
\ No newline at end of file
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
| X509Cert | getPublicKey() : cryptoFramework.PubKey | 获取证书公钥 | | X509Cert | getPublicKey() : cryptoFramework.PubKey | 获取证书公钥 |
| X509Cert | checkValidityWithDate(date: string) : void | 校验证书有效期 | | X509Cert | checkValidityWithDate(date: string) : void | 校验证书有效期 |
| X509Cert | getVersion() : number | 获取证书版本 | | X509Cert | getVersion() : number | 获取证书版本 |
| X509Cert | getSerialNumber() : number | 获取证书序列号 | | X509Cert | getCertSerialNumber() : bigint<sup>10+</sup> | 获取证书序列号 |
| X509Cert | getIssuerName() : DataBlob | 获取证书颁发者名称 | | X509Cert | getIssuerName() : DataBlob | 获取证书颁发者名称 |
| X509Cert | getSubjectName() : DataBlob | 获取证书主体名称 | | X509Cert | getSubjectName() : DataBlob | 获取证书主体名称 |
| X509Cert | getNotBeforeTime() : string | 获取证书有效期起始时间 | | X509Cert | getNotBeforeTime() : string | 获取证书有效期起始时间 |
......
...@@ -776,7 +776,7 @@ ...@@ -776,7 +776,7 @@
## ohos.permission.MANAGE_MISSIONS ## ohos.permission.MANAGE_MISSIONS
允许用户管理元能力任务栈 允许应用管理系统中的任务
**权限级别**:system_core **权限级别**:system_core
...@@ -860,7 +860,7 @@ ...@@ -860,7 +860,7 @@
## ohos.permission.SET_ABILITY_CONTROLLER ## ohos.permission.SET_ABILITY_CONTROLLER
允许设置ability组件启动和停止控制权 允许应用拦截Ability组件启动,主要用于测试调试,比如稳定性金刚测试
**权限级别**:system_basic **权限级别**:system_basic
...@@ -956,7 +956,7 @@ ...@@ -956,7 +956,7 @@
## ohos.permission.RUNNING_STATE_OBSERVER ## ohos.permission.RUNNING_STATE_OBSERVER
允许应用观察应用状态。 允许应用监听应用状态。
**权限级别**:system_basic **权限级别**:system_basic
...@@ -1656,7 +1656,7 @@ ...@@ -1656,7 +1656,7 @@
## ohos.permission.CAMERA ## ohos.permission.CAMERA
允许应用使用相机拍摄照片和录制视频 允许应用使用相机。
**权限级别**:normal **权限级别**:normal
...@@ -2683,3 +2683,15 @@ ...@@ -2683,3 +2683,15 @@
**ACL使能**:TRUE **ACL使能**:TRUE
**起始版本**:10 **起始版本**:10
## ohos.permission.MANAGE_DEVICE_AUTH_CRED
允许应用调用设备认证华为帐号凭据管理应用接口。
**权限级别**:system_basic
**授权方式**:system_grant
**ACL使能**:FALSE
**起始版本**:10
...@@ -185,9 +185,9 @@ java -jar app_unpacking_tool.jar --mode <option> --appqf-path <options> --out-pa ...@@ -185,9 +185,9 @@ java -jar app_unpacking_tool.jar --mode <option> --appqf-path <options> --out-pa
| appName | String | 标识显示在桌面上的ability的label | NA | | appName | String | 标识显示在桌面上的ability的label | NA |
| appNameEN | String | 标识显示在桌面上的ability的label | NA | | appNameEN | String | 标识显示在桌面上的ability的label | NA |
| releaseType | String | 标识应用运行需要的API目标版本的类型 | NA | | releaseType | String | 标识应用运行需要的API目标版本的类型 | NA |
| shellVersionCode | String | 标识HarmonyOS中的API版本 | NA | | shellVersionCode | String | 标识应用的API版本号 | NA |
| shellVersionName | String | 标识HarmonyOS中的API版本名称 | NA | | shellVersionName | String | 标识应用的API版本名称 | NA |
| multiFrameworkBundle | String | 标识双框架下app的bundleName | NA | | multiFrameworkBundle | boolean | 标识应用框架 | NA |
| debug | boolean | 标识应用是否可调试 | NA | | debug | boolean | 标识应用是否可调试 | NA |
| icon | String | 标识应用的图标路径 | NA | | icon | String | 标识应用的图标路径 | NA |
| label | String | 标识应用的label | NA | | label | String | 标识应用的label | NA |
......
...@@ -191,3 +191,9 @@ HiDumper可以为开发者导出系统当前基本信息,通过这些基本信 ...@@ -191,3 +191,9 @@ HiDumper可以为开发者导出系统当前基本信息,通过这些基本信
``` ```
hidumper -t [timeout] hidumper -t [timeout]
``` ```
19. 运行 hidumper --mem-smaps pid [-v] 命令获取指定进程内存信息的详细使用情况。
```
hidumper --mem-smaps pid [-v]
```
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
- [OpenHarmony v3.2 Release (2023-04-09)](OpenHarmony-v3.2-release.md) - [OpenHarmony v3.2 Release (2023-04-09)](OpenHarmony-v3.2-release.md)
- [OpenHarmony v3.2.1 Release (2023-05-22)](OpenHarmony-v3.2.1-release.md) - [OpenHarmony v3.2.1 Release (2023-05-22)](OpenHarmony-v3.2.1-release.md)
- [OpenHarmony v3.2.2 Release (2023-06-27)](OpenHarmony-v3.2.2-release.md)
- [OpenHarmony v3.2 Beta5 (2023-01-31)](OpenHarmony-v3.2-beta5.md) - [OpenHarmony v3.2 Beta5 (2023-01-31)](OpenHarmony-v3.2-beta5.md)
- [OpenHarmony v3.2 Beta4 (2022-11-30)](OpenHarmony-v3.2-beta4.md) - [OpenHarmony v3.2 Beta4 (2022-11-30)](OpenHarmony-v3.2-beta4.md)
- [OpenHarmony v3.2 Beta3 (2022-09-30)](OpenHarmony-v3.2-beta3.md) - [OpenHarmony v3.2 Beta3 (2022-09-30)](OpenHarmony-v3.2-beta3.md)
......
# 安全子系统ChangeLog
## cl.cert.1 X509Cert.getSerialNumber接口废弃变更
X509Cert.getSerialNumber接口作废弃处理,变更为X509Cert.getCertSerialNumber接口。
**变更影响**
从API version 10开始,X509Cert.getSerialNumber接口废弃。应用需要自行适配为X509Cert.getCertSerialNumber。接口功能不变。
**关键的接口/组件变更**
修改前的接口原型:
```js
interface X509Cert {
...
getSerialNumber(): number;
...
}
```
修改后的接口原型:
```js
interface X509Cert {
...
getCertSerialNumber(): bigint;
...
}
```
**适配指导**
接口使用的示例代码可参考[证书开发指导](../../../application-dev/security/cert-guidelines.md)[API参考](../../../application-dev/reference/apis/js-apis-cert.md)
...@@ -21,6 +21,8 @@ mediaLibrary部分接口兼容性变更。 ...@@ -21,6 +21,8 @@ mediaLibrary部分接口兼容性变更。
| medialibrary | **function** getAlbums(options: MediaFetchOptions): Promise&lt;Array&lt;Album&gt;&gt; | 接口兼容性变更 | | medialibrary | **function** getAlbums(options: MediaFetchOptions): Promise&lt;Array&lt;Album&gt;&gt; | 接口兼容性变更 |
| medialibrary | **function** FileAsset.commitModify(callback: AsyncCallback&lt;void&gt;): void | 接口兼容性变更 | | medialibrary | **function** FileAsset.commitModify(callback: AsyncCallback&lt;void&gt;): void | 接口兼容性变更 |
| medialibrary | **function** FileAsset.commitModify(): Promise&lt;void&gt; | 接口兼容性变更 | | medialibrary | **function** FileAsset.commitModify(): Promise&lt;void&gt; | 接口兼容性变更 |
| medialibrary | **function** FileAsset.deleteAsset(uri: string, callback: AsyncCallback&lt;void&gt;): void | 接口兼容性变更 |
| medialibrary | **function** FileAsset.deleteAsset(uri: string): Promise&lt;void&gt; | 接口兼容性变更 |
**适配指导** **适配指导**
...@@ -145,3 +147,40 @@ async function example() { ...@@ -145,3 +147,40 @@ async function example() {
**FileAsset.commitModify接口获取相册兼容性影响:** **FileAsset.commitModify接口获取相册兼容性影响:**
在API version 10的SDK上去掉了针对audio无意义的orientation属性,在使用commitModify接口时将无法对audio资源的orientation属性进行修改。 在API version 10的SDK上去掉了针对audio无意义的orientation属性,在使用commitModify接口时将无法对audio资源的orientation属性进行修改。
**FileAsset.deleteAsset接口删除媒体文件资源兼容性影响:**
修复了文件删除机制的已知问题。该问题会导致系统应用可以通过MediaLibrary.deleteAsset方法将一个没有进入回收站的文件直接彻底删除。
对于系统应用,彻底删除一个文件应该按照以下步骤进行操作:
1. 调用[getFileAssets](../../../application-dev/reference/apis/js-apis-medialibrary.md#getfileassets7)接口获取文件资源。
2. 然后调用[FileAsset.trash](../../../application-dev/reference/apis/js-apis-medialibrary.md#trash8)接口将文件放入回收站。
3. 最后调用[MediaLibrary.deleteAsset](../../../application-dev/reference/apis/js-apis-medialibrary.md#deleteasset8)方法将文件彻底删除。
**正确示例:**
```js
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
async function example() {
try {
let context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let fileKeyObj = mediaLibrary.FileKey;
let imageType = mediaLibrary.MediaType.IMAGE;
let getImageOp = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [imageType.toString()],
};
const fetchFileResult = await media.getFileAssets(getImageOp);
const fileAsset = await fetchFileResult.getFirstObject();
// 将文件删除(放入系统图库的回收站)。
await fileAsset.trash(true);
// 将文件从系统中彻底删除。
await media.deleteAsset(fileAsset.uri);
} catch (err) {
console.error('Failed to delete asset permanently from system, error: ' + err);
}
}
```
...@@ -7,3 +7,42 @@ FormComponent组件中的JS卡片禁止使用网络图片。 ...@@ -7,3 +7,42 @@ FormComponent组件中的JS卡片禁止使用网络图片。
**变更影响** **变更影响**
影响FormComponent中JS卡片中网络图片的加载显示。变更前JS卡片支持网络图片加载,变更后JS卡片不支持网络图片的加载。变更后建议将所需的网络图片下载至内存后刷新。 影响FormComponent中JS卡片中网络图片的加载显示。变更前JS卡片支持网络图片加载,变更后JS卡片不支持网络图片的加载。变更后建议将所需的网络图片下载至内存后刷新。
## cl.arkui.2 TextInput、TextArea、Search组件行为变更
TextInput、TextArea、Search组件获焦默认拉起输入法
开发者需要根据以下说明对应用进行适配。
**变更影响**
影响Api version 10的TextInput、TextArea、Search组件
**关键的接口/组件变更**
TextInput、TextArea、Search组件获焦会拉起输入法
**适配指导**
可以通过接口enableKeyboardOnFocus(bool)控制是否获焦拉起输入法,此接口参数默认值为true。
## cl.arkui.3 组件属性以及参数非法值拦截
非法值使用默认值
开发者需要根据以下说明对应用进行适配。
**变更影响**
影响所有设置了非法值的组件属性
**关键的接口/组件变更**
原先属性值由正常值变为非法值会跳过非法值的设置,变更后会拦截非法值并设置属性的默认值。对于没有默认值的属性如width、height等,则会取消其原先的正常值设置。
**适配指导**
组件属性以及参数非法值拦截排查非法值如"100abc"等,将其改为正常值。
# bluetooth子系统ChangeLog
## cl.bluetooth.1 蓝牙模块JS接口拆分,支持按profile加载接口能力。
1. ArkTS接口,拆分在对应的.d.ts中。
2. 权限变更,接口权限统一变更为ohos.permission.ACCESS_BLUETOOTH,[部分接口](#需要双权限校验的接口)额外需要ohos.permission.MANAGE_BLUETOOTH权限。
| 模块 | 接口文档 |
| -------- | ---------------------------- |
|@ohos.bluetooth.a2dp | [js-apis-bluetooth-a2dp.md](../../../application-dev/reference/apis/js-apis-bluetooth-a2dp.md) |
|@ohos.bluetooth.access | [js-apis-bluetooth-access.md](../../../application-dev/reference/apis/js-apis-bluetooth-access.md) |
|@ohos.bluetooth.baseProfile | [js-apis-bluetooth-baseProfile.md](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md) |
|@ohos.bluetooth.ble | [js-apis-bluetooth-ble.md](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md) |
|@ohos.bluetooth.connection | [js-apis-bluetooth-connection.md](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md) |
|@ohos.bluetooth.constant | [js-apis-bluetooth-constant.md](../../../application-dev/reference/apis/js-apis-bluetooth-constant.md) |
|@ohos.bluetooth.hfp | [js-apis-bluetooth-hfp.md](../../../application-dev/reference/apis/js-apis-bluetooth-hfp.md) |
|@ohos.bluetooth.hid | [js-apis-bluetooth-hid.md](../../../application-dev/reference/apis/js-apis-bluetooth-hid.md) |
|@ohos.bluetooth.pan | [js-apis-bluetooth-pan.md](../../../application-dev/reference/apis/js-apis-bluetooth-pan.md) |
|@ohos.bluetooth.socket | [js-apis-bluetooth-socket.md](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md) |
### 需要双权限校验的接口
| 模块 | 接口声明 |
|------|---------|
|@ohos.bluetooth.baseProfile|[setConnectionStrategy(deviceId: string, strategy: ConnectionStrategy, callback: AsyncCallback&lt;void&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilesetconnectionstrategy) |
|@ohos.bluetooth.baseProfile| [setConnectionStrategy(deviceId: string, strategy: ConnectionStrategy): Promise&lt;void&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilesetconnectionstrategy-1) |
|@ohos.bluetooth.baseProfile| [getConnectionStrategy(deviceId: string, callback: AsyncCallback&lt;ConnectionStrategy&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilegetconnectionstrategy)|
|@ohos.bluetooth.baseProfile| [getConnectionStrategy(deviceId: string): Promise&lt;ConnectionStrategy&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilegetconnectionstrategy-1) |
|@ohos.bluetooth.connection| [pairCredibleDevice(deviceId: string, transport: BluetoothTransport, callback: AsyncCallback&lt;void&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionpaircredibledevice) |
|@ohos.bluetooth.connection| [pairCredibleDevice(deviceId: string, transport: BluetoothTransport): Promise&lt;void&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionpaircredibledevice-1) |
|@ohos.bluetooth.connection| [setDevicePairingConfirmation(deviceId: string, accept: boolean): void](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionsetdevicepairingconfirmation) |
|connect(hid profile) | [connect(deviceId: string): void](../../../application-dev/reference/apis/js-apis-bluetooth-hid.md#connect) |
|disconnect(hid profile) | [disconnect(deviceId: string): void](../../../application-dev/reference/apis/js-apis-bluetooth-hid.md#disconnect) |
|setTethering | [setTethering(enable: boolean): void](../../../application-dev/reference/apis/js-apis-bluetooth-pan.md#settethering) |
**变更影响**
1. TS拆分后,接口调用import文件发生改变。
2. 权限变更后,只需关注ohos.permission.ACCESS_BLUETOOTH和ohos.permission.MANAGE_BLUETOOTH权限。
**关键接口/组件变更**
| 拆分前 | 拆分后 |
| -------- | ---------------------------- |
|bluetoothManager.enableBluetooth | [access.enableBluetooth](../../../application-dev/reference/apis/js-apis-bluetooth-access.md#accessenablebluetooth) |
|bluetoothManager.disableBluetooth | [access.disableBluetooth](../../../application-dev/reference/apis/js-apis-bluetooth-access.md#accessdisablebluetooth) |
|bluetoothManager.getLocalName | [connection.getLocalName](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetlocalname) |
|bluetoothManager.getState | [access.getState](../../../application-dev/reference/apis/js-apis-bluetooth-access.md#accessgetstate) |
|bluetoothManager.getBtConnectionState | [connection.getProfileConnectionState](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetprofileconnectionstate) |
|bluetoothManager.setLocalName | [connection.setLocalName](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionsetlocalname) |
|bluetoothManager.pairDevice | [connection.pairDevice](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionpairdevice) |
|bluetoothManager.getProfileConnectionState | [connection.getProfileConnectionState](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetprofileconnectionstate) |
|bluetoothManager.cancelPairedDevice | [connection.cancelPairedDevice](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectioncancelpaireddevice) |
|bluetoothManager.getRemoteDeviceName | [connection.getRemoteDeviceName](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetremotedevicename) |
|bluetoothManager.getRemoteDeviceClass | [connection.getRemoteDeviceClass](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetremotedeviceclass) |
|bluetoothManager.getPairedDevices | [connection.getPairedDevices](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetpaireddevices) |
|bluetoothManager.setBluetoothScanMode | [connection.setBluetoothScanMode](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionsetbluetoothscanmode) |
|bluetoothManager.getBluetoothScanMode | [connection.getBluetoothScanMode](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetbluetoothscanmode) |
|bluetoothManager.startBluetoothDiscovery | [connection.startBluetoothDiscovery](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionstartbluetoothdiscovery) |
|bluetoothManager.stopBluetoothDiscovery | [connection.stopBluetoothDiscovery](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionstopbluetoothdiscovery) |
|bluetoothManager.setDevicePairingConfirmation | [connection.setDevicePairingConfirmation](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionsetdevicepairingconfirmation) |
|bluetoothManager.on('bluetoothDeviceFind') | [connection.on('bluetoothDeviceFind')](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiononbluetoothdevicefind) |
|bluetoothManager.off('bluetoothDeviceFind') | [connection.off('bluetoothDeviceFind')](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionoffbluetoothdevicefind) |
|bluetoothManager.on('pinRequired') | [connection.on('pinRequired')](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiononpinrequired) |
|bluetoothManager.off('pinRequired') | [connection.off('pinRequired')](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionoffpinrequired) |
|bluetoothManager.on('bondStateChange') | [connection.on('bondStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiononbondstatechange) |
|bluetoothManager.off('bondStateChange') | [connection.off('bondStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionoffbondstatechange) |
|bluetoothManager.on('stateChange') | [access.on('stateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-access.md#accessonstatechange) |
|bluetoothManager.off('stateChange') | [access.off('stateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-access.md#accessoffstatechange) |
|bluetoothManager.sppListen | [socket.sppListen](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketspplisten) |
|bluetoothManager.sppAccept | [socket.sppAccept](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketsppaccept) |
|bluetoothManager.sppConnect | [socket.sppConnect](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionoffbluetoothdevicefind) |
|bluetoothManager.sppCloseServerSocket | [socket.sppCloseServerSocket](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketsppcloseserversocket) |
|bluetoothManager.sppCloseClientSocket | [socket.sppCloseClientSocket](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketsppcloseclientsocket) |
|bluetoothManager.sppWrite | [socket.sppWrite](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketsppwrite) |
|bluetoothManager.on('sppRead') | [socket.on('sppRead')](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketonsppread) |
|bluetoothManager.off('sppRead') | [socket.off('sppRead')](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#socketoffsppread) |
|bluetoothManager.getProfileInstance | 已废弃 |
|createGattServer | [ble.createGattServer](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blecreategattserver) |
|createGattClientDevice | [ble.createGattClientDevice](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blegetconnectedbledevices) |
|getConnectedBLEDevices | [ble.getConnectedBLEDevices](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blegetconnectedbledevices) |
|startBLEScan | [ble.startBLEScan](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blestartblescan) |
|stopBLEScan | [ble.stopBLEScan](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blestopblescan) |
|on('BLEDeviceFind') | [ble.on('BLEDeviceFind')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#bleonbledevicefind) |
|off('BLEDeviceFind') | [ble.off('BLEDeviceFind')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#bleoffbledevicefind) |
|getConnectionDevices | [baseProfile.getConnectedDevices](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilegetconnecteddevices) |
|getDeviceState | [baseProfile.getConnectionState](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilegetconnectionstate) |
|A2dpSourceProfile | [A2dpSourceProfile](../../../application-dev/reference/apis/js-apis-bluetooth-a2dp.md#a2dpsourceprofile) |
|connect(a2dp profile) | [connect](../../../application-dev/reference/apis/js-apis-bluetooth-a2dp.md#connect) |
|disconnect(a2dp profile) | [disconnect](../../../application-dev/reference/apis/js-apis-bluetooth-a2dp.md#disconnect) |
|on('connectionStateChange') | [baseProfile.on('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileonconnectionstatechange) |
|off('connectionStateChange') | [baseProfile.off('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileoffconnectionstatechange) |
|getPlayingState | [getPlayingState](../../../application-dev/reference/apis/js-apis-bluetooth-a2dp.md#getplayingstate) |
|HandsFreeAudioGatewayProfile | [HandsFreeAudioGatewayProfile](../../../application-dev/reference/apis/js-apis-bluetooth-hfp.md#handsfreeaudiogatewayprofile) |
|connect(hfp profile) | [connect](../../../application-dev/reference/apis/js-apis-bluetooth-hfp.md#connect) |
|disconnect(hfp profile) | [disconnect](../../../application-dev/reference/apis/js-apis-bluetooth-hfp.md#disconnect) |
|on('connectionStateChange')(hfp profile) | [baseProfile.on('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileonconnectionstatechange) |
|off('connectionStateChange')(hfp profile) | [baseProfile.off('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileoffconnectionstatechange) |
|connect(hid profile) | [connect](../../../application-dev/reference/apis/js-apis-bluetooth-hid.md#connect) |
|disconnect(hid profile) | [disconnect](../../../application-dev/reference/apis/js-apis-bluetooth-hid.md#disconnect) |
|on('connectionStateChange')(hid profile) | [baseProfile.on('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileonconnectionstatechange) |
|off('connectionStateChange')(hid profile) | [baseProfile.off('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileoffconnectionstatechange) |
|disconnect(pan profile) | [disconnect](../../../application-dev/reference/apis/js-apis-bluetooth-pan.md#disconnect) |
|on('connectionStateChange')(pan profile) | [baseProfile.on('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileonconnectionstatechange) |
|off('connectionStateChange')(pan profile) | [baseProfile.off('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofileoffconnectionstatechange) |
|setTethering | [setTethering](../../../application-dev/reference/apis/js-apis-bluetooth-pan.md#settethering) |
|isTetheringOn | [isTetheringOn](../../../application-dev/reference/apis/js-apis-bluetooth-pan.md#istetheringon) |
|startAdvertising | [ble.startAdvertising](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blestartadvertising) |
|stopAdvertising | [ble.stopAdvertising](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blestopadvertising) |
|addService | [addService](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#addservice) |
|removeService | [removeService](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#removeservice) |
|close | [close](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#close) |
|notifyCharacteristicChanged | [notifyCharacteristicChanged](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#notifycharacteristicchanged) |
|sendResponse | [sendResponse](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#sendresponse) |
|on('characteristicRead') | [on('characteristicRead')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#oncharacteristicread) |
|off('characteristicRead') | [off('characteristicRead')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offcharacteristicread) |
|on('characteristicWrite') | [on('characteristicWrite')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#oncharacteristicwrite) |
|off('characteristicWrite') | [off('characteristicWrite')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offcharacteristicwrite) |
|on('descriptorRead') | [on('descriptorRead')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#ondescriptorread) |
|off('descriptorRead') | [off('descriptorRead')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offdescriptorread) |
|on('descriptorWrite') | [on('descriptorWrite')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#ondescriptorwrite) |
|off('descriptorWrite') | [off('descriptorWrite')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offdescriptorwrite) |
|on('connectStateChange') | [on('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#onconnectionstatechange) |
|off('connectStateChange') | [off('connectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offconnectionstatechange) |
|connect | [connect](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#connect) |
|disconnect | [disconnect](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#disconnect) |
|close | [close](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#close) |
|getServices(callback) | [getServices](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#getservices) |
|getServices(promise) | [getServices](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#getservices-1) |
|readCharacteristicValue(callback) | [readCharacteristicValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#readcharacteristicvalue) |
|readCharacteristicValue(promise) | [readCharacteristicValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#readcharacteristicvalue-1) |
|readDescriptorValue(callback) | [readDescriptorValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#readdescriptorvalue) |
|readDescriptorValue(promise) | [readDescriptorValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#readdescriptorvalue-1) |
|writeCharacteristicValue(callback) | [writeCharacteristicValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#writecharacteristicvalue) |
|writeDescriptorValue(promise) | [writeCharacteristicValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#writecharacteristicvalue-1) |
|setBLEMtuSize | [setBLEMtuSize](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#setblemtusize) |
|setNotifyCharacteristicChanged | [setCharacteristicChangeNotification](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#setcharacteristicchangenotification) |
|on('BLECharacteristicChange') | [on('BLECharacteristicChange')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#onblecharacteristicchange) |
|off('BLECharacteristicChange') | [off('BLECharacteristicChange')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offblecharacteristicchange) |
|on('BLEConnectionStateChange') | [on('BLEConnectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#onbleconnectionstatechange) |
|off('BLEConnectionStateChange') | [off('BLEConnectionStateChange')](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#offbleconnectionstatechange) |
|getDeviceName(callback) | [getDeviceName](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#getdevicename) |
|getDeviceName(promise) | [getDeviceName](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#getdevicename-1) |
|getRssiValue(callback) | [getRssiValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#getrssivalue) |
|getRssiValue(promise) | [getRssiValue](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#getrssivalue-1) |
|ScanMode | [ScanMode](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#scanmode) |
|BondState | [BondState](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#bondstate) |
|SppOption | [SppOptions](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#sppoptions) |
|SppType | [SppType](../../../application-dev/reference/apis/js-apis-bluetooth-socket.md#spptype) |
|GattService | [GattService](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#gattservice) |
|BLECharacteristic | [BLECharacteristic](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blecharacteristic) |
|BLEDescriptor | [BLEDescriptor](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#bledescriptor) |
|NotifyCharacteristic | [NotifyCharacteristic](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#notifycharacteristic) |
|CharacteristicReadRequest | [CharacteristicReadRequest](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#characteristicreadrequest) |
|CharacteristicWriteRequest | [CharacteristicWriteRequest](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#characteristicwriterequest) |
|DescriptorReadRequest | [DescriptorReadRequest](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#descriptorreadrequest) |
|DescriptorWriteRequest | [DescriptorWriteRequest](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#descriptorwriterequest) |
|ServerResponse | [ServerResponse](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#serverresponse) |
|BLEConnectChangedState | [BLEConnectionChangeState](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#bleconnectionchangestate) |
|ProfileConnectionState | [ProfileConnectionState](../../../application-dev/reference/apis/js-apis-bluetooth-constant.md#profileconnectionstate) |
|ScanFilter | [ScanFilter](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#scanfilter) |
|ScanOptions | [ScanOptions](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#scanoptions) |
|ScanDuty | [ScanDuty](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#scanduty) |
|MatchMode | [MatchMode](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#matchmode) |
|ScanResult | [ScanResult](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#scanresult) |
|BluetoothState | [BluetoothState](../../../application-dev/reference/apis/js-apis-bluetooth-access.md#bluetoothstate) |
|AdvertiseSetting | [AdvertiseSetting](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#advertisesetting) |
|AdvertiseData | [AdvertiseData](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#advertisedata) |
|ManufactureData | [ManufactureData](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#manufacturedata) |
|ServiceData | [ServiceData](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#servicedata) |
|PinRequiredParam | [PinRequiredParam](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#pinrequiredparam) |
|BondStateParam | [BondStateParam](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#bondstateparam) |
|StateChangeParam | [StateChangeParam](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#statechangeparam) |
|DeviceClass | [DeviceClass](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#deviceclass) |
|MajorClass | [MajorClass](../../../application-dev/reference/apis/js-apis-bluetooth-constant.md#majorclass) |
|MajorMinorClass | [MajorMinorClass](../../../application-dev/reference/apis/js-apis-bluetooth-constant.md#majorminorclass) |
|PlayingState | [PlayingState](../../../application-dev/reference/apis/js-apis-bluetooth-a2dp.md#playingstate) |
|ProfileId | [ProfileId](../../../application-dev/reference/apis/js-apis-bluetooth-constant.md#profileid) |
### 新增接口声明
| 模块名 |新增声明 |
| ------------- |-------------------------------------------------------- |
|@ohos.bluetooth.connection| [pairCredibleDevice(deviceId: string, transport: BluetoothTransport, callback: AsyncCallback&lt;void&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionpaircredibledevice) |
|@ohos.bluetooth.connection| [pairCredibleDevice(deviceId: string, transport: BluetoothTransport): Promise&lt;void&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionpaircredibledevice-1) |
|@ohos.bluetooth.connection| [getRemoteProfileUuids(deviceId: string, callback: AsyncCallback&lt;Array&lt;ProfileUuids&gt;&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetremoteprofileuuids) |
|@ohos.bluetooth.connection| [getRemoteProfileUuids(deviceId: string): Promise&lt;Array&lt;ProfileUuids&gt;&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetremoteprofileuuids-1) |
|@ohos.bluetooth.connection| [getLocalProfileUuids(callback: AsyncCallback&lt;Array&lt;ProfileUuids&gt;&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetlocalprofileuuids) |
|@ohos.bluetooth.connection| [getLocalProfileUuids(): Promise&lt;Array&lt;ProfileUuids&gt;&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectiongetlocalprofileuuids-1) |
|@ohos.bluetooth.connection| [setDevicePinCode(deviceId: string, code: string, callback: AsyncCallback&lt;void&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionsetdevicepincode) |
|@ohos.bluetooth.connection| [setDevicePinCode(deviceId: string, code: string): Promise&lt;void&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#connectionsetdevicepincode-1) |
|@ohos.bluetooth.baseProfile| [setConnectionStrategy(deviceId: string, strategy: ConnectionStrategy, callback: AsyncCallback&lt;void&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilesetconnectionstrategy)|
|@ohos.bluetooth.baseProfile| [setConnectionStrategy(deviceId: string, strategy: ConnectionStrategy): Promise&lt;void&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilesetconnectionstrategy-1) |
|@ohos.bluetooth.baseProfile| [getConnectionStrategy(deviceId: string, callback: AsyncCallback&lt;ConnectionStrategy&gt;): void](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilegetconnectionstrategy)|
|@ohos.bluetooth.baseProfile| [getConnectionStrategy(deviceId: string): Promise&lt;ConnectionStrategy&gt;](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#baseprofilegetconnectionstrategy-1) |
|@ohos.bluetooth.baseprofile|[ConnectionStrategy](../../../application-dev/reference/apis/js-apis-bluetooth-baseProfile.md#connectionstrategy)|
|@ohos.bluetooth.ble|[properties](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#blecharacteristic)|
|@ohos.bluetooth.ble|[deviceName](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#scanresult)|
|@ohos.bluetooth.ble|[includeDeviceName](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#advertisedata)|
|@ohos.bluetooth.ble|[GattProperties](../../../application-dev/reference/apis/js-apis-bluetooth-ble.md#gattproperties)|
|@ohos.bluetooth.connection|[pinType](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#pintype)|
|@ohos.bluetooth.connection|[BluetoothTransport](../../../application-dev/reference/apis/js-apis-bluetooth-connection.md#bluetoothtransport)|
|@ohos.bluetooth.constant|[ProfileUuids](../../../application-dev/reference/apis/js-apis-bluetooth-constant.md#profileuuids)|
**适配指导**
注意根据不同profile模块选择import方式,部分接口需要import多个TS文件。
```js
//例如@ohos.bluetooth.baseProfile下getConnectionState接口。
import a2dp from '@ohos.bluetooth.a2dp';
try {
let a2dpSrc = a2dp.createA2dpSrcProfile();
let ret = a2dpSrc.getConnectionState('XX:XX:XX:XX:XX:XX');
} catch (err) {
console.error('errCode: ' + err.code + ', errMessage: ' + err.message);
}
```
...@@ -226,4 +226,38 @@ API version 10及以后:onReady在组件创建完成时触发,在组件位 ...@@ -226,4 +226,38 @@ API version 10及以后:onReady在组件创建完成时触发,在组件位
**变更影响** **变更影响**
onReady事件在组件位置发生变化时行为变更,API version 9及以前会触发,API version 10及以后不会触发。 onReady事件在组件位置发生变化时行为变更,API version 9及以前会触发,API version 10及以后不会触发。
\ No newline at end of file
## cl.arkui.4 Margin属性百分比计算变更
当前margin会计算两次,margin的百分比参照在第一次margin计算之后会减去margin的大小,计算完之后百分比类型margin第二次计算时数值偏小。变更后margin第一次计算的结果为最终值,不进行第二次计算。
**变更影响**
所有设置百分比的margin大小。
**错误示例:**
```ts
// xxx.ets
@Entry
@Component
struct TextInputExample {
@State text: string = ''
controller: TextInputController = new TextInputController()
build() {
Column(){
Row().margin({left:"50%"}).width(100).height(100)
}.width("100%").height("100%")
}
}
```
**关键接口/组件变更**
不涉及
**适配指导**
变更后margin的百分比参照固定为父组件的宽减去父组件的padding且不会减去第一次margin的计算结果,其百分比参照比变更前稍大,建议调整margin百分比的数值。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册