提交 83e12c48 编写于 作者: 冯俊青 提交者: Gitee

Merge branch 'monthly_20230815' of gitee.com:openharmony/docs into mergeMonth0815

Signed-off-by: N冯俊青 <fengjunqing@huawei.com>
......@@ -331,7 +331,7 @@ zh-cn/application-dev/reference/apis/js-apis-prompt.md @HelloCrease @niulihua @t
zh-cn/application-dev/reference/apis/js-apis-queue.md @gongjunsong @ge-yafang @flyingwolf @blackstone-oh
zh-cn/application-dev/reference/apis/js-apis-radio.md @zhang-hai-feng @zengyawen @jyh926 @gaoxi785
zh-cn/application-dev/reference/apis/js-apis-reminderAgent.md @jayleehw @RayShih @li-weifeng2 @currydavids
zh-cn/application-dev/reference/apis/js-apis-request.md @feng-aiwen @ningningW @nagexiucai @murphy1984
zh-cn/application-dev/reference/apis/js-apis-request.md @feng-aiwen @ningningW @hanruofei @murphy1984
zh-cn/application-dev/reference/apis/js-apis-resource-manager.md @Buda-Liu @ningningW @mengjingzhimo @yangqing3
zh-cn/application-dev/reference/apis/js-apis-router.md @HelloCrease @niulihua @tomatodevboy
zh-cn/application-dev/reference/apis/js-apis-rpc.md @xuepianpian @RayShih @zhaopeng_gitee @vagrant_world
......@@ -366,7 +366,7 @@ zh-cn/application-dev/reference/apis/js-apis-system-notification.md @jayleehw @R
zh-cn/application-dev/reference/apis/js-apis-system-package.md @shuaytao @RayShih @wangzhen107 @inter515
zh-cn/application-dev/reference/apis/js-apis-system-parameter.md @mupceet @zengyawen @handyohos @nan-xiansen
zh-cn/application-dev/reference/apis/js-apis-system-prompt.md @HelloCrease @niulihua @tomatodevboy
zh-cn/application-dev/reference/apis/js-apis-system-request.md @zhang-hai-feng @zengyawen @jyh926 @gaoxi785
zh-cn/application-dev/reference/apis/js-apis-system-request.md @zhang-hai-feng @ningningW @jyh926 @gaoxi785
zh-cn/application-dev/reference/apis/js-apis-system-router.md @HelloCrease @niulihua @tomatodevboy
zh-cn/application-dev/reference/apis/js-apis-system-sensor.md @hellohyh001 @ningningW @butterls @star-wind-snow-and-rain
zh-cn/application-dev/reference/apis/js-apis-system-storage.md @feng-aiwen @ge-yafang @gong-a-shi @logic42
......
......@@ -62,7 +62,7 @@ sequenceable a.b..C.D
The preceding statement is parsed into the following code in the C++ header file:
```cpp
#include "a/b/d.h"
#include "a/b/d.h"
using C::D;
```
......@@ -347,11 +347,10 @@ export default {
#### Calling Methods from the Client for IPC
When the client calls **connectAbility()** to connect to a Service ability, the **onConnect** callback in **onAbilityConnectDone** of the client receives the **IRemoteObject** instance returned by the **onConnect()** method of the Service ability. The client and Service ability are in different applications. Therefore, the directory of the client application must contain a copy of the .idl file (the SDK automatically generates the proxy class). The **onConnect** callback then uses the **IRemoteObject** instance to create the **testProxy** instance of the **IdlTestServiceProxy** class and calls the related IPC method. The sample code is as follows:
When the client calls **connectServiceExtensionAbility()** to connect to a Service ability, the **onConnect** callback in **onAbilityConnectDone** of the client receives the **IRemoteObject** instance returned by the **onConnect()** method of the Service ability. The client and Service ability are in different applications. Therefore, the directory of the client application must contain a copy of the .idl file (the SDK automatically generates the proxy class). The **onConnect** callback then uses the **IRemoteObject** instance to create the **testProxy** instance of the **IdlTestServiceProxy** class and calls the related IPC method. The sample code is as follows:
```ts
import IdlTestServiceProxy from './idl_test_service_proxy'
import featureAbility from '@ohos.ability.featureAbility';
function callbackTestIntTransaction(result: number, ret: number): void {
if (result == 0 && ret == 124) {
......@@ -396,13 +395,13 @@ var onAbilityConnectDone = {
}
};
function connectAbility: void {
function connectAbility(): void {
let want = {
bundleName: 'com.example.myapplicationidl',
abilityName: 'com.example.myapplicationidl.ServiceAbility'
};
let connectionId = -1;
connectionId = featureAbility.connectAbility(want, onAbilityConnectDone);
connectionId = this.context.connectServiceExtensionAbility(want, onAbilityConnectDone);
}
......
# Ability Development
> **NOTE**<br/>
> This folder is deprecated. Read [Application Models](../application-models/Readme-EN.md) instead.
- [Ability Framework Overview](ability-brief.md)
- [Context Usage](context-userguide.md)
- FA Model
- [FA Model Overview](fa-brief.md)
- [Page Ability Development](fa-pageability.md)
- [Service Ability Development](fa-serviceability.md)
- [Data Ability Development](fa-dataability.md)
- [FA Widget Development](fa-formability.md)
- Stage Model
- [Stage Model Overview](stage-brief.md)
- [Ability Development](stage-ability.md)
- [Service Extension Ability Development](stage-serviceextension.md)
- [Ability Continuation Development](stage-ability-continuation.md)
- [Ability Call Development](stage-call.md)
- [Stage Widget Development](stage-formextension.md)
- Other
- [WantAgent Development](wantagent.md)
- [Ability Assistant Usage](ability-assistant-guidelines.md)
- [ContinuationManager Development](continuationmanager.md)
- [Test Framework Usage](ability-delegator.md)
# Ability Assistant Usage
The ability assistant enables you to start applications, atomic services, and test cases and debug applications. By using this tool, you can send commands in the hdc shell to perform various system operations, such as starting abilities, forcibly stopping processes, and printing ability information.
## Query-related Commands
- **help**
Displays help information for the ability assistant.
**Return value**
Returns the help information.
**Method**
```
aa help
```
## Ability-related Commands
- **start**
Starts an ability.
| Name | Description |
| --------- | -------------------------- |
| -h/--help | Help information. |
| -d | Device ID. This parameter is optional. |
| -a | Ability name. This parameter is mandatory.|
| -b | Bundle name. This parameter is mandatory. |
| -D | Debugging mode. This parameter is optional. |
**Return value**
Returns "start ability successfully." if the ability is started; returns "error: failed to start ability." otherwise.
**Method**
```
aa start [-d <device-id>] -a <ability-name> -b <bundle-name> [-D]
```
- **stop-service**
Stops a Service ability.
| Name | Description |
| --------- | ------------------------ |
| -h/--help | Help information. |
| -d | Device ID. This parameter is optional. |
| -a | Ability name. This parameter is mandatory.|
| -b | Bundle name. This parameter is mandatory. |
**Return value**
Returns "stop service ability successfully." if the Service ability is stopped; returns "error: failed to stop service ability." otherwise.
**Method**
```
aa stop-service [-d <device-id>] -a <ability-name> -b <bundle-name>
```
- **dump**
Prints ability-related information.
| Name | Level-2 Parameter | Description |
| ----------------- | -------------------- | ------------------------------------------------------------ |
| -h/--help | - | Prints help information. |
| -a/--all | - | Prints ability information in all missions. |
| -l/--mission-list | type (All logs are printed if this parameter is left unspecified.)| Prints mission stack information.<br>The following values are available for **type**:<br>- NORMAL <br>- DEFAULT_STANDARD<br>- DEFAULT_SINGLE<br>- LAUNCHER |
| -e/--extension | elementName | Prints extended component information. |
| -u/--userId | UserId | Prints stack information of a specified user ID. This parameter must be used together with other parameters. <br/>Example commands: aa **dump -a -u 100** and **aa dump -d -u 100**.|
| -d/--data | - | Prints Data ability information. |
| -i/--ability | AbilityRecord ID | Prints detailed information about a specified ability. |
| -c/--client | - | Prints detailed ability information. This parameter must be used together with other parameters. <br/>Example commands: **aa dump -a -c** and **aa dump -i 21 -c**.|
**Method**
```
aa dump -a
```
![aa-dump-a](figures/aa-dump-a.PNG)
```
aa dump -l
```
![aa-dump-l](figures/aa-dump-l.PNG)
```
aa dump -i 12
```
![aa-dump-i](figures/aa-dump-i.PNG)
- **force-stop**
Forcibly stops a process based on the bundle name.
**Return value**
Returns "force stop process successfully." if the process is forcibly stopped; returns "error: failed to force stop process." otherwise.
**Method**
```
aa force-stop <bundle-name>
```
# Ability Framework Overview
Ability is the basic abstraction of applications in OpenHarmony.
Each ability is an application component that provides an independent service and is the minimum unit for the system to schedule an application. An application can contain one or more **Ability** instances.
The ability framework model has two forms:
- FA model, which is available for application development using API version 8 and earlier versions. In the FA model, there are PageAbility, ServiceAbility, DataAbility, and FormAbility.
- Stage model, which is introduced since API version 9. In the stage model, there are two classes: UIAbility and ExtensionAbility. ExtensionAbility is further extended to ServiceExtensionAbility, FormExtensionAbility, DataShareExtensionAbility, and more.
Starting from API version 9, the stage model is recommended.
The stage model is designed to make it easier to develop complex applications in the distributed environment. The table below lists the design differences between the two models.
| Item | FA Model | Stage Model |
| -------------- | ------------------------------------------------------------ | -------------------------------------------------------- |
| Application component development mode | Web-like development | Object-oriented development |
| Engine instance | Each **Ability** instance exclusively occupies a VM instance. | Multiple **Ability** instances share a VM instance. |
| Intra-process object sharing| Not supported | Supported |
| Bundle description file | The **config.json** file is used to describe the HAP and component information. Each component must use a fixed file name.| The **module.json5** file is used to describe the HAP and component information. The entry file name can be specified.|
| Component | Four types of components are provided: PageAbility (used for UI page display), ServiceAbility (used to provide services), DataAbility (used for data sharing), and FormAbility (used to provide widgets).| Two types of components are provided: UIAbility (used for UI page display) and ExtensionAbility (scenario-based service extension). |
In addition, the following differences exist in the development process:
* Different ability types
![favsstage](figures/favsstage.png)
* Different ability lifecycles
![lifecycle](figures/lifecycle.png)
For details about the two models, see [FA Model Overview](fa-brief.md) and [Stage Model Overview](stage-brief.md).
# Test Framework Usage
## Overview
The delegator test framework provides a self-test environment for OpenHarmony applications. Using this framework, you can start an ability, schedule its lifecycle, listen for its state changes, run a shell command, and print the test result.
## Constraints
The APIs provided by the test framework can be used only in the test HAP. They take effect only after the test framework is started.
## Starting the Test Framework
The test framework can be started in either of the following ways:
- Method 1: Run the `aa test` command.
- Method 2: Use DevEco Studio.
### Running aa test
To start the test framework, specify the **TestRunner** and the package name or module name of the HAP where the **TestRunner** is located.
An example command in the FA model is as follows:
```javascript
aa test -b BundleName -p com.example.myapplicationfaets -s unittest OpenHarmonyTestRunner -s class ActsAbilityTest -w 20
```
An example command in the stage model is as follows:
```javascript
aa test -b BundleName -m com.example.myapplicationfaets -s unittest OpenHarmonyTestRunner -s class ActsAbilityTest -w 20
```
| Parameter | Mandatory| Description |
| --------------- | -------- | ------------------------------------------------------------ |
| -b | Yes | Bundle name of the HAP where the **TestRunner** is located. |
| -p | Yes | Package name of the HAP where the **TestRunner** is located. This parameter is used by the FA model. |
| -m | Yes | Module name of the HAP where the **TestRunner** is located. This parameter is used by the stage model. |
| -s unittest | Yes | Name of the **TestRunner** to be used. The TestRunner name must be the same as the file name. |
| -w | No | Timeout interval of a test case, in seconds. If this parameter is not specified or is set to a value less than or equal to **0**, the test framework exits only after **finishTest** is invoked.|
| -s \<key>\<value> | No | **-s** can be followed by any key-value pair obtained through **AbilityDelegatorArgs.parameters**. For example, in **-s classname myTest**, **-s classname** is the key and **myTest** is the value.|
| -D | No | Debug mode for starting the tested application.|
| -h | No | Help information.|
### Using DevEco Studio
For details about how to use DevEco Studio to start the test framework, see [OpenHarmony Test Framework](https://developer.harmonyos.com/en/docs/documentation/doc-guides/ohos-openharmony-test-framework-0000001263160453#section1034420367508).
## Introduction to TestRunner
**TestRunner** is the entry class of the test framework test process. When the test process is started, the system calls related APIs in **TestRunner**. You need to inherit this class and override the **onPrepare** and **onRun** APIs. When creating an application template, DevEco Studio initializes the default **TestRunner** and starts the default **TestAbility** in the **onRun** API. You can modify the test code of **TestAbility** or override **onPrepare** and **onRun** in **TestRunner** to implement your own test code. For details, see [TestRunner](../reference/apis/js-apis-application-testRunner.md).
## Introduction to AbilityDelegatorRegistry
**AbilityDelegatorRegistry** is the **AbilityDelegator** repository class provided by the test framework. You can use **AbilityDelegatorRegistry** to obtain an **AbilityDelegator** instance and the input and generated parameters **AbilityDelegatorArgs** during the test. You can use **AbilityDelegator** to invoke the function set provided by the test framework for testing and verification. For details, see [AbilityDelegatorRegistry](../reference/apis/js-apis-application-abilityDelegatorRegistry.md).
## Introduction to AbilityDelegatorArgs
**AbilityDelegatorArgs** is a test parameter class provided by the test framework. You can use **AbilityDelegatorArgs** to obtain the parameters passed and generated during the test. For details, see [AbilityDelegatorArgs](../reference/apis/js-apis-inner-application-abilityDelegatorArgs.md).
## Introduction to AbilityMonitor
**AbilityMonitor** is provided by the test framework for binding to and listening for abilities. You can use **AbilityMonitor** to bind to an **Ability** instance and add **AbilityMonitor** to the listening list. When **AbilityMonitor** is bound to an ability, the creation and lifecycle changes of the ability will trigger the related callback in **AbilityMonitor**. You can test and verify the ability in these callbacks. For details, see [AbilityMonitor](../reference/apis/js-apis-inner-application-abilityMonitor.md).
**Example**
```javascript
import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
function onAbilityCreateCallback(data) {
console.info("onAbilityCreateCallback");
}
var monitor = {
abilityName: "abilityname",
onAbilityCreate: onAbilityCreateCallback
}
var abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.addAbilityMonitor(monitor).then(() => {
console.info("addAbilityMonitor promise");
});
```
## Introduction to AbilityDelegator
**AbilityDelegator** is a main function class of the test framework. It provides the functions of starting an ability, obtaining an **Ability** instance, scheduling the ability lifecycle, listening for the ability state, and printing test results.
**Modules to Import**
```javascript
import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
```
```javascript
var abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
```
### Starting an Ability and Listening for the Ability State
Use **AbilityDelegator** and **AbilityMonitor** to start an ability, obtain an **Ability** instance, and listen for the ability state.
**Example**
```javascript
var abilityDelegator;
var ability;
var timeout = 100;
function onAbilityCreateCallback(data) {
console.info("onAbilityCreateCallback");
}
var monitor = {
abilityName: "abilityname",
onAbilityCreate: onAbilityCreateCallback
}
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.waitAbilityMonitor(monitor, timeout, (err, data) => {
ability = data;
console.info("waitAbilityMonitor callback");
});
var want = {
bundleName: "bundleName",
abilityName: "abilityName"
};
abilityDelegator.startAbility(want, (err, data) => {
console.info("startAbility callback");
});
```
### Scheduling the Ability Lifecycle
**AbilityDelegator** provides APIs to display and schedule the ability lifecycle and supports the foreground and background. It works with **AbilityMonitor** to listen for the ability lifecycle. For details, see [AbilityDelegator](../reference/apis/js-apis-inner-application-abilityDelegator.md).
### Running a Shell Command
**AbilityDelegator** provides APIs to run shell commands in the test environment.
**Example**
```javascript
var abilityDelegator;
var cmd = "cmd";
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.executeShellCommand(cmd, (err, data) => {
console.info("executeShellCommand callback");
});
```
### Printing Log Information
**AbilityDelegator** provides APIs for printing log information. You can call any API in the test code to print process logs to the unit test console.
**Example**
```javascript
var abilityDelegator;
var msg = "msg";
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.print(msg, (err) => {
console.info("print callback");
});
```
### Finishing the Test and Printing Log Information
**AbilityDelegator** provides the APIs for actively finishing the test. You can call any API in test code to finish the test and print logs to the unit test console.
**Example**
```javascript
var abilityDelegator;
var msg = "msg";
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
abilityDelegator.finishTest(msg, 0, (err) => {
console.info("finishTest callback");
});
```
# Context Usage
## Context Overview
**Context** provides the capability of obtaining contextual information of an application.
The OpenHarmony application framework has two models: Feature Ability (FA) model and stage model. Correspondingly, there are two sets of context mechanisms. **application/BaseContext** is a common context base class. It uses the **stageMode** attribute to specify whether the context is used for the stage model.
- FA model
Only the methods in **app/Context** can be used for the context in the FA model. Both the application-level context and ability-level context are instances of this type. If an ability-level method is invoked in the application-level context, an error occurs. Therefore, you must pay attention to the actual meaning of the **Context** instance.
- Stage model
The stage model has the following types of contexts: **application/Context**, **application/ApplicationContext**, **application/AbilityStageContext**, **application/ExtensionContext**, **application/AbilityContext**, and **application/FormExtensionContext**. For details about these contexts and how to use them, see [Context in the Stage Model](#context-in-the-stage-model).
![contextIntroduction](figures/contextIntroduction.png)
## Context in the FA Model
Only the methods in **app/Context** can be used for the context in the FA model.
The FA model has only one context definition. All capabilities in the context are provided through methods. The context uses these methods to extend the capabilities of the FA.
**d.ts statement**
https://gitee.com/openharmony/interface_sdk-js/blob/master/api/app/context.d.ts
**Example**
```javascript
import featureAbility from '@ohos.ability.featureAbility'
export default {
onCreate() {
// Obtain the context and call related APIs.
let context = featureAbility.getContext();
context.getBundleName((data, bundleName)=>{
console.info("ability bundleName:" + bundleName)
});
console.info('Application onCreate')
},
onDestroy() {
console.info('Application onDestroy')
},
}
```
### Common Context-related Methods in the FA Model
The following context-related methods are available in the FA model:
```javascript
setDisplayOrientation(orientation: bundle.DisplayOrientation, callback: AsyncCallback<void>): void
setDisplayOrientation(orientation: bundle.DisplayOrientation): Promise<void>;
```
The methods are used to set the display orientation of the current ability.
**Example**
```javascript
import featureAbility from '@ohos.ability.featureAbility'
import bundle from '@ohos.bundle';
export default {
onCreate() {
// Obtain the context and call related APIs.
let context = featureAbility.getContext();
context.setDisplayOrientation(bundle.DisplayOrientation.LANDSCAPE).then(() => {
console.log("Set display orientation.")
})
console.info('Application onCreate')
},
onDestroy() {
console.info('Application onDestroy')
},
}
```
## Context in the Stage Model
The following describes the contexts provided by the stage model in detail.
### application/Context
**application/Context** is the base class context. It provides basic application information, such as **resourceManager**, **applicationInfo**, **cacheDir**, and **area**. It also provides basic application methods such as **createModuleContext**.
**d.ts statement**
https://gitee.com/openharmony/interface_sdk-js/blob/master/api/application/Context.d.ts
### application/ApplicationContext
**application/ApplicationContext** is an application-level context. In addition to the capabilities provided by the base class context, the application-level context provides **registerAbilityLifecycleCallback** and **unregisterAbilityLifecycleCallback** to monitor the ability lifecycle in a process.
**How to Obtain**
Obtain the context by calling **context.getApplicationContext()** in **Ability**.
**Example**
```javascript
import UIAbility from '@ohos.app.ability.UIAbility';
var lifecycleid;
export default class EntryAbility extends UIAbility {
onCreate() {
console.log("EntryAbility onCreate")
let AbilityLifecycleCallback = {
onAbilityCreate(ability){
console.log("AbilityLifecycleCallback onAbilityCreate ability:" + JSON.stringify(ability));
},
onWindowStageCreate(ability, windowStage){
console.log("AbilityLifecycleCallback onWindowStageCreate ability:" + JSON.stringify(ability));
console.log("AbilityLifecycleCallback onWindowStageCreate windowStage:" + JSON.stringify(windowStage));
},
onWindowStageActive(ability, windowStage){
console.log("AbilityLifecycleCallback onWindowStageActive ability:" + JSON.stringify(ability));
console.log("AbilityLifecycleCallback onWindowStageActive windowStage:" + JSON.stringify(windowStage));
},
onWindowStageInactive(ability, windowStage){
console.log("AbilityLifecycleCallback onWindowStageInactive ability:" + JSON.stringify(ability));
console.log("AbilityLifecycleCallback onWindowStageInactive windowStage:" + JSON.stringify(windowStage));
},
onWindowStageDestroy(ability, windowStage){
console.log("AbilityLifecycleCallback onWindowStageDestroy ability:" + JSON.stringify(ability));
console.log("AbilityLifecycleCallback onWindowStageDestroy windowStage:" + JSON.stringify(windowStage));
},
onAbilityDestroy(ability){
console.log("AbilityLifecycleCallback onAbilityDestroy ability:" + JSON.stringify(ability));
},
onAbilityForeground(ability){
console.log("AbilityLifecycleCallback onAbilityForeground ability:" + JSON.stringify(ability));
},
onAbilityBackground(ability){
console.log("AbilityLifecycleCallback onAbilityBackground ability:" + JSON.stringify(ability));
},
onAbilityContinue(ability){
console.log("AbilityLifecycleCallback onAbilityContinue ability:" + JSON.stringify(ability));
}
}
// 1. Obtain applicationContext through the context attribute.
let applicationContext = this.context.getApplicationContext();
// 2. Use applicationContext to register and listen for the ability lifecycle in the application.
lifecycleid = applicationContext.registerAbilityLifecycleCallback(AbilityLifecycleCallback);
console.log("registerAbilityLifecycleCallback number: " + JSON.stringify(lifecycleid));
},
onDestroy() {
let applicationContext = this.context.getApplicationContext();
applicationContext.unregisterAbilityLifecycleCallback(lifecycleid, (error, data) => {
console.log("unregisterAbilityLifecycleCallback success, err: " + JSON.stringify(error));
});
}
}
```
**d.ts statement**
https://gitee.com/openharmony/interface_sdk-js/blob/master/api/application/ApplicationContext.d.ts
### application/AbilityStageContext
**application/AbilityStageContext** is the context for the HAP file. In addition to those provided by the base class **application/Context**, this context contains **HapModuleInfo** and **Configuration**.
**How to Obtain**
Obtain the context from the **context** attribute in **AbilityStage**.
**Example**
```javascript
export default class MyAbilityStage extends AbilityStage {
onCreate() {
// The context attribute is of the AbilityStageContext type.
console.log('HapModuleInfo is ' + this.context.currentHapModuleInfo);
}
}
```
**d.ts statement**
https://gitee.com/openharmony/interface_sdk-js/blob/master/api/application/AbilityStageContext.d.ts
### application/AbilityContext
In the stage model, each ability has a context attribute.
**Ability** provides methods to manage the ability lifecycle, and **AbilityContext** provides methods to operate abilities (such as **startAbility** and **connectAbility**).
**How to Obtain**
Obtain the context from the **context** attribute in **Ability**.
**Example**
```javascript
import UIAbility from '@ohos.app.ability.UIAbility';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
console.log("[Demo] EntryAbility onCreate")
globalThis.abilityWant = want;
}
onDestroy() {
console.log("[Demo] EntryAbility onDestroy")
}
onWindowStageCreate(windowStage) {
// Set the main page for this ability when the main window is created.
console.log("[Demo] EntryAbility onWindowStageCreate")
// Obtain AbilityContext and print the ability information.
let context = this.context;
console.log("[Demo] EntryAbility bundleName " + context.abilityInfo.bundleName)
windowStage.loadContent("pages/index", (err, data) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
});
}
onWindowStageDestroy() {
// Release the UI related resources when the main window is destroyed.
console.log("[Demo] EntryAbility onWindowStageDestroy")
}
onForeground() {
// The ability is switched to run in the foreground.
console.log("[Demo] EntryAbility onForeground")
}
onBackground() {
// The ability is switched to run in the background.
console.log("[Demo] EntryAbility onBackground")
}
};
```
### application/FormExtensionContext
For details, see [FormExtensionContext](../reference/apis/js-apis-inner-application-formExtensionContext.md).
### Obtaining the Context on an ArkTS Page
In the stage model, in the onWindowStageCreate lifecycle of an ability, you can call **SetUIContent** of **WindowStage** to load an ArkTS page. In some scenarios, you need to obtain the context on the page to call related APIs.
**How to Obtain**
Use the API described in the table below to obtain the context associated with an ArkTS page.
| API | Description |
| :------------------------------------ | :----------------------------------------------------------- |
| getContext(component: Object): Object | Obtains the **Context** object associated with a component on the page.<br>Since API version 9, this API is supported in ArkTS widgets.|
**Example**
```ts
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
console.log("[Demo] EntryAbility onCreate")
}
onDestroy() {
console.log("[Demo] EntryAbility onDestroy")
}
onWindowStageCreate(windowStage) {
// Load the index page and pass the current Context object.
windowStage.setUIContent(this.context, "pages/index", null)
}
onWindowStageDestroy() {}
onForeground() {}
onBackground() {}
};
```
```ts
// pages/index.ets
import context from '@ohos.app.ability.context'
type Context = context.Context
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Text('GetContext')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
// Obtain the Context object associated with the current component.
var context : Context = getContext(this) as Context
console.info("CacheDir:" + context.cacheDir)
})
}
.width('100%')
}
.height('100%')
}
}
```
## Common Incorrect Usage
**Error 1: Use globalThis to obtain the context in the stage model.**
**Reason**
In the FA model, each ability instance has a JS VM instance. Therefore, a global ability instance can be obtained from the **global** object of the JS engine. In the stage model, where all the processes of an application share a JS VM instance, there is no global ability instance, and using **globalThis** may cause an error or crash.
# ContinuationManager Development
> **NOTE**
>
> Currently, the **ContinuationManager** module is not available for application development. Its APIs are mainly used to start the device selection module.
## When to Use
Users are using two or more devices to experience an all-scenario, multi-device lifestyle. Each type of device has its unique advantages and disadvantages specific to scenarios. The ability continuation capability breaks boundaries of devices and enables multi-device collaboration, achieving precise control, universal coordination, and seamless hops of user applications.
As the entry of the ability continuation capability, **continuationManager** is used to start the device selection module for the user to select the target device. After a device is selected, information about the selected device is returned to the user. The user can then initiate cross-device continuation or collaboration based on the device information.
![continuationManager](figures/continuationManager.png)
## Available APIs
| API | Description|
| ---------------------------------------------------------------------------------------------- | ----------- |
| registerContinuation(callback: AsyncCallback\<number>): void | Registers the continuation management service and obtains a token. This API does not involve any filter parameters and uses an asynchronous callback to return the result.|
| registerContinuation(options: ContinuationExtraParams, callback: AsyncCallback\<number>): void | Registers the continuation management service and obtains a token. This API uses an asynchronous callback to return the result.|
| registerContinuation(options?: ContinuationExtraParams): Promise\<number> | Registers the continuation management service and obtains a token. This API uses a promise to return the result.|
| on(type: "deviceSelected", token: number, callback: Callback\<Array\<ContinuationResult>>): void | Subscribes to device connection events. This API uses an asynchronous callback to return the result.|
| on(type: "deviceUnselected", token: number, callback: Callback\<Array\<ContinuationResult>>): void | Subscribes to device disconnection events. This API uses an asynchronous callback to return the result.|
| off(type: "deviceSelected", token: number): void | Unsubscribes from device connection events.|
| off(type: "deviceUnselected", token: number): void | Unsubscribes from device disconnection events.|
| startContinuationDeviceManager(token: number, callback: AsyncCallback\<void>): void | Starts the device selection module to show the list of available devices. This API does not involve any filter parameters and uses an asynchronous callback to return the result.|
| startContinuationDeviceManager(token: number, options: ContinuationExtraParams, callback: AsyncCallback\<void>): void | Starts the device selection module to show the list of available devices. This API uses an asynchronous callback to return the result.|
| startContinuationDeviceManager(token: number, options?: ContinuationExtraParams): Promise\<void> | Starts the device selection module to show the list of available devices. This API uses a promise to return the result.|
| updateContinuationState(token: number, deviceId: string, status: DeviceConnectState, callback: AsyncCallback\<void>): void | Instructs the device selection module to update the device connection state. This API uses an asynchronous callback to return the result.|
| updateContinuationState(token: number, deviceId: string, status: DeviceConnectState): Promise\<void> | Instructs the device selection module to update the device connection state. This API uses a promise to return the result.|
| unregisterContinuation(token: number, callback: AsyncCallback\<void>): void | Deregisters the continuation management service. This API uses an asynchronous callback to return the result.|
| unregisterContinuation(token: number): Promise\<void> | Deregisters the continuation management service. This API uses a promise to return the result.|
## How to Develop
1. Import the **continuationManager** module.
```ts
import continuationManager from '@ohos.continuation.continuationManager';
```
2. Apply for the **DISTRIBUTED_DATASYNC** permission.
The permission application operation varies according to the ability model in use. In the FA mode, add the required permission in the `config.json` file, as follows:
```json
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}
```
This permission must also be granted by the user through a dialog box when the application is started for the first time. The sample code is as follows:
```ts
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
import bundle from '@ohos.bundle';
import featureAbility from '@ohos.ability.featureAbility';
async function requestPermission() {
let permissions: Array<string> = [
"ohos.permission.DISTRIBUTED_DATASYNC"
];
let needGrantPermission: boolean = false;
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let applicationInfo = await bundle.getApplicationInfo('ohos.samples.etsDemo', 0, 100);
for (let i = 0; i < permissions.length; i++) {
let result = await atManager.verifyAccessToken(applicationInfo.accessTokenId, permissions[i]);
// Check whether the permission is granted.
if (result == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
needGrantPermission = true;
break;
}
}
// If the permission is not granted, call requestPermissionsFromUser to apply for the permission.
if (needGrantPermission) {
await featureAbility.getContext().requestPermissionsFromUser(permissions, 1);
} else {
console.info('app permission already granted');
}
}
```
In the stage model, add the required permission in the `module.json5` file. The sample code is as follows:
```json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}
```
```ts
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
import bundle from '@ohos.bundle';
async function requestPermission() {
let permissions: Array<string> = [
"ohos.permission.DISTRIBUTED_DATASYNC"
];
let needGrantPermission: boolean = false;
let atManger: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let applicationInfo = await bundle.getApplicationInfo('ohos.samples.continuationmanager', 0, 100);
for (const permission of permissions) {
try {
let grantStatus = await atManger.verifyAccessToken(applicationInfo.accessTokenId, permission);
// Check whether the permission is granted.
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) {
needGrantPermission = true;
break;
}
} catch (err) {
console.error('app permission query grant status error' + JSON.stringify(err));
needGrantPermission = true;
break;
}
}
// If the permission is not granted, call requestPermissionsFromUser to apply for the permission.
if (needGrantPermission) {
try {
// globalThis.context is Ability.context, which must be assigned a value in the MainAbility.ts file in advance.
await atManger.requestPermissionsFromUser(globalThis.context, permissions);
} catch (err) {
console.error('app permission request permissions error' + JSON.stringify(err));
}
} else {
console.info('app permission already granted');
}
}
```
3. Register the continuation management service and obtain a token.
The sample code is as follows:
```ts
let token: number = -1; // Used to save the token returned after the registration. The token will be used when listening for device connection/disconnection events, starting the device selection module, and updating the device connection state.
try {
continuationManager.registerContinuation().then((data) => {
console.info('registerContinuation finished, ' + JSON.stringify(data));
token = data; // Obtain a token and assign a value to the token variable.
}).catch((err) => {
console.error('registerContinuation failed, cause: ' + JSON.stringify(err));
});
} catch (err) {
console.error('registerContinuation failed, cause: ' + JSON.stringify(err));
}
```
4. Listen for the device connection/disconnection state.
The sample code is as follows:
```ts
let remoteDeviceId: string = ""; // Used to save the information about the remote device selected by the user, which will be used for cross-device continuation or collaboration.
try {
// The token parameter is the token obtained during the registration.
continuationManager.on("deviceSelected", token, (continuationResults) => {
console.info('registerDeviceSelectedCallback len: ' + continuationResults.length);
if (continuationResults.length <= 0) {
console.info('no selected device');
return;
}
remoteDeviceId = continuationResults[0].id; // Assign the deviceId of the first selected remote device to the remoteDeviceId variable.
// Pass the remoteDeviceId parameter to want.
let want = {
deviceId: remoteDeviceId,
bundleName: 'ohos.samples.continuationmanager',
abilityName: 'EntryAbility'
};
globalThis.abilityContext.startAbility(want).then((data) => {
console.info('StartRemoteAbility finished, ' + JSON.stringify(data));
}).catch((err) => {
console.error('StartRemoteAbility failed, cause: ' + JSON.stringify(err));
});
});
} catch (err) {
console.error('on failed, cause: ' + JSON.stringify(err));
}
```
The preceding multi-device collaboration operation is performed across devices in the stage model. For details about this operation in the FA model, see [Page Ability Development](fa-pageability.md).
You can also instruct the device selection module to update the device connection state. The sample code is as follows:
```ts
// Set the device connection state.
let deviceConnectStatus: continuationManager.DeviceConnectState = continuationManager.DeviceConnectState.CONNECTED;
// The token parameter is the token obtained during the registration, and the remoteDeviceId parameter is the remoteDeviceId obtained.
try {
continuationManager.updateContinuationState(token, remoteDeviceId, deviceConnectStatus).then((data) => {
console.info('updateContinuationState finished, ' + JSON.stringify(data));
}).catch((err) => {
console.error('updateContinuationState failed, cause: ' + JSON.stringify(err));
});
} catch (err) {
console.error('updateContinuationState failed, cause: ' + JSON.stringify(err));
}
```
Listen for the device disconnection state so that the user can stop cross-device continuation or collaboration in time. The sample code is as follows:
```ts
try {
// The token parameter is the token obtained during the registration.
continuationManager.on("deviceUnselected", token, (continuationResults) => {
console.info('onDeviceUnselected len: ' + continuationResults.length);
if (continuationResults.length <= 0) {
console.info('no unselected device');
return;
}
// Update the device connection state.
let unselectedDeviceId: string = continuationResults[0].id; // Assign the deviceId of the first deselected remote device to the unselectedDeviceId variable.
let deviceConnectStatus: continuationManager.DeviceConnectState = continuationManager.DeviceConnectState.DISCONNECTING; // Device disconnected.
// The token parameter is the token obtained during the registration, and the unselectedDeviceId parameter is the unselectedDeviceId obtained.
continuationManager.updateContinuationState(token, unselectedDeviceId, deviceConnectStatus).then((data) => {
console.info('updateContinuationState finished, ' + JSON.stringify(data));
}).catch((err) => {
console.error('updateContinuationState failed, cause: ' + JSON.stringify(err));
});
});
} catch (err) {
console.error('updateContinuationState failed, cause: ' + JSON.stringify(err));
}
```
5. Start the device selection module to show the list of available devices on the network.
The sample code is as follows:
```ts
// Filter parameters.
let continuationExtraParams = {
deviceType: ["00E"], // Device type.
continuationMode: continuationManager.ContinuationMode.COLLABORATION_SINGLE // Single-choice mode of the device selection module.
};
try {
// The token parameter is the token obtained during the registration.
continuationManager.startContinuationDeviceManager(token, continuationExtraParams).then((data) => {
console.info('startContinuationDeviceManager finished, ' + JSON.stringify(data));
}).catch((err) => {
console.error('startContinuationDeviceManager failed, cause: ' + JSON.stringify(err));
});
} catch (err) {
console.error('startContinuationDeviceManager failed, cause: ' + JSON.stringify(err));
}
```
6. If you do not need to perform cross-device migration or collaboration operations, you can deregister the continuation management service, by passing the token obtained during the registration.
The sample code is as follows:
```ts
try {
// The token parameter is the token obtained during the registration.
continuationManager.unregisterContinuation(token).then((data) => {
console.info('unregisterContinuation finished, ' + JSON.stringify(data));
}).catch((err) => {
console.error('unregisterContinuation failed, cause: ' + JSON.stringify(err));
});
} catch (err) {
console.error('unregisterContinuation failed, cause: ' + JSON.stringify(err));
}
```
# FA Model Overview
## Overall Architecture
Ability is the entry for application development in OpenHarmony.
The core of ability development is the processing on ability lifecycle callbacks.
The Feature Ability (FA) model can be used only for application development using API version 8 and earlier versions. In this model, there are PageAbility, ServiceAbility, DataAbility, and FormAbility.
- PageAbility implements the ArkUI and provides the capability for interacting with users.
- ServiceAbility does not have a UI. It runs in the background and provides custom services for other abilities to invoke.
- DataAbility does not have a UI. It runs in the background and enables other abilities to insert, delete, and query data.
- FormAbility is used to implement widgets, a new UI display form available on OpenHarmony devices.
> Note: Starting from API version 9, the stage model is recommended for application development.
## Lifecycle
Among all abilities, PageAbility has the most complex lifecycle, because it has a UI and acts as a touchpoint for interacting with users.
**The following figure shows the lifecycle of PageAbility.**
![fa-pageAbility-lifecycle](figures/fa-pageAbility-lifecycle.png)
The other abilities do not involve foreground and background switch or the **onShow** and **onHide** callbacks.
You can override the lifecycle callbacks in **app.js** or **app.ets** to process application logic.
The **app.js** file provides only the **onCreate** and **onDestroy** callbacks, and the **app.ets** file provides the callbacks covering the entire lifecycle.
## Process and Thread Model
Each application runs in a process. In the FA model, each ability runs in an independent VM.
When an ability is started, an application process as well as a thread for this ability is created. For an application that has multiple abilities, each ability runs in an independent thread. In the FA model, each ability is bound to an independent VM instance. In this way, abilities are isolated from each other.
![fa-threading-model](figures/fa-threading-model.png)
## Application Package Structure
For details about the project directory structure of the FA model, see [OpenHarmony Project Overview](https://developer.harmonyos.com/en/docs/documentation/doc-guides/ohos-project-overview-0000001218440650#section4154183910141).
For details about how to configure the application package structure of the FA model, see [Application Package Structure Configuration File](../quick-start/application-configuration-file-overview-fa.md).
# Data Ability Development
## When to Use
A Data ability helps applications manage access to data stored by themselves and other applications. It also provides APIs for sharing data with other applications either on the same device or across devices.
Data ability providers can customize data access-related APIs such as data inserting, deleting, updating, and querying, as well as file opening, and share data with other applications through these open APIs.
## URI Introduction
A Uniform Resource Identifier (URI) is used to identify a specific data item, such as a table in the database or a file on the disk. URIs used in OpenHarmony comply with the commonly used URI standard. A URI consists of the components:
![fa-dataability-uri](figures/fa-dataability-uri.png)
- **scheme**: name of the scheme used by the Data ability. The value is fixed at **dataability**.
- **authority**: device ID. To access data on a remote device, set this component to the ID of the remote device. To access data on the local device, leave this component empty.
- **path**: location of the specific resource to access.
- **query**: query parameters.
- **fragment**: subordinate resources to access.
Example URIs:
- Cross-device communication: **dataability://***device_id***/***com.domainname.dataability.persondata***/***person***/***10*
- Local-device communication: **dataability:///***com.domainname.dataability.persondata***/***person***/***10*
> **NOTE**
>
> In the case of local-device communication, **device_id** is empty, and therefore, there are three slashes (/) after **dataability:**.
## Available APIs
**Table 1** Data ability lifecycle APIs
|API|Description|
|:------|:------|
|onInitialized(info: AbilityInfo): void|Called during ability initialization to initialize the relational database (RDB).|
|update(uri: string, valueBucket: rdb.ValuesBucket, predicates: dataAbility.DataAbilityPredicates, callback: AsyncCallback\<number>): void|Updates data in the database.|
|query(uri: string, columns: Array\<string>, predicates: dataAbility.DataAbilityPredicates, callback: AsyncCallback\<ResultSet>): void|Queries data in the database.|
|delete(uri: string, predicates: dataAbility.DataAbilityPredicates, callback: AsyncCallback\<number>): void|Deletes one or more data records from the database.|
|normalizeUri(uri: string, callback: AsyncCallback\<string>): void|Normalizes the URI. A normalized URI applies to cross-device use, persistence, backup, and restore. When the context changes, it ensures that the same data item can be referenced.|
|batchInsert(uri: string, valueBuckets: Array\<rdb.ValuesBucket>, callback: AsyncCallback\<number>): void|Inserts multiple data records into the database.|
|denormalizeUri(uri: string, callback: AsyncCallback\<string>): void|Converts a normalized URI generated by **normalizeUri** into a denormalized URI.|
|insert(uri: string, valueBucket: rdb.ValuesBucket, callback: AsyncCallback\<number>): void|Inserts a data record into the database.|
|openFile(uri: string, mode: string, callback: AsyncCallback\<number>): void|Opens a file.|
|getFileTypes(uri: string, mimeTypeFilter: string, callback: AsyncCallback\<Array\<string>>): void|Obtains the MIME type of a file.|
|getType(uri: string, callback: AsyncCallback\<string>): void|Obtains the MIME type matching the data specified by the URI.|
|executeBatch(ops: Array\<DataAbilityOperation>, callback: AsyncCallback\<Array\<DataAbilityResult>>): void|Operates data in the database in batches.|
|call(method: string, arg: string, extras: PacMap, callback: AsyncCallback\<PacMap>): void|Calls a custom API.|
## How to Develop
### Creating a Data Ability
1. To meet the basic requirements of the database storage service, implement the **Insert**, **Query**, **Update**, and **Delete** APIs in the **Data** class. The **BatchInsert** and **ExecuteBatch** APIs have already implemented the traversal logic, but not batch data processing.
The following code snippet shows how to create a Data ability:
```javascript
import featureAbility from '@ohos.ability.featureAbility'
import dataAbility from '@ohos.data.dataAbility'
import dataRdb from '@ohos.data.rdb'
const TABLE_NAME = 'book'
const STORE_CONFIG = { name: 'book.db' }
const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS book(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, introduction TEXT NOT NULL)'
let rdbStore: dataRdb.RdbStore = undefined
export default {
onInitialized(abilityInfo) {
console.info('DataAbility onInitialized, abilityInfo:' + abilityInfo.bundleName)
let context = featureAbility.getContext()
dataRdb.getRdbStore(context, STORE_CONFIG, 1, (err, store) => {
console.info('DataAbility getRdbStore callback')
store.executeSql(SQL_CREATE_TABLE, [])
rdbStore = store
});
},
insert(uri, valueBucket, callback) {
console.info('DataAbility insert start')
rdbStore.insert(TABLE_NAME, valueBucket, callback)
},
batchInsert(uri, valueBuckets, callback) {
console.info('DataAbility batch insert start')
for (let i = 0;i < valueBuckets.length; i++) {
console.info('DataAbility batch insert i=' + i)
if (i < valueBuckets.length - 1) {
rdbStore.insert(TABLE_NAME, valueBuckets[i], (err: any, num: number) => {
console.info('DataAbility batch insert ret=' + num)
})
} else {
rdbStore.insert(TABLE_NAME, valueBuckets[i], callback)
}
}
},
query(uri, columns, predicates, callback) {
console.info('DataAbility query start')
let rdbPredicates = dataAbility.createRdbPredicates(TABLE_NAME, predicates)
rdbStore.query(rdbPredicates, columns, callback)
},
update(uri, valueBucket, predicates, callback) {
console.info('DataAbilityupdate start')
let rdbPredicates = dataAbility.createRdbPredicates(TABLE_NAME, predicates)
rdbStore.update(valueBucket, rdbPredicates, callback)
},
delete(uri, predicates, callback) {
console.info('DataAbilitydelete start')
let rdbPredicates = dataAbility.createRdbPredicates(TABLE_NAME, predicates)
rdbStore.delete(rdbPredicates, callback)
}
};
```
2. Configure the submodule.
| JSON Field| Description |
| ------------ | ------------------------------------------------------------ |
| "name" | Ability name, corresponding to the **Data** class name derived from **Ability**. |
| "type" | Ability type, which is **Data** for a Data ability. |
| "uri" | URI used for communication. |
| "visible" | Whether the Data ability is visible to other applications. When this parameter is set to **true**, the Data ability can communicate with other applications.|
**config.json configuration example**
```json
"abilities":[{
"srcPath": "DataAbility",
"name": ".DataAbility",
"icon": "$media:icon",
"srcLanguage": "ets",
"description": "$string:description_dataability",
"type": "data",
"visible": true,
"uri": "dataability://ohos.samples.etsdataability.DataAbility"
}]
```
### Accessing a Data ability
#### Development Preparations
Import the basic dependency packages and obtain the URI string for communicating with the Data submodule.
The basic dependency packages include:
- @ohos.ability.featureAbility
- @ohos.data.dataAbility
- @ohos.data.rdb
#### Data Ability API Development
1. Create a Data ability helper.
For details about the APIs provided by **DataAbilityHelper**, see [DataAbilityHelper Module](../reference/apis/js-apis-inner-ability-dataAbilityHelper.md).
```js
// Different from the URI defined in the config.json file, the URI passed in the parameter has an extra slash (/), because there is a DeviceID parameter between the second and the third slash (/).
import featureAbility from '@ohos.ability.featureAbility'
import ohos_data_ability from '@ohos.data.dataAbility'
import ohos_data_rdb from '@ohos.data.rdb'
var urivar = "dataability:///com.ix.DataAbility"
var DAHelper = featureAbility.acquireDataAbilityHelper(
urivar
);
```
2. Construct RDB data.
```js
var valuesBucket = {"name": "gaolu"}
var da = new ohos_data_ability.DataAbilityPredicates()
var valArray =new Array("value1");
var cars = new Array({"batchInsert1" : "value1",});
```
3. Use **insert** to insert data to the Data submodule.
```js
// Callback mode:
DAHelper.insert(
urivar,
valuesBucket,
(error, data) => {
console.log("DAHelper insert result: " + data)
}
);
```
```js
// Promise mode:
var datainsert = await DAHelper.insert(
urivar,
valuesBucket
);
```
4. Use **delete** to delete data from the Data submodule.
```js
// Callback mode:
DAHelper.delete(
urivar,
da,
(error, data) => {
console.log("DAHelper delete result: " + data)
}
);
```
```js
// Promise mode:
var datadelete = await DAHelper.delete(
urivar,
da,
);
```
5. Use **update** to update data in the Data submodule.
```js
// Callback mode:
DAHelper.update(
urivar
valuesBucket,
da,
(error, data) => {
console.log("DAHelper update result: " + data)
}
);
```
```js
// Promise mode:
var dataupdate = await DAHelper.update(
urivar,
valuesBucket,
da,
);
```
6. Use **query** to query data in the Data submodule.
```js
// Callback mode:
DAHelper.query(
urivar,
valArray,
da,
(error, data) => {
console.log("DAHelper query result: " + data)
}
);
```
```js
// Promise mode:
var dataquery = await DAHelper.query(
urivar,
valArray,
da
);
```
7. Use **batchInsert** to insert data in batches to the Data submodule.
```js
// Callback mode:
DAHelper.batchInsert(
urivar,
cars,
(error, data) => {
console.log("DAHelper batchInsert result: " + data)
}
);
```
```js
// Promise mode:
var databatchInsert = await DAHelper.batchInsert(
urivar,
cars
);
```
8. Use **executeBatch** to process data in batches in the Data submodule.
```js
// Callback mode:
DAHelper.executeBatch(
urivar,
[
{
uri: urivar,
type: featureAbility.DataAbilityOperationType.TYPE_INSERT,
valuesBucket: {"executeBatch" : "value1",},
predicates: da,
expectedCount:0,
predicatesBackReferences: null,
interrupted:true,
}
],
(error, data) => {
console.log("DAHelper executeBatch result: " + data)
}
);
```
```js
// Promise mode:
var dataexecuteBatch = await DAHelper.executeBatch(
urivar,
[
{
uri: urivar,
type: featureAbility.DataAbilityOperationType.TYPE_INSERT,
valuesBucket:
{
"executeBatch" : "value1",
},
predicates: da,
expectedCount:0,
predicatesBackReferences: null,
interrupted:true,
}
]
);
```
# Page Ability Development
## Overview
### Concepts
The Page ability implements the ArkUI and provides the capability of interacting with developers. When you create an ability in DevEco Studio, DevEco Studio automatically creates template code.
The capabilities related to the Page ability are implemented through the **featureAbility**, and the lifecycle callbacks are implemented through the callbacks in **app.js** or **app.ets**.
### Page Ability Lifecycle
Introduction to the Page ability lifecycle:
The Page ability lifecycle defines all states of a Page ability, such as **INACTIVE**, **ACTIVE**, and **BACKGROUND**.
The following figure shows the lifecycle state transition of the Page ability.
![PageAbility-Lifecycle](figures/page-ability-lifecycle.png)
Description of ability lifecycle states:
- **UNINITIALIZED**: The Page ability is not initialized. This is a temporary state, from which a Page ability changes directly to the **INITIAL** state upon its creation.
- **INITIAL**: The Page ability is initialized but not running. The Page ability enters the **INACTIVE** state after it is started.
- **INACTIVE**: The Page ability is visible but does not gain focus.
- **ACTIVE**: The Page ability runs in the foreground and has focus.
- **BACKGROUND**: The Page ability runs in the background. After being re-activated, the Page ability enters the **ACTIVE** state. After being destroyed, the Page ability enters the **INITIAL** state.
The following figure shows the relationship between lifecycle callbacks and lifecycle states of the Page ability.
![fa-pageAbility-lifecycle](figures/fa-pageAbility-lifecycle.png)
You can override the lifecycle callbacks provided by the Page ability in the **app.js** or **app.ets** file. Currently, the **app.js** file provides only the **onCreate** and **onDestroy** callbacks, and the **app.ets** file provides the full lifecycle callbacks.
### Launch Type
The ability supports two launch types: singleton and multi-instance.
You can specify the launch type by setting **launchType** in the **config.json** file.
**Table 1** Startup modes
| Launch Type | Description |Description |
| ----------- | ------- |---------------- |
| standard | Multi-instance | A new instance is started each time an ability starts.|
| singleton | Singleton | The ability has only one instance in the system. If an instance already exists when an ability is started, that instance is reused.|
By default, **singleton** is used.
## Development Guidelines
### Available APIs
**Table 2** APIs provided by featureAbility
| API | Description |
| --------------------------------------------------- | --------------- |
| void startAbility(parameter: StartAbilityParameter) | Starts an ability. |
| Context getContext(): | Obtains the application context.|
| void terminateSelf() | Terminates the ability. |
| bool hasWindowFocus() | Checks whether the ability has focus. |
### Starting a Local Page Ability
**Modules to Import**
```js
import featureAbility from '@ohos.ability.featureAbility'
```
**Example**
```javascript
import featureAbility from '@ohos.ability.featureAbility'
featureAbility.startAbility({
want: {
action: "",
entities: [""],
type: "",
deviceId: "",
bundleName: "com.example.myapplication",
/* In the FA model, abilityName consists of package and ability name. */
abilityName: "com.example.entry.secondAbility",
uri: ""
}
});
```
### Starting a Remote Page Ability
>NOTE
>
>This feature applies only to system applications, since the **getTrustedDeviceListSync** API of the **DeviceManager** class is open only to system applications.
**Modules to Import**
```
import featureAbility from '@ohos.ability.featureAbility'
import deviceManager from '@ohos.distributedHardware.deviceManager';
```
**Example**
```ts
function onStartRemoteAbility() {
console.info('onStartRemoteAbility begin');
let params;
let wantValue = {
bundleName: 'ohos.samples.etsDemo',
abilityName: 'ohos.samples.etsDemo.RemoteAbility',
deviceId: getRemoteDeviceId(),
parameters: params
};
console.info('onStartRemoteAbility want=' + JSON.stringify(wantValue));
featureAbility.startAbility({
want: wantValue
}).then((data) => {
console.info('onStartRemoteAbility finished, ' + JSON.stringify(data));
});
console.info('onStartRemoteAbility end');
}
```
Obtain **deviceId** from **DeviceManager**. The sample code is as follows:
```ts
import deviceManager from '@ohos.distributedHardware.deviceManager';
let dmClass;
function getRemoteDeviceId() {
if (typeof dmClass === 'object' && dmClass != null) {
let list = dmClass.getTrustedDeviceListSync();
if (typeof (list) == 'undefined' || typeof (list.length) == 'undefined') {
console.log("EntryAbility onButtonClick getRemoteDeviceId err: list is null");
return;
}
console.log("EntryAbility onButtonClick getRemoteDeviceId success:" + list[0].deviceId);
return list[0].deviceId;
} else {
console.log("EntryAbility onButtonClick getRemoteDeviceId err: dmClass is null");
}
}
```
In the cross-device scenario, the application must also apply for the data synchronization permission from end users. The sample code is as follows:
```ts
import abilityAccessCtrl from "@ohos.abilityAccessCtrl";
import bundle from '@ohos.bundle';
async function RequestPermission() {
console.info('RequestPermission begin');
let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];
let bundleFlag = 0;
let tokenID = undefined;
let userID = 100;
let appInfo = await bundle.getApplicationInfo('ohos.samples.etsDemo', bundleFlag, userID);
tokenID = appInfo.accessTokenId;
let atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = [];
for (let i = 0;i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]);
console.info("verifyAccessToken result:" + JSON.stringify(result));
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
requestPermissions.push(array[i]);
}
}
console.info("requestPermissions:" + JSON.stringify(requestPermissions));
if (requestPermissions.length == 0 || requestPermissions == []) {
return;
}
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
console.info("data:" + JSON.stringify(data));
console.info("data requestCode:" + data.requestCode);
console.info("data permissions:" + data.permissions);
console.info("data authResults:" + data.authResults);
});
console.info('RequestPermission end');
}
```
### Lifecycle APIs
**Table 3** Lifecycle callbacks
| API | Description |
| ------------ | ------------------------------------------------------------ |
| onShow() | Called when the ability is switched from the background to the foreground. In this case, the ability is visible to users.|
| onHide() | Called when the ability is switched from the foreground to the background. In this case, the ability is invisible to users.|
| onDestroy() | Called when the ability is destroyed. In this callback, you can make preparations for application exit, such as recycling resources and clearing the cache.|
| onCreate() | Called when the ability is created for the first time. You can initialize the application in this callback.|
| onInactive() | Called when the ability loses focus. An ability loses focus when it is about to enter the background state.|
| onActive() | Called when the ability is switched to the foreground and gains focus. |
**Example**
You need to override the lifecycle callbacks except **onCreate()** and **onDestroy()** in **app.js** or **app.ets**. The **onCreate()** and **onDestroy()** callbacks are automatically generated in the template code provided by DevEco Studio.
```javascript
export default {
onCreate() {
console.info('Application onCreate')
},
onDestroy() {
console.info('Application onDestroy')
},
onShow(){
console.info('Application onShow')
},
onHide(){
console.info('Application onHide')
},
onInactive(){
console.info('Application onInactive')
},
onActive(){
console.info('Application onActive')
},
}
```
# Service Ability Development
## When to Use
A Service ability is used to run tasks in the background, such as playing music or downloading files. It does not provide a UI for user interaction. Service abilities can be started by other applications or abilities and can keep running in the background even after the user switches to another application.
## Lifecycle APIs
**Table 1** Service ability lifecycle APIs
|API|Description|
|:------|:------|
|onStart?(): void|Called to initialize a Service ability when the Service ability is being created. This callback is invoked only once in the entire lifecycle of a Service ability.|
|onCommand?(want: Want, startId: number): void|Called every time a Service ability is created on the client. You can collect calling statistics and perform initialization operations in this callback.|
|onConnect?(want: Want): rpc.RemoteObject|Called when another ability is connected to the Service ability.|
|onDisconnect?(want: Want): void|Called when another ability is disconnected from the Service ability.|
|onStop?(): void|Called when the Service ability is being destroyed. You should override this callback for your Service ability to clear its resources, such as threads and registered listeners.|
The differences between **onCommand()** and **onConnect()** are as follows:
- The **onCommand()** callback is triggered each time the client starts the Service ability by calling **startAbility** or **startAbilityForResult**.
- The **onConnect()** callback is triggered each time the client establishes a new connection with the Service ability by calling **connectAbility**.
## How to Develop
### Creating and Registering a Service Ability
1. Override the Service ability-related lifecycle callbacks to implement your own logic for processing interaction requests.
```ts
export default {
onStart() {
console.log('ServiceAbility onStart');
},
onCommand(want, startId) {
console.log('ServiceAbility onCommand');
},
onConnect(want) {
console.log('ServiceAbility OnConnect');
// Below lists the implementation of ServiceAbilityStub.
return new ServiceAbilityStub('test');
},
onDisconnect(want) {
console.log('ServiceAbility OnDisConnect');
},
onStop() {
console.log('ServiceAbility onStop');
}
}
```
2. Register a Service ability.
Declare the Service ability in the **config.json** file by setting its **type** attribute to **service**.
```json
{
"module": {
"abilities": [
{
"name": ".ServiceAbility",
"type": "service",
"visible": true
...
}
]
...
}
...
}
```
### Starting a Service Ability
The **Ability** class provides the **startAbility()** API for you to start another Service ability by passing a **Want** object.
To set information about the target Service ability, you can first construct a **Want** object with the **bundleName** and **abilityName** parameters specified.
- **bundleName** specifies the bundle name of the target application.
- **abilityName** specifies the target ability name.
The following code snippet shows how to start a Service ability running on the local device:
```ts
import featureAbility from '@ohos.ability.featureAbility'
featureAbility.startAbility(
{
want:
{
bundleName: "com.jstest.service",
abilityName: "com.jstest.service.ServiceAbility"
}
}
).then((err) => {
console.log("startService success");
}).catch (err => {
console.log("startService FAILED");
});
```
In the preceding code, the **startAbility()** API is used to start the Service ability.
- If the Service ability is not running, the system initializes the Service ability, and calls **onStart()** and **onCommand()** on the Service ability in sequence.
- If the Service ability is running, the system directly calls **onCommand()** on the Service ability.
The following code snippet shows how to start a Service ability running on the remote device. For details, see [Connecting to a Remote Service Ability](#connecting-to-a-remote-service-ability).
```ts
import featureAbility from '@ohos.ability.featureAbility'
featureAbility.startAbility(
{
want:
{
deviceId: remoteDeviceId, // Remote device ID.
bundleName: "com.jstest.service",
abilityName: "com.jstest.service.ServiceAbility"
}
}
).then((err) => {
console.log("startService success");
}).catch (err => {
console.log("startService FAILED");
});
```
### Stopping a Service Ability
In normal cases, a Service ability can be stopped by itself or by the system.
- The Service ability can call **particleAbility.terminateSelf()** to stop itself.
- If the application process where the Service ability is located exits, the Service ability is reclaimed along with the process.
- If the Service ability is only accessed through **connectAbility()** (the **onCommand()** callback has never been triggered), the system stops the Service ability when the last connection to the Service ability is disconnected.
### Connecting to a Local Service Ability
If a Service ability wants to interact with a Page ability or a Service ability in another application, you must first create a connection. A Service ability allows other abilities to connect to it through **connectAbility()**.
You can use either of the following methods to connect to a Service ability:
1. Using the IDL to automatically generate code
Use OpenHarmony Interface Definition Language (IDL) to automatically generate the corresponding client, server, and **IRemoteObject** code. For details, see [Development Using TS](../IDL/idl-guidelines.md#development-using-ts).
2. Writing code in the corresponding file
When using **connectAbility()**, pass the **Want** and **ConnectOptions** objects of the target Service ability, where **ConnectOptions** encapsulates the following three callbacks that need to be implemented.
- **onConnect()**: callback used for processing when the Service ability is connected.
- **onDisconnect()**: callback used for processing when the Service ability is disconnected.
- **onFailed()**: callback used for processing when the connection to the Service ability fails.
The following code snippet shows how to implement the callbacks:
```ts
import prompt from '@system.prompt'
var option = {
onConnect: function onConnectCallback(element, proxy) {
console.log(`onConnectLocalService onConnectDone`);
if (proxy === null) {
prompt.showToast({
message: "Connect service failed"
});
return;
}
// After obtaining the proxy of the Service ability, the calling ability can communicate with the Service ability.
let data = rpc.MessageParcel.create();
let reply = rpc.MessageParcel.create();
let option = new rpc.MessageOption();
data.writeString("InuptString");
proxy.sendRequest(0, data, reply, option);
prompt.showToast({
message: "Connect service success"
});
},
onDisconnect: function onDisconnectCallback(element) {
console.log(`onConnectLocalService onDisconnectDone element:${element}`);
prompt.showToast({
message: "Disconnect service success"
});
},
onFailed: function onFailedCallback(code) {
console.log(`onConnectLocalService onFailed errCode:${code}`);
prompt.showToast({
message: "Connect local service onFailed"
});
}
};
```
The following code snippet shows how to connect to a local Service ability:
```ts
import featureAbility from '@ohos.ability.featureAbility'
let want = {
bundleName: "com.jstest.service",
abilityName: "com.jstest.service.ServiceAbility"
};
let connectId = featureAbility.connectAbility(want, option);
```
When a Service ability is connected, the **onConnect()** callback is invoked and returns an **IRemoteObject** defining the proxy used for communicating with the Service ability. OpenHarmony provides the default implementation of **IRemoteObject**. You can inherit **rpc.RemoteObject** to create a custom implementation class for interaction with the Service ability. For details, see the [RPC API Reference](..\reference\apis\js-apis-rpc.md).
The following code snippet shows how the Service ability returns itself to the calling ability:
```ts
import rpc from "@ohos.rpc"
class ServiceAbilityStub extends rpc.RemoteObject {
constructor(des: any) {
if (typeof des === 'string') {
super(des);
} else {
console.log("Error, the input param is not string");
return;
}
}
onRemoteRequest(code: number, data: any, reply: any, option: any) {
console.log("onRemoteRequest called");
// Execute the service logic.
if (code === 1) {
// Sort the input strings.
let string = data.readString();
console.log(`Input string = ${string}`);
let result = Array.from(string).sort().join('');
console.log(`Output result = ${result}`);
reply.writeString(result);
} else {
console.log(`Unknown request code`);
}
return true;
}
}
export default {
onStart() {
console.log('ServiceAbility onStart');
},
onCommand(want, startId) {
console.log('ServiceAbility onCommand');
},
onConnect(want) {
console.log('ServiceAbility OnConnect');
return new ServiceAbilityStub('ServiceAbilityRemoteObject');
},
onDisconnect(want) {
console.log('ServiceAbility OnDisConnect');
},
onStop() {
console.log('ServiceAbility onStop');
}
}
```
### Connecting to a Remote Service Ability
This feature applies only to system applications. The method of creating a **ConnectOptions** object for connecting to a remote Service ability is similar to that for connecting to a local Service ability. The differences are as follows:
- The application must apply for the data synchronization permission from the user.
- **Want** of the target Service ability must contain the remote device ID.
> **NOTE**
>
> The **getTrustedDeviceList** API of **DeviceManager** is open only to system applications. Currently, only system applications can connect to a remote Service ability.
>
> For details about the API definition, see [Device Management](..\reference\apis\js-apis-device-manager.md).
The data synchronization permission is required in the cross-device scenario. Configure the permission in the **config.json** file.
```json
{
...
"module": {
...
"reqPermissions": [{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}]
}
}
```
The **DISTRIBUTED_DATASYNC** permission is user granted. Therefore, your application, when being started, must display a dialog box to request the permission. The sample code is as follows:
```ts
import abilityAccessCtrl from "@ohos.abilityAccessCtrl"
import bundle from '@ohos.bundle'
async function RequestPermission() {
console.info('RequestPermission begin');
let array: Array<string> = ["ohos.permission.DISTRIBUTED_DATASYNC"];
let bundleFlag = 0;
let tokenID = undefined;
let userID = 100;
let appInfo = await bundle.getApplicationInfo('ohos.samples.etsDemo', bundleFlag, userID);
tokenID = appInfo.accessTokenId;
let atManager = abilityAccessCtrl.createAtManager();
let requestPermissions: Array<string> = [];
for (let i = 0;i < array.length; i++) {
let result = await atManager.verifyAccessToken(tokenID, array[i]);
console.info("verifyAccessToken result:" + JSON.stringify(result));
if (result != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
requestPermissions.push(array[i]);
}
}
console.info("requestPermissions:" + JSON.stringify(requestPermissions));
if (requestPermissions.length == 0 || requestPermissions == []) {
return;
}
let context = featureAbility.getContext();
context.requestPermissionsFromUser(requestPermissions, 1, (data)=>{
console.info("data:" + JSON.stringify(data));
});
console.info('RequestPermission end');
}
```
To obtain the device ID, import the **@ohos.distributedHardware.deviceManager** module, which provides **getTrustedDeviceList** to obtain the remote device ID. For details about how to use the API, see [Device Management](..\reference\apis\js-apis-device-manager.md).
To connect to a remote Service ability, you only need to define **deviceId** in **Want**. The sample code is as follows:
```ts
import featureAbility from '@ohos.ability.featureAbility'
let want = {
deviceId: remoteDeviceId,
bundleName: "com.jstest.service",
abilityName: "com.jstest.service.ServiceAbility"
};
let connectId = featureAbility.connectAbility(want, option);
```
The other implementations are the same as those for the connection to a local Service ability. For details, see the sample code provided under [Connecting to a Local Service Ability](#connecting-to-a-local-service-ability).
# Ability Continuation Development
## When to Use
Ability continuation is to continue the current mission of an application, including the UI component state variables and distributed objects, on another device. The UI component state variables are used to synchronize UI data, and the distributed objects are used to synchronize memory data.
## Available APIs
The following table lists the APIs used for ability continuation. For details about the APIs, see [UIAbility](../reference/apis/js-apis-app-ability-uiAbility.md).
**Table 1** Ability continuation APIs
|API| Description|
|:------ | :------|
| onContinue(wantParam : {[key: string]: any}): OnContinueResult | Called by the initiator to store the data required for continuation. The return value indicates whether the continuation request is accepted. The value **AGREE** means that the continuation request is accepted, **REJECT** means that the continuation request is rejected, and **MISMATCH** means a version mismatch.|
| onCreate(want: Want, param: AbilityConstant.LaunchParam): void; | Called by the target to restore the data and UI page in the multi-instance ability scenario.|
| onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; | Called by the target to restore the data and UI page in the singleton ability scenario.|
**Figure 1** Ability continuation development
![continuation_dev](figures/continuation-info.png)
In effect, ability continuation is a cross-device ability startup that carries data. When a continuation action is initiated, the system on device A calls back **onContinue()** of the application. You must implement storage of the current data in this API. Then, the system initiates a cross-device ability startup on device B and transmits the data to device B. The system on device B calls back **onCreate()** or **onNewWant()**. You must implement restoration of the transmitted data in this API.
## How to Develop
The code snippets provided below are all from [Sample](https://gitee.com/openharmony/ability_dmsfwk/tree/master/services/dtbschedmgr/test/samples/continuationManualTestSuite).
### Application Continuation
1. Modify the configuration file.
- Configure the application to support ability continuation.
Set the **continuable** field in the **module.json5** file to **true**. The default value is **false**. If this parameter is set to **false**, the application cannot be continued on another device.
```javascript
{
"module": {
"abilities": [
{
"continuable": true
}
]
}
}
```
- Configure the application startup type.
If **launchType** is set to **multiton** in the **module.json5** file, the application is of the multi-instance launch type. During ability continuation, regardless of whether the application is already open, the target starts the application and restores the UI page. If **launchType** is set to **singleton**, the application is of the singleton launch type. If the application is already open, the target clears the existing page stack and restores the UI page. For more information, see "Launch Type" in [Ability Development](./stage-ability.md).
Configure a multi-instance application as follows:
```javascript
{
"module": {
"abilities": [
{
"launchType": "multiton"
}
]
}
}
```
Configure a singleton application as follows or retain the default settings of **launchType**:
```javascript
{
"module": {
"abilities": [
{
"launchType": "singleton"
}
]
}
}
```
- Apply for the distributed permissions.
Declare the **DISTRIBUTED_DATASYNC** permission in the **module.json5** file for the application.
```javascript
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
},
```
This permission must be granted by the user in a dialog box when the application is started for the first time. To enable the application to display a dialog box to ask for the permission, add the following code to **onWindowStageCreate** of the **Ability** class:
```javascript
requestPermissions = async () => {
let permissions: Array<string> = [
"ohos.permission.DISTRIBUTED_DATASYNC"
];
let needGrantPermission = false
let accessManger = accessControl.createAtManager()
Logger.info("app permission get bundle info")
let bundleInfo = await bundle.getApplicationInfo(BUNDLE_NAME, 0, 100)
Logger.info(`app permission query permission ${bundleInfo.accessTokenId.toString()}`)
for (const permission of permissions) {
Logger.info(`app permission query grant status ${permission}`)
try {
let grantStatus = await accessManger.verifyAccessToken(bundleInfo.accessTokenId, permission)
if (grantStatus === PERMISSION_REJECT) {
needGrantPermission = true
break;
}
} catch (err) {
Logger.error(`app permission query grant status error ${permission} ${JSON.stringify(err)}`)
needGrantPermission = true
break;
}
}
if (needGrantPermission) {
Logger.info("app permission needGrantPermission")
try {
await accessManger.requestPermissionsFromUser(this.context, permissions)
} catch (err) {
Logger.error(`app permission ${JSON.stringify(err)}`)
}
} else {
Logger.info("app permission already granted")
}
}
```
2. Implement the **onContinue()** API.
The **onContinue()** API is called by the initiator to save the UI component state variables and memory data and prepare for continuation. After the application completes the continuation preparation, the system must return either **OnContinueResult.AGREE(0)** to accept the continuation request or an error code to reject the request. If this API is not implemented, the system rejects the continuation request by default.
Modules to import:
```javascript
import UIAbility from '@ohos.app.ability.UIAbility';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
```
To implement ability continuation, you must implement this API and have the value **AGREE** returned.
You can obtain the target device ID (identified by the key **targetDevice**) and the version number (identified by the key **version**) of the application installed on the target device from the **wantParam** parameter of this API. The version number can be used for compatibility check. If the current application version is incompatible with that on the target device, **OnContinueResult.MISMATCH** can be returned to reject the continuation request.
Example:
```javascript
onContinue(wantParam : {[key: string]: any}) {
Logger.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`)
let workInput = AppStorage.Get<string>('ContinueWork');
// Set the user input data into wantParam.
wantParam["work"] = workInput // set user input data into want params
Logger.info(`onContinue input = ${wantParam["input"]}`);
return AbilityConstant.OnContinueResult.AGREE
}
```
3. Implement the continuation logic in the **onCreate()** or **onNewWant()** API.
The **onCreate()** API is called by the target. When the ability is started on the target device, this API is called to instruct the application to synchronize the memory data and UI component state, and triggers page restoration after the synchronization is complete. If the continuation logic is not implemented, the ability will be started in common startup mode and the page cannot be restored.
The target device determines whether the startup is **LaunchReason.CONTINUATION** based on **launchReason** in **onCreate()**.
After data restore is complete, call **restoreWindowStage** to trigger page restoration.
You can also use **want.parameters.version** in the **want** parameter to obtain the application version number of the initiator.
Example:
```javascript
import UIAbility from '@ohos.app.ability.UIAbility';
import distributedObject from '@ohos.data.distributedDataObject';
export default class EntryAbility extends UIAbility {
storage : LocalStorag;
onCreate(want, launchParam) {
Logger.info(`EntryAbility onCreate ${AbilityConstant.LaunchReason.CONTINUATION}`)
if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
// Obtain the user data from the want parameter.
let workInput = want.parameters.work
Logger.info(`work input ${workInput}`)
AppStorage.SetOrCreate<string>('ContinueWork', workInput)
this.storage = new LocalStorage();
this.context.restoreWindowStage(this.storage);
}
}
}
```
For a singleton ability, use **onNewWant()** to achieve the same implementation.
### Data Continuation
Use distributed objects.
Distributed objects allow cross-device data synchronization like local variables. For two devices that form a Super Device, when data in the distributed data object of an application is added, deleted, or modified on a device, the data for the same application is also updated on the other device. Both devices can listen for the data changes and online and offline states of the other. For details, see [Sharing Distributed Data Objects](../database/data-sync-of-distributed-data-object.md).
In the ability continuation scenario, the distributed data object is used to synchronize the memory data from the local device to the target device.
- In **onContinue()**, the initiator saves the data to be migrated to the distributed object, calls the **save()** API to save the data and synchronize the data to the target device, sets the session ID, and sends the session ID to the target device through **wantParam**.
```javascript
import UIAbility from '@ohos.app.ability.UIAbility';
import distributedObject from '@ohos.data.distributedDataObject';
var g_object = distributedObject.createDistributedObject({data:undefined});
export default class EntryAbility extends UIAbility {
sessionId : string;
onContinue(wantParam : {[key: string]: any}) {
Logger.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`)
if (g_object.__sessionId === undefined) {
this.sessionId = distributedObject.genSessionId()
Logger.info(`onContinue generate new sessionId`)
}
else {
this.sessionId = g_object.__sessionId;
}
wantParam["session"] = this.sessionId
g_object.data = AppStorage.Get<string>('ContinueStudy');
Logger.info(`onContinue sessionId = ${this.sessionId}, name = ${g_object.data}`)
g_object.setSessionId(this.sessionId);
g_object.save(wantParam.targetDevice, (result, data)=>{
Logger.info("save callback");
Logger.info("save sessionId " + data.sessionId);
Logger.info("save version " + data.version);
Logger.info("save deviceId " + data.deviceId);
});
```
- The target device obtains the session ID from **onCreate()**, creates a distributed object, and associates the distributed object with the session ID. In this way, the distributed object can be synchronized. Before calling **restoreWindowStage**, ensure that all distributed objects required for continuation have been associated.
```javascript
import UIAbility from '@ohos.app.ability.UIAbility';
import distributedObject from '@ohos.data.distributedDataObject';
var g_object = distributedObject.createDistributedObject({data:undefined});
export default class EntryAbility extends UIAbility {
storage : LocalStorag;
onCreate(want, launchParam) {
Logger.info(`EntryAbility onCreate ${AbilityConstant.LaunchReason.CONTINUATION}`)
if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
// Obtain the session ID of the distributed data object from the want parameter.
this.sessionId = want.parameters.session
Logger.info(`onCreate for continuation sessionId: ${this.sessionId}`)
// Before fetching data from the remote device, reset g_object.data to undefined.
g_object.data = undefined;
// Set the session ID, so the target will fetch data from the remote device.
g_object.setSessionId(this.sessionId);
AppStorage.SetOrCreate<string>('ContinueStudy', g_object.data)
this.storage = new LocalStorage();
this.context.restoreWindowStage(this.storage);
}
}
}
```
### More Information
1. Timeout
- If the application to be continued is not installed on the target device, the system checks whether the application can be installed on it and waits for a response for 4 seconds. If no response is received within 4 seconds, the caller receives a timeout error code, which means that the application cannot be installed on the target device. If the application can be installed, the system prompts the consumer to install the application on the target device. The consumer can initiate the continuation again after the installation.
- If the application to be continued has been installed on the target device, the system waits for a response to the continuation request for 20 seconds. If no response is received within 20 seconds, the caller receives a timeout error code, which means that the continuation fails.
2. By default, the system supports page stack information migration, which means that the page stack of the initiator will be automatically migrated to the target device. No adaptation is required.
### Restrictions
1. The continuation must be performed between the same ability, which means the same bundle name, module name, and ability name. For details, see [Application Package Structure Configuration File](../quick-start/module-configuration-file.md).
2. Currently, the application can only implement the continuation capability. The continuation action must be initiated by the system.
### Best Practice
For better user experience, you are advised to use the **wantParam** parameter to transmit data smaller than 100 KB and use distributed objects to transmit data larger than 100 KB.
<!--no_check-->
\ No newline at end of file
# Ability Development
## When to Use
Ability development in the [stage model](stage-brief.md) is significantly different from that in the FA model. The stage model requires you to declare the application package structure in the **module.json5** and **app.json5** files during application development. For details about the configuration file, see [Application Package Structure Configuration File](../quick-start/application-package-structure-stage.md). To develop an ability based on the stage model, implement the following logic:
- Create an ability that supports screen viewing and human-machine interaction. You must implement the following scenarios: ability lifecycle callbacks, obtaining ability configuration, requesting permissions, and notifying environment changes.
- Start an ability. You need to implement ability startup on the same device, on a remote device, or with a specified UI page.
- Call abilities. For details, see [Call Development](stage-call.md).
- Connect to and disconnect from a Service Extension ability. For details, see [Service Extension Ability Development](stage-serviceextension.md).
- Continue the ability on another device. For details, see [Ability Continuation Development](stage-ability-continuation.md).
### Launch Type
An ability can be launched in the **standard**, **singleton**, or **specified** mode, as configured by **launchType** in the **module.json5** file. Depending on the launch type, the action performed when the ability is started differs, as described below.
| Launch Type | Description |Action |
| ----------- | ------- |---------------- |
| multiton | Multi-instance mode| A new instance is started each time an ability starts.|
| singleton | Singleton mode | Default type. The ability has only one instance in the system. If an instance already exists when an ability is started, that instance is reused.|
| specified | Instance-specific| The internal service of an ability determines whether to create multiple instances during running.|
By default, the singleton mode is used. The following is an example of the **module.json5** file:
```json
{
"module": {
"abilities": [
{
"launchType": "singleton",
}
]
}
}
```
## Creating an Ability
### Available APIs
The table below describes the APIs provided by the **AbilityStage** class, which has the **context** attribute. For details about the APIs, see [AbilityStage](../reference/apis/js-apis-app-ability-abilityStage.md).
**Table 1** AbilityStage APIs
|API|Description|
|:------|:------|
|onCreate(): void|Called when an ability stage is created.|
|onAcceptWant(want: Want): string|Called when a specified ability is started.|
|onConfigurationUpdated(config: Configuration): void|Called when the global configuration is updated.|
The table below describes the APIs provided by the **Ability** class. For details about the APIs, see [UIAbility](../reference/apis/js-apis-app-ability-uiAbility.md).
**Table 2** Ability APIs
|API|Description|
|:------|:------|
|onCreate(want: Want, param: AbilityConstant.LaunchParam): void|Called when an ability is created.|
|onDestroy(): void|Called when the ability is destroyed.|
|onWindowStageCreate(windowStage: window.WindowStage): void|Called when a **WindowStage** is created for the ability. You can use the **window.WindowStage** APIs to implement operations such as page loading.|
|onWindowStageDestroy(): void|Called when the **WindowStage** is destroyed for the ability.|
|onForeground(): void|Called when the ability is switched to the foreground.|
|onBackground(): void|Called when the ability is switched to the background.|
|onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void|Called when the ability launch type is set to **singleton**.|
|onConfigurationUpdated(config: Configuration): void|Called when the configuration of the environment where the ability is running is updated.|
### Implementing AbilityStage and Ability Lifecycle Callbacks
To create Page abilities for an application in the stage model, you must implement the **AbilityStage** class and ability lifecycle callbacks, and use the **Window** class to set the pages. The sample code is as follows:
1. Import the **AbilityStage** module.
```ts
import AbilityStage from "@ohos.app.ability.AbilityStage";
```
2. Implement the **AbilityStage** class. The default relative path generated by the APIs is **entry\src\main\ets\Application\AbilityStage.ts**.
```ts
export default class MyAbilityStage extends AbilityStage {
onCreate() {
console.log("MyAbilityStage onCreate")
}
}
```
3. Import the **Ability** module.
```js
import UIAbility from '@ohos.app.ability.UIAbility';
```
4. Implement the lifecycle callbacks of the **UIAbility** class. The default relative path generated by the APIs is **entry\src\main\ets\entryability\EntryAbility.ts**.
In the **onWindowStageCreate(windowStage)** API, use **loadContent** to set the application page to be loaded. For details about how to use the **Window** APIs, see [Window Development](../windowmanager/application-window-stage.md).
```ts
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
console.log("EntryAbility onCreate")
}
onDestroy() {
console.log("EntryAbility onDestroy")
}
onWindowStageCreate(windowStage) {
console.log("EntryAbility onWindowStageCreate")
windowStage.loadContent("pages/index").then(() => {
console.log("EntryAbility load content succeed")
}).catch((error) => {
console.error("EntryAbility load content failed with error: " + JSON.stringify(error))
})
}
onWindowStageDestroy() {
console.log("EntryAbility onWindowStageDestroy")
}
onForeground() {
console.log("EntryAbility onForeground")
}
onBackground() {
console.log("EntryAbility onBackground")
}
}
```
### Obtaining AbilityStage and Ability Configurations
Both the **AbilityStage** and **Ability** classes have the **context** attribute. An application can obtain the context of an **Ability** instance through **this.context** to obtain the configuration details.
The following example shows how an application obtains the bundle code directory, HAP file name, ability name, and system language through the **context** attribute in the **AbilityStage** class. The sample code is as follows:
```ts
import AbilityStage from "@ohos.app.ability.AbilityStage";
export default class MyAbilityStage extends AbilityStage {
onCreate() {
console.log("MyAbilityStage onCreate")
let context = this.context
console.log("MyAbilityStage bundleCodeDir" + context.bundleCodeDir)
let currentHapModuleInfo = context.currentHapModuleInfo
console.log("MyAbilityStage hap module name" + currentHapModuleInfo.name)
console.log("MyAbilityStage hap module mainAbilityName" + currentHapModuleInfo.mainAbilityName)
let config = this.context.config
console.log("MyAbilityStage config language" + config.language)
}
}
```
The following example shows how an application obtains the bundle code directory, HAP file name, ability name, and system language through the **context** attribute in the **Ability** class. The sample code is as follows:
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
console.log("EntryAbility onCreate")
let context = this.context
console.log("EntryAbility bundleCodeDir" + context.bundleCodeDir)
let abilityInfo = this.context.abilityInfo;
console.log("EntryAbility ability bundleName" + abilityInfo.bundleName)
console.log("EntryAbility ability name" + abilityInfo.name)
let config = this.context.config
console.log("EntryAbility config language" + config.language)
}
}
```
### Notifying of Environment Changes
Environment changes include changes of global configurations and ability configurations. Currently, the global configurations include the system language and color mode. The change of global configurations is generally triggered by configuration items in **Settings** or icons in **Control Panel**. The ability configuration is specific to a single **Ability** instance, including the display ID, screen resolution, and screen orientation. The configuration is related to the display where the ability is located, and the change is generally triggered by the window. Before configuring a project, define the project in the [Configuration](../reference/apis/js-apis-application-configuration.md) class.
For an application in the stage model, when the configuration changes, its abilities are not restarted, but the **onConfigurationUpdated(config: Configuration)** callback is triggered. If the application needs to perform processing based on the change, you can override **onConfigurationUpdated**. Note that the **Configuration** object in the callback contains all the configurations of the current ability, not only the changed configurations.
The following example shows the implementation of the **onConfigurationUpdated** callback in the **AbilityStage** class. The callback is triggered when the system language and color mode are changed.
```ts
import AbilityStage from '@ohos.app.ability.AbilityStage';
import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant';
export default class MyAbilityStage extends AbilityStage {
onConfigurationUpdated(config) {
if (config.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
console.log('colorMode changed to dark')
}
}
}
```
The following example shows the implementation of the **onConfigurationUpdated** callback in the **Ability** class. The callback is triggered when the system language, color mode, or display parameters (such as the direction and density) change.
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant';
export default class EntryAbility extends UIAbility {
direction : number;
onCreate(want, launchParam) {
this.direction = this.context.config.direction
}
onConfigurationUpdated(config) {
if (this.direction !== config.direction) {
console.log(`direction changed to ${config.direction}`)
}
}
}
```
## Starting an Ability
### Available APIs
The **Ability** class has the **context** attribute, which belongs to the **AbilityContext** class. The **AbilityContext** class has the **abilityInfo**, **currentHapModuleInfo**, and other attributes as well as the APIs used for starting abilities. For details, see [AbilityContext](../reference/apis/js-apis-inner-application-uiAbilityContext.md).
**Table 3** AbilityContext APIs
|API|Description|
|:------|:------|
|startAbility(want: Want, callback: AsyncCallback\<void>): void|Starts an ability.|
|startAbility(want: Want, options?: StartOptions): Promise\<void>|Starts an ability.|
|startAbilityWithAccount(want: Want, accountId: number, callback: AsyncCallback\<void>): void|Starts an ability with the account ID.|
|startAbilityWithAccount(want: Want, accountId: number, options?: StartOptions): Promise\<void>|Starts an ability with the account ID.|
|startAbilityForResult(want: Want, callback: AsyncCallback\<AbilityResult>): void|Starts an ability with the returned result.|
|startAbilityForResult(want: Want, options?: StartOptions): Promise\<AbilityResult>|Starts an ability with the returned result.|
|startAbilityForResultWithAccount(want: Want, accountId: number, callback: AsyncCallback\<AbilityResult>): void|Starts an ability with the execution result and account ID.|
|startAbilityForResultWithAccount(want: Want, accountId: number, options?: StartOptions): Promise\<AbilityResult>|Starts an ability with the execution result and account ID.|
### Starting an Ability on the Same Device
An application can obtain the context of an **Ability** instance through **this.context** and then use the **startAbility** API in the **AbilityContext** class to start the ability. The ability can be started by specifying **Want**, **StartOptions**, and **accountId**, and the operation result can be returned using a callback or **Promise** instance. The sample code is as follows:
```ts
let context = this.context
let want = {
"deviceId": "",
"bundleName": "com.example.MyApplication",
"abilityName": "EntryAbility"
};
context.startAbility(want).then(() => {
console.log("Succeed to start ability")
}).catch((error) => {
console.error("Failed to start ability with error: "+ JSON.stringify(error))
})
```
### Starting an Ability on a Remote Device
>This feature applies only to system applications, since the **getTrustedDeviceListSync** API of the **DeviceManager** class is open only to system applications.
In the cross-device scenario, you must specify the ID of the remote device. The sample code is as follows:
```ts
let context = this.context
let want = {
"deviceId": getRemoteDeviceId(),
"bundleName": "com.example.MyApplication",
"abilityName": "EntryAbility"
};
context.startAbility(want).then(() => {
console.log("Succeed to start remote ability")
}).catch((error) => {
console.error("Failed to start remote ability with error: " + JSON.stringify(error))
})
```
Obtain the ID of a specified device from **DeviceManager**. The sample code is as follows:
```ts
import deviceManager from '@ohos.distributedHardware.deviceManager';
function getRemoteDeviceId() {
if (typeof dmClass === 'object' && dmClass !== null) {
let list = dmClass.getTrustedDeviceListSync();
if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
console.log("EntryAbility onButtonClick getRemoteDeviceId err: list is null");
return;
}
console.log("EntryAbility onButtonClick getRemoteDeviceId success:" + list[0].deviceId);
return list[0].deviceId;
} else {
console.log("EntryAbility onButtonClick getRemoteDeviceId err: dmClass is null");
}
}
```
Request the permission **ohos.permission.DISTRIBUTED_DATASYNC** from consumers. This permission is used for data synchronization. For details about the sample code for requesting permissions, see [abilityAccessCtrl.requestPermissionsFromUse](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9).
### Starting an Ability with the Specified Page
If the launch type of an ability is set to **singleton** and the ability has been started, the **onNewWant** callback is triggered when the ability is started again. You can pass start options through the **want**. For example, to start an ability with the specified page, use the **uri** or **parameters** parameter in the **want** to pass the page information. Currently, the ability in the stage model cannot directly use the **router** capability. You must pass the start options to the custom component and invoke the **router** method to display the specified page during the custom component lifecycle management. The sample code is as follows:
When using **startAbility** to start an ability again, use the **uri** parameter in the **want** to pass the page information.
```ts
async function reStartAbility() {
try {
await this.context.startAbility({
bundleName: "com.sample.MyApplication",
abilityName: "EntryAbility",
uri: "pages/second"
})
console.log('start ability succeed')
} catch (error) {
console.error(`start ability failed with ${error.code}`)
}
}
```
Obtain the **want** parameter that contains the page information from the **onNewWant** callback of the ability.
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
export default class EntryAbility extends UIAbility {
onNewWant(want, launchParams) {
globalThis.newWant = want
}
}
```
Obtain the **want** parameter that contains the page information from the custom component and process the route based on the URI.
```ts
import router from '@ohos.router'
@Entry
@Component
struct Index {
newWant = undefined
onPageShow() {
console.info('Index onPageShow')
let newWant = globalThis.newWant
if (newWant.hasOwnProperty("uri")) {
router.push({ url: newWant.uri });
globalThis.newWant = undefined
}
}
}
```
# Stage Model Overview
## Design Ideas
The stage model is designed to provide a better application development mode in the distributed environment.
The following figure shows the design ideas of the stage model.
![stagedesign](figures/stagedesign.png)
The stage model is designed based on the following considerations:
- Efficient management of application processes
As the device memory becomes larger, the number of processes concurrently running in the system increases. If the number of concurrent processes reaches several hundreds, the overall power consumption and performance of the system will be adversely affected without effective management measures. To restrict the behavior of background processes, the stage model uses four measures: transient task, continuous task, agent task, and Work Scheduler task. With these measures, foreground processes will obtain guaranteed resources, thereby delivering a better user experience.
- Native support for cross-device migration and multi-device collaboration
OpenHarmony is a native distributed OS. Its application framework must be designed for easier component migration and collaboration across devices. The stage model achieves this design objective by providing features such as separation between ability and UI as well as integration of UI display and service capabilities.
- Different window forms for various device types
The stage model redefines the ability lifecycle. In terms of architecture, the component manager and window manager are decoupled. This facilitates adaptation between window forms and device types.
## Basic Concepts
The following figure shows the basic concepts in the stage model.
![stageconcept](figures/stageconcept.png)
- **HAP**: basic unit for building, distributing, and loading OpenHarmony applications. Each HAP corresponds to a module in the development state. In an application, **moduleName** uniquely identifies a module.
- **Bundle**: an OpenHarmony application identified by **appid**. A bundle can contain multiple HAP files. Each application has a **bundleName**. However, **bundleName** must be used together with **appid** and other information to uniquely identify an application.
- **AbilityStage**: runtime object of an HAP. It is created when the HAP is loaded to the process for the first time and is visible to developers in the runtime.
- **Application**: runtime object of a bundle. It is invisible to developers in the runtime.
- **Context**: base class that provides APIs in the runtime to obtain information such as the bundle name, module name, and path. The **Context** classes of the Ability and ExtensionAbility components inherit from this class.
- **Ability**: provides lifecycle callbacks, holds the ability context, and supports cross-device component migration and multi-device collaboration.
- **ExtensionAbility**: general name of scenario-based Extension abilities. The system defines multiple scenario-based **ExtensionAbility** classes, each of which has its own **ExtensionContext**.
- **WindowStage**: local window manager.
- **Window**: application window, which holds an ArkUI engine instance.
- **ArkUI Page**: UI developed based on ArkUI.
## Lifecycle
The ability and ability stage are key objects in the application lifecycle.
For details about the lifecycle differences between the stage model and FA model, see [Ability Framework Overview](ability-brief.md). This section focuses on the ability lifecycle transition and the scheduling relationships between the ability, ability stage, and window stage.
![stageabilitylifecyclecallback](figures/stageabilitylifecyclecallback.png)
To implement device adaptation and multi-window scalability, OpenHarmony decouples the component manager from the window manager.
The ability lifecycle defined in the stage model includes only the creation, destruction, foreground, and background states. The gain focus and lose focus states that are closely related to UI are defined in the window stage. This implements weak coupling between the abilities and windows. On the service side, the window manager notifies the component manager of the foreground and background state changes, so the component manager only senses the foreground and background state changes but not the focus changes.
There are two lifecycle states related to the window stage in **Ability**: **onWindowStageCreate** and **onWindowStageDestroy**. They are valid only for devices with the display capability. **onWindowStageCreate** is invoked when a window stage is created, where you can call **loadContent** to set pages to be loaded for the ability. **onWindowStageDestroy** is invoked when the window stage is destroyed, where you can release resources.
## Ability Instances and Missions
Abilities can be started in any of the following modes:
* **Singleton**: For each type of ability, only one instance exists in the application process. **Ability1** in the figure below is started in singleton mode.
* **Standard**: Each time **startAbility** is called, an instance of the specified ability type is created in the application process. **Ability2** in the figure below is started in standard mode.
* **Specified**: Before creating an **Ability** instance, you can create a key for the instance. Each time **startAbility** is called, the system asks the application which ability instance (corresponding to a key) will be used. **Ability3** in the figure below is started in specified mode.
Each **Ability** instance corresponds to a mission in **Recents**.
The mission corresponding to an ability instance has a snapshot of the ability instance. After the ability instance is destroyed, the ability class information and snapshot are retained in the mission until the user deletes the information or the storage space reaches the upper limit.
![AbilityComponentInstanceMission](figures/AbilityComponentInstanceMission.png)
## ExtensionAbility Mechanism
Different from the ability used for UI display, ExtensionAbility provides a restricted running environment.
ExtensionAbility has the following features:
- Its process runs independently from the main process but shares the same storage sandbox with the main process. There is no inter-process communication (IPC) between the ExtensionAbility process and the main process.
- It has an independent context that provides scenario-specific APIs.
- It is created by the system, rather than by applications.
- The lifecycles of the ExtensionAbility component and process are managed by the system.
The following figure uses the widget an example. **FormExtensionAbility** is the base class. You can inherit from this class to provide widget information. The lifecycle of the **FormExtensionAbility** instance and that of the ExtensionAbility process where the instance is located are managed by a system service named **FormManagerService**.
![ExtensionAbility](figures/ExtensionAbility.png)
## Process Model
OpenHarmony forces strong control policies on application processes. No APIs are provided to configure multiple processes. All application processes are created and managed by the system.
The processes of an application can be classified into three types:
- Main process: runs the **UIAbility** component, UI, and service logic.
- Extension process: runs classes derived from **ExtensionAbility** in the application. The lifecycle of this process is managed by a scenario-specific system service.
- Render process: created for the WebView and used to load the WebView rendering library.
The following figure shows the process model of an application.
![stageprocessmodel](figures/stageprocessmodel.png)
## Application Package Structure
For details about the project directory structure of the stage model, see [OpenHarmony Project Overview](https://developer.harmonyos.com/en/docs/documentation/doc-guides/ohos-project-overview-0000001218440650#section56487581904).
For details about how to configure the application package structure of the stage model, see [Application Package Structure Configuration File](../quick-start/application-configuration-file-overview-stage.md).
# Ability Call Development
## When to Use
Ability call is an extension of the ability capability. It enables an ability to be invoked by and communicate with external systems. The ability invoked can be either started in the foreground or created and run in the background. You can use the ability call to implement data sharing between two abilities (caller ability and callee ability) through inter-process communication (IPC).
The core API used for the ability call is **startAbilityByCall**, which differs from **startAbility** in the following ways:
- **startAbilityByCall** supports ability startup in the foreground and background, whereas **startAbility** supports ability startup in the foreground only.
- The caller ability can use the **Caller** object returned by **startAbilityByCall** to communicate with the callee ability, but **startAbility** does not provide the communication capability.
Ability call is usually used in the following scenarios:
- Communicating with the callee ability
- Starting the callee ability in the background
**Table 1** Terms used in the ability call
|Term|Description|
|:------|:------|
|Caller ability|Ability that triggers the ability call.|
|Callee ability|Ability invoked by the ability call.|
|Caller |Object returned by **startAbilityByCall** and used by the caller ability to communicate with the callee ability.|
|Callee |Object held by the callee ability to communicate with the caller ability.|
|IPC |Inter-process communication.|
The ability call process is as follows:
- The caller ability uses **startAbilityByCall** to obtain a **Caller** object and uses **call()** of the **Caller** object to send data to the callee ability.
- The callee ability, which holds a **Callee** object, uses **on()** of the **Callee** object to register a callback. This callback is invoked when the callee ability receives data from the caller ability.
![stage-call](figures/stage-call.png)
> **NOTE**
>
> The launch type of the callee ability must be **singleton**.
>
> Currently, only system applications can use the ability call.
## Available APIs
The table below describes the ability call APIs. For details, see [UIAbility](../reference/apis/js-apis-app-ability-uiAbility.md#caller).
**Table 2** Ability call APIs
|API|Description|
|:------|:------|
|startAbilityByCall(want: Want): Promise\<Caller>|Starts an ability in the foreground (through the **want** configuration) or background (default) and obtains the **Caller** object for communication with the ability. For details, see [AbilityContext](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartabilitybycall) or **ServiceExtensionContext**.|
|on(method: string, callback: CalleeCallBack): void|Callback invoked when the callee ability registers a method.|
|off(method: string): void|Callback invoked when the callee ability deregisters a method.|
|call(method: string, data: rpc.Sequenceable): Promise\<void>|Sends agreed sequenceable data to the callee ability.|
|callWithResult(method: string, data: rpc.Sequenceable): Promise\<rpc.MessageParcel>|Sends agreed sequenceable data to the callee ability and obtains the agreed sequenceable data returned by the callee ability.|
|release(): void|Releases the **Caller** object.|
|on(type: "release", callback: OnReleaseCallback): void|Callback invoked when the **Caller** object is released.|
## How to Develop
The procedure for developing the ability call is as follows:
1. Create a callee ability.
2. Access the callee ability.
### Creating a Callee Ability
For the callee ability, implement the callback to receive data and the methods to marshal and unmarshal data. When data needs to be received, use **on()** to register a listener. When data does not need to be received, use **off()** to deregister the listener.
1. **Configure the ability launch type.**
Set **launchType** of the callee ability to **singleton** in the **module.json5** file.
|JSON Field|Description|
|:------|:------|
|"launchType"|Ability launch type. Set this parameter to **singleton**.|
An example of the ability configuration is as follows:
```json
"abilities":[{
"name": ".CalleeAbility",
"srcEntry": "./ets/CalleeAbility/CalleeAbility.ts",
"launchType": "singleton",
"description": "$string:CalleeAbility_desc",
"icon": "$media:icon",
"label": "$string:CalleeAbility_label",
"exported": true
}]
```
2. **Import the UIAbility module.**
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
```
3. **Define the agreed sequenceable data.**
The data formats sent and received by the caller and callee abilities must be consistent. In the following example, the data formats are number and string. The code snippet is as follows:
```ts
export default class MySequenceable {
num: number = 0
str: string = ""
constructor(num, string) {
this.num = num
this.str = string
}
marshalling(messageParcel) {
messageParcel.writeInt(this.num)
messageParcel.writeString(this.str)
return true
}
unmarshalling(messageParcel) {
this.num = messageParcel.readInt()
this.str = messageParcel.readString()
return true
}
}
```
4. **Implement Callee.on and Callee.off.**
The time to register a listener for the callee ability depends on your application. The data sent and received before the listener is registered and that after the listener is deregistered are not processed. In the following example, the **MSG_SEND_METHOD** listener is registered in **onCreate** of the ability and deregistered in **onDestroy**. After receiving sequenceable data, the application processes the data and returns the data result. You need to implement processing based on service requirements. The code snippet is as follows:
```ts
const TAG: string = '[CalleeAbility]'
const MSG_SEND_METHOD: string = 'CallSendMsg'
function sendMsgCallback(data) {
console.log('CalleeSortFunc called')
// Obtain the sequenceable data sent by the caller ability.
let receivedData = new MySequenceable(0, '')
data.readSequenceable(receivedData)
console.log(`receiveData[${receivedData.num}, ${receivedData.str}]`)
// Process the data.
// Return the sequenceable data result to the caller ability.
return new MySequenceable(receivedData.num + 1, `send ${receivedData.str} succeed`)
}
export default class CalleeAbility extends Ability {
onCreate(want, launchParam) {
try {
this.callee.on(MSG_SEND_METHOD, sendMsgCallback)
} catch (error) {
console.log(`${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`)
}
}
onDestroy() {
try {
this.callee.off(MSG_SEND_METHOD)
} catch (error) {
console.error(TAG, `${MSG_SEND_METHOD} unregister failed with error ${JSON.stringify(error)}`)
}
}
}
```
### Accessing the Callee Ability
1. **Import the Ability module.**
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
```
2. **Obtain the Caller object.**
The **context** attribute of the ability implements **startAbilityByCall** to obtain the **Caller** object for communication. The following example uses **this.context** to obtain the **context** attribute of the ability, uses **startAbilityByCall** to start the callee ability, obtain the **Caller** object, and register the **onRelease** listener of the caller ability. You need to implement processing based on service requirements. The code snippet is as follows:
```ts
// Register the onRelease listener of the caller ability.
private regOnRelease(caller) {
try {
caller.on("release", (msg) => {
console.log(`caller onRelease is called ${msg}`)
})
console.log('caller register OnRelease succeed')
} catch (error) {
console.log(`caller register OnRelease failed with ${error}`)
}
}
async onButtonGetCaller() {
try {
this.caller = await context.startAbilityByCall({
bundleName: 'com.samples.CallApplication',
abilityName: 'CalleeAbility'
})
if (this.caller === undefined) {
console.log('get caller failed')
return
}
console.log('get caller success')
this.regOnRelease(this.caller)
} catch (error) {
console.log(`get caller failed with ${error}`)
}
}
```
In the cross-device scenario, you need to specify the ID of the peer device. The code snippet is as follows:
```ts
async onButtonGetRemoteCaller() {
var caller = undefined
var context = this.context
context.startAbilityByCall({
deviceId: getRemoteDeviceId(),
bundleName: 'com.samples.CallApplication',
abilityName: 'CalleeAbility'
}).then((data) => {
if (data != null) {
caller = data
console.log('get remote caller success')
// Register the onRelease listener of the caller ability.
caller.on("release", (msg) => {
console.log(`remote caller onRelease is called ${msg}`)
})
console.log('remote caller register OnRelease succeed')
}
}).catch((error) => {
console.error(`get remote caller failed with ${error}`)
})
}
```
Obtain the ID of the peer device from **DeviceManager**. Note that the **getTrustedDeviceListSync** API is open only to system applications. The code snippet is as follows:
```ts
import deviceManager from '@ohos.distributedHardware.deviceManager';
var dmClass;
function getRemoteDeviceId() {
if (typeof dmClass === 'object' && dmClass != null) {
var list = dmClass.getTrustedDeviceListSync()
if (typeof (list) == 'undefined' || typeof (list.length) == 'undefined') {
console.log("EntryAbility onButtonClick getRemoteDeviceId err: list is null")
return
}
console.log("EntryAbility onButtonClick getRemoteDeviceId success:" + list[0].deviceId)
return list[0].deviceId
} else {
console.log("EntryAbility onButtonClick getRemoteDeviceId err: dmClass is null")
}
}
```
In the cross-device scenario, your application must also apply for the data synchronization permission from end users. The code snippet is as follows:
```ts
import abilityAccessCtrl from '@ohos.abilityAccessCtrl.d.ts';
requestPermission() {
let context = this.context
let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
let atManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
console.log("Succeed to request permission from user with data: "+ JSON.stringify(data))
}).catch((error) => {
console.log("Failed to request permission from user with error: "+ JSON.stringify(error))
})
}
```
3. **Send agreed sequenceable data.**
The sequenceable data can be sent to the callee ability with or without a return value. The method and sequenceable data must be consistent with those of the callee ability. The following example describes how to send data to the callee ability. The code snippet is as follows:
```ts
const MSG_SEND_METHOD: string = 'CallSendMsg'
async onButtonCall() {
try {
let msg = new MySequenceable(1, 'origin_Msg')
await this.caller.call(MSG_SEND_METHOD, msg)
} catch (error) {
console.log(`caller call failed with ${error}`)
}
}
```
In the following, **CallWithResult** is used to send data **originMsg** to the callee ability and assign the data processed by the **CallSendMsg** method to **backMsg**. The code snippet is as follows:
```ts
const MSG_SEND_METHOD: string = 'CallSendMsg'
originMsg: string = ''
backMsg: string = ''
async onButtonCallWithResult(originMsg, backMsg) {
try {
let msg = new MySequenceable(1, originMsg)
const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg)
console.log('caller callWithResult succeed')
let result = new MySequenceable(0, '')
data.readSequenceable(result)
backMsg(result.str)
console.log(`caller result is [${result.num}, ${result.str}]`)
} catch (error) {
console.log(`caller callWithResult failed with ${error}`)
}
}
```
4. **Release the Caller object.**
When the **Caller** object is no longer required, use **release()** to release it. The code snippet is as follows:
```ts
releaseCall() {
try {
this.caller.release()
this.caller = undefined
console.log('caller release succeed')
} catch (error) {
console.log(`caller release failed with ${error}`)
}
}
```
\ No newline at end of file
# Service Extension Ability Development
## When to Use
`ExtensionAbility` is the base class of the new Extension component in the stage model. It is used to process missions without UIs. The lifecycle of an Extension ability is simple and does not involve foreground or background states. `ServiceExtensionAbility` is extended from `ExtensionAbility`.
You can customize a class that inherits from `ServiceExtensionAbility` and override the lifecycle callbacks in the base class to perform service logic operations during the initialization, connection, and disconnection processes.
## Available APIs
**Table 1** ServiceExtensionAbility lifecycle APIs
|API|Description|
|:------|:------|
|onCreate(want: Want): void|Called for the initialization when `startAbility` or `connectAbility` is invoked for a given ability for the first time.|
|onRequest(want: Want, startId: number): void|Called each time `startAbility` is invoked for a given ability. The initial value of `startId` is `1`, and the value is incremented by one each time `startAbility` is invoked for that ability.|
|onConnect(want: Want): rpc.RemoteObject|Called when `connectAbility` is invoked for a given ability. This callback is not invoked for repeated calling of `connectAbility` for a specific ability. However, it will be invoked unless `connectAbility` is called after the ability has been disconnected using `disconnectAbility`. The returned result is a `RemoteObject`.|
|onDisconnect(want: Want): void|Called when `disconnectAbility` is called for a given ability. If the Extension ability is started by `connectAbility` and is not bound to other applications, the `onDestroy` callback will also be triggered to destroy the Extension ability.|
|onDestroy(): void|Called when `terminateSelf` is invoked to terminate the ability.|
## Constraints
OpenHarmony does not support creation of a Service Extension ability for third-party applications.
## How to Develop
1. Declare the Service Extension ability in the `module.json5` file by setting its `type` attribute to `service`. The following is a configuration example of the `module.json5` file:
```json
"extensionAbilities":[{
"name": "ServiceExtAbility",
"icon": "$media:icon",
"description": "service",
"type": "service",
"exported": true,
"srcEntry": "./ets/ServiceExtAbility/ServiceExtAbility.ts"
}]
```
2. Customize a class that inherits from `ServiceExtensionAbility` in the .ts file in the directory where the Service Extension ability is defined (`entry\src\main\ets\ServiceExtAbility\ServiceExtAbility.ts` by default) and override the lifecycle callbacks of the base class. The code sample is as follows:
```js
import ServiceExtensionAbility from '@ohos.app.ability.ServiceExtensionAbility';
import rpc from '@ohos.rpc';
class StubTest extends rpc.RemoteObject {
constructor(des) {
super(des);
}
onRemoteRequest(code, data, reply, option) {
}
}
class ServiceExtAbility extends ServiceExtensionAbility {
onCreate(want) {
console.log('onCreate, want:' + want.abilityName);
}
onRequest(want, startId) {
console.log('onRequest, want:' + want.abilityName);
}
onConnect(want) {
console.log('onConnect , want:' + want.abilityName);
return new StubTest("test");
}
onDisconnect(want) {
console.log('onDisconnect, want:' + want.abilityName);
}
onDestroy() {
console.log('onDestroy');
}
}
```
# WantAgent Development
## When to Use
The **WantAgent** class encapsulates want information that specifies a particular action, which can be starting an ability or publishing a common event. You can either call **wantAgent.trigger** to trigger a **WantAgent** directly or add a **WantAgent** to a notification so that it will be triggered when users tap the notification.
## Available APIs
| API | Description|
| ---------------------------------------------------------------------------------------------- | ----------- |
| getWantAgentInfo(info: WantAgentInfo, callback: AsyncCallback\<WantAgent\>) | Creates a **WantAgent** object. This API uses an asynchronous callback to return the result.|
| getWantAgent(info: WantAgentInfo): Promise\<WantAgent\> | Creates a **WantAgent** object. This API uses a promise to return the result.|
| trigger(agent: WantAgent, triggerInfo: TriggerInfo, callback?: Callback\<CompleteData\>) | Triggers a **WantAgent** object.|
## How to Develop
1. Import the **WantAgent** module.
```ts
import wantAgent from '@ohos.app.ability.wantAgent';
```
2. Create a **WantAgentInfo** object that will be used for starting an ability. For details about the data types and parameters of **WantAgentInfo**, see [WantAgent](../reference/apis/js-apis-wantAgent.md#wantagentinfo).
```ts
private wantAgentObj = null // Save the WantAgent object created. It will be used to complete the trigger operations.
// wantAgentInfo
var wantAgentInfo = {
wants: [
{
deviceId: "",
bundleName: "com.example.test",
abilityName: "com.example.test.EntryAbility",
action: "",
entities: [],
uri: "",
parameters: {}
}
],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
wantAgentFlags:[wantAgent.WantAgentFlags.CONSTANT_FLAG]
}
```
3. Create a **WantAgentInfo** object for publishing a common event.
```ts
private wantAgentObj = null // Save the WantAgent object created. It will be used to complete the trigger operations.
// wantAgentInfo
var wantAgentInfo = {
wants: [
{
action: "event_name", // Set the action name.
parameters: {}
}
],
operationType: wantAgent.OperationType.SEND_COMMON_EVENT,
requestCode: 0,
wantAgentFlags:[wantAgent.WantAgentFlags.CONSTANT_FLAG]
}
```
4. Create a **WantAgent** object and save the returned **wantAgentObj** for subsequent trigger operations.
```ts
// Create a WantAgent object.
wantAgent.getWantAgent(wantAgentInfo, (err, wantAgentObj) => {
if (err.code) {
console.error("[WantAgent]getWantAgent err=" + JSON.stringify(err))
} else {
console.log("[WantAgent]getWantAgent success")
this.wantAgentObj = wantAgentObj
}
})
```
5. Trigger the **WantAgent** object.
```ts
// Trigger the WantAgent object.
var triggerInfo = {
code:0
}
wantAgent.trigger(wantAgentObj, triggerInfo, (completeData) => {
console.log("[WantAgent]getWantAgent success, completeData: ", + JSON.stringify(completeData))
})
```
......@@ -61,7 +61,7 @@
- [Component Startup Rules (Stage Model)](component-startup-rules.md)
- Inter-Device Application Component Interaction (Continuation)
- [Continuation Overview](inter-device-interaction-hop-overview.md)
- [Cross-Device Migration (for System Applications Only)](hop-cross-device-migration.md)
- [Cross-Device Migration](hop-cross-device-migration.md)
- [Multi-device Collaboration (for System Applications Only)](hop-multi-device-collaboration.md)
- [Subscribing to System Environment Variable Changes](subscribe-system-environment-variable-changes.md)
- Process Model
......
......@@ -118,7 +118,12 @@ After developing the custom logic for an accessibility extension service, you mu
```
## Enabling or Disabling a Custom Accessibility Extension Service
To enable or disable an accessibility extension service, run the following command:
You can enable or disable a custom accessibility extension service through the command line interface or the device settings.
**Method 1**: through the command line interface
Run the **hdc shell** command, then the following system command:
- To enable the service: **accessibility enable -a AccessibilityExtAbility -b com.example.demo -c rg**
- To disable the service: **accessibility disable -a AccessibilityExtAbility -b com.example.demo**
......@@ -126,3 +131,9 @@ In the preceding commands, **AccessibilityExtAbility** indicates the name of the
If the service is enabled or disabled successfully, the message "enable ability successfully" or "disable ability successfully" is displayed.
**Method 2**: through the device settings
- From the device settings screen, access the list of installed extended services under accessibility.
If an extended service is not installed, it is grayed out, and "No service" is displayed.
- Select the target extended service, and toggle on or off the switch to enable or disable it.
- If you opt to enable a service, a security reminder is displayed. Wait until the countdown ends and then select the check box indicating that you are aware of and willing to assume the listed risks.
......@@ -54,7 +54,7 @@ Widget-related configuration includes **FormExtensionAbility** configuration and
| formVisibleNotify | Whether the widget is allowed to use the widget visibility notification.| String| Yes (initial value: left empty)|
| metadata | Metadata of the widget. This field contains the array of the **customizeData** field.| Object| Yes (initial value: left empty)|
| dataProxyEnabled | Whether the widget supports the [update-through-proxy](./arkts-ui-widget-update-by-proxy.md) feature.<br>- **true**: The widget supports the update-through-proxy feature.<br>- **false**: The widget does not support the update-through-proxy feature.<br>If this tag is set to **true**, the settings for the scheduled update time will still take effect, but the settings for the update interval and next update time will not.| Boolean| Yes (initial value: **false**)|
| isDynamic | Whether the widget is a dynamic widget. This tag only applies to ArkTS widgets.<br>- **true**: The widget is a dynamic widget.<br>- **false**: The widget is a static widget. In this case, the widget is displayed as a static image after being added.| Boolean| Yes (initial value: **true**)|
| isDynamic | Whether the widget is a dynamic widget. This tag applies only to ArkTS widgets.<br>- **true**: The widget is a dynamic widget.<br>- **false**: The widget is a static widget. In this case, the widget is displayed as a static image after being added.| Boolean| Yes (initial value: **true**)|
**Table 2** Internal structure of the window object
......
......@@ -26,7 +26,7 @@ In view of this, OpenHarmony formulates a set of component startup rules, as fol
- If the **exported** field of the target component is **false**, verify the **ohos.permission.START_INVISIBLE_ABILITY** permission.
- For details, see [Component exported Configuration](../quick-start/module-configuration-file.md#abilities).
- **Before starting a component of a background application, verify the BACKGROUND permission.**
- **Before starting a UIAbility component of a background application, verify the BACKGROUND permission.**
- An application is considered as a foreground application only when the application process gains focus or its UIAbility component is running in the foreground.
- Verify the **ohos.permission.START_ABILITIES_FROM_BACKGROUND** permission.
......@@ -45,7 +45,9 @@ In view of this, OpenHarmony formulates a set of component startup rules, as fol
The rules for starting components on the same device vary in the following scenarios:
- Starting or connecting to the UIAbility, ServiceExtensionAbility, and DataShareExtensionAbility components
- Starting the UIAbility component
- Starting the ServiceExtensionAbility and DataShareExtensionAbility components
- Using **startAbilityByCall()** to start the UIAbility component
......@@ -56,9 +58,10 @@ In view of this, OpenHarmony formulates a set of component startup rules, as fol
The rules for starting components on a different device vary in the following scenarios:
- Starting or connecting to the UIAbility, ServiceExtensionAbility, and DataShareExtensionAbility components
- Starting the UIAbility component
- Starting the ServiceExtensionAbility and DataShareExtensionAbility components
- Using **startAbilityByCall()** to start the UIAbility component
![component-startup-rules](figures/component-startup-inter-stage.png)
......@@ -8,6 +8,10 @@ This topic describes how the two application components of the stage model start
A UIAbility starts a PageAbility in the same way as it starts another UIAbility.
> **NOTE**
>
> In the FA model, **abilityName** consists of **bundleName** and **AbilityName**. For details, see the code snippet below.
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
......@@ -25,7 +29,7 @@ export default class EntryAbility extends UIAbility {
});
let want = {
bundleName: "com.ohos.fa",
abilityName: "EntryAbility",
abilityName: "com.ohos.fa.EntryAbility",
};
this.context.startAbility(want).then(() => {
console.info('Start Ability successfully.');
......
......@@ -14,7 +14,7 @@ import featureAbility from '@ohos.ability.featureAbility';
let parameter = {
"want": {
bundleName: "com.ohos.stage",
abilityName: "com.ohos.stage.EntryAbility"
abilityName: "EntryAbility"
}
};
featureAbility.startAbility(parameter).then((code) => {
......
......@@ -12,7 +12,7 @@
## Types of Want
- **Explicit Want**: If **abilityName** and **bundleName** are specified in the **want** parameter when starting an an application component, explicit Want is used.
- **Explicit Want**: If **abilityName** and **bundleName** are specified in the **want** parameter when starting an application component, explicit Want is used.
Explicit Want is usually used to start a known target application component in the same application. The target application component is started by specifying **bundleName** of the application where the target application component is located and **abilityName** in the **Want** object. When there is an explicit object to process the request, explicit Want is a simple and effective way to start the target application component.
......@@ -24,7 +24,7 @@
}
```
- **Implicit Want**: If **abilityName** is not specified in the **want** parameter when starting the an application component, implicit Want is used.
- **Implicit Want**: If **abilityName** is not specified in the **want** parameter when starting an application component, implicit Want is used.
Implicit Want can be used when the object used to process the request is unclear and the current application wants to use a capability (defined by the [skills tag](../quick-start/module-configuration-file.md#skills)) provided by another application. The system matches all applications that declare to support the capability. For example, for a link open request, the system matches all applications that support the request and provides the available ones for users to select.
......@@ -47,6 +47,7 @@
> - An application component that meets the conditions is matched. That application component is started.
> - Multiple application components that meet the conditions are matched. A dialog box is displayed for users to select one of them.
>
> - If the **want** parameter passed does not contain **abilityName** or **bundleName**, the ServiceExtensionAbility components of all applications cannot be started through implicit Want.
> - In the scenario for starting the ServiceExtensionAbility component:
> - If the **want** parameter passed in contains **abilityName**, the ServiceExtensionAbility component cannot be started through implicit Want.
>
> - If the **want** parameter passed contains **bundleName**, the **startServiceExtensionAbility()** method can be used to implicitly start ServiceExtensionAbility. By default, ServiceExtensionAbility with the highest priority is returned. If all the matching ServiceExtensionAbility components have the same priority, the first ServiceExtensionAbility is returned.
> - If the **want** parameter passed in contains **bundleName**, the **startServiceExtensionAbility()** method can be used to implicitly start the ServiceExtensionAbility component. By default, the ServiceExtensionAbility component with the highest priority is returned. If all the matching ServiceExtensionAbility components have the same priority, the first ServiceExtensionAbility component is returned.
......@@ -15,6 +15,7 @@ If a task does not need to occupy a background thread for a long time (3 minutes
1. Implement the logic of image processing.
2. Segment the data, and initiate associated task scheduling through task groups.
Create a [task group](../reference/apis/js-apis-taskpool.md#taskgroup10), call [addTask()](../reference/apis/js-apis-taskpool.md#addtask10) to add tasks, call [execute()](../reference/apis/js-apis-taskpool.md#taskpoolexecute10) to execute the tasks in the task group, and set [a high priority](../reference/apis/js-apis-taskpool.md#priority) for the task group. After all the tasks in the task group are complete, the histogram processing result is returned simultaneously.
3. Summarize and process the result arrays.
......@@ -78,7 +79,7 @@ The following uses the training of a region-specific house price prediction mode
![newWorker](figures/newWorker.png)
2. In the main thread, call [ThreadWorker()](../reference/apis/js-apis-worker.md#threadworker9) to create a **Worker** object. The calling thread is the host thread.
2. In the main thread, call [constructor()](../reference/apis/js-apis-worker.md#constructor9) of **ThreadWorker** to create a **Worker** object. The calling thread is the host thread.
```js
import worker from '@ohos.worker';
......@@ -167,24 +168,22 @@ The following uses the training of a region-specific house price prediction mode
6. After the task is completed in the worker thread, destroy the worker thread. The worker thread can be destroyed by itself or the host thread. Then, call [onexit()](../reference/apis/js-apis-worker.md#onexit9) in the host thread to define the processing logic after the worker thread is destroyed.
```js
// After the worker thread is destroyed, execute the onexit() callback.
workerInstance.onexit = function() {
console.info("main thread terminate");
}
```
In the host thread, call [terminate()](../reference/apis/js-apis-worker.md#terminate9) to destroy the worker thread and stop the worker thread from receiving messages.
Method 1: In the host thread, call [terminate()](../reference/apis/js-apis-worker.md#terminate9) to destroy the worker thread and stop the worker thread from receiving messages.
```js
// Destroy the worker thread.
// Destroy the worker thread.
workerInstance.terminate();
```
In the worker thread, call [close()](../reference/apis/js-apis-worker.md#close9) to destroy the worker thread and stop the worker thread from receiving messages.
Method 2: In the worker thread, call [close()](../reference/apis/js-apis-worker.md#close9) to destroy the worker thread and stop the worker thread from receiving messages.
```js
// Destroy the worker thread.
workerPort.close();
......
......@@ -44,14 +44,14 @@ The APIs provided by **Vector** are deprecated since API version 9. You are advi
| Accessing elements| Use **vec\[index]** to obtain the value at a given position (specified by **index**).|
| Accessing elements| Use **get(index: number)** to obtain the element at a given position (specified by **index**).|
| Accessing elements| Use **getLastElement()** to obtain the last element in this container.|
| Accessing elements| Use **getlndexOf(element: T)** to obtain the index of the first occurrence of the specified element.|
| Accessing elements| Use **getLastlndexOf(element: T)** to obtain the index of the last occurrence of the specified element.|
| Accessing elements| Use **getIndexOf(element: T)** to obtain the index of the first occurrence of the specified element.|
| Accessing elements| Use **getLastIndexOf(element: T)** to obtain the index of the last occurrence of the specified element.|
| Accessing elements| Use **forEach(callbackFn: (value: T, index?: number, Vector?: Vector&lt;T&gt;) =&gt; void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;T&gt;** for data access.|
| Modifying elements| Use **vec\[index]=xxx** to change the value at a given position (specified by **index**).|
| Modifying elements| Use **set(index: number, element: T)** to replace an element at a given position (specified by **index**) with a given element.|
| Modifying elements| Use **setLength(newSize: number)** to set the size of this container.|
| Deleting elements| Use **removeBylndex(index: number)** to remove the value at a given position (specified by **index**).|
| Deleting elements| Use **removeByIndex(index: number)** to remove the value at a given position (specified by **index**).|
| Deleting elements| Use **remove(element: T)** to remove the first occurrence of the specified element.|
| Deleting elements| Use **removeByRange(fromIndex: number, toIndex: number)** to remove all of the elements within a range.|
......@@ -74,14 +74,14 @@ You are advised to use **List** for frequent insertion and removal operations.
| Accessing elements| Use **get(index: number)** to obtain the element at a given position (specified by **index**).|
| Accessing elements| Use **getFirst()** to obtain the first element in this container.|
| Accessing elements| Use **getLast()** to obtain the last element in this container.|
| Accessing elements| Use **getlndexOf(element: T)** to obtain the index of the first occurrence of the specified element.|
| Accessing elements| Use **getLastlndexOf(element: T)** to obtain the index of the last occurrence of the specified element.|
| Accessing elements| Use **getIndexOf(element: T)** to obtain the index of the first occurrence of the specified element.|
| Accessing elements| Use **getLastIndexOf(element: T)** to obtain the index of the last occurrence of the specified element.|
| Accessing elements| Use **forEach(callbackfn: (value: T, index?: number, list?: List&lt;T&gt;)=&gt; void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;T&gt;** for data access.|
| Modifying elements| Use **list\[index] = xxx** to change the value at a given position (specified by **index**).|
| Modifying elements| Use **set(index: number, element: T)** to replace an element at a given position (specified by **index**) with a given element.|
| Modifying elements| Use **replaceAllElements(callbackFn:(value: T,index?: number,list?: List&lt;T&gt;)=&gt;T,thisArg?: Object)** to replace all elements in this container with new elements.|
| Deleting elements| Use **removeBylndex(index: number)** to remove the value at a given position (specified by **index**).|
| Deleting elements| Use **removeByIndex(index: number)** to remove the value at a given position (specified by **index**).|
| Deleting elements| Use **remove(element: T)** to remove the first occurrence of the specified element.|
......@@ -105,13 +105,13 @@ You are advised to use **LinkedList** for frequent insertion and removal operati
| Accessing elements| Use **get(index: number)** to obtain the element at a given position (specified by **index**).|
| Accessing elements| Use **getFirst()** to obtain the first element in this container.|
| Accessing elements| Use **getLast()** to obtain the last element in this container.|
| Accessing elements| Use **getlndexOf(element: T)** to obtain the index of the first occurrence of the specified element.|
| Accessing elements| Use **getLastlndexOf(element: T)** to obtain the index of the last occurrence of the specified element.|
| Accessing elements| Use **getIndexOf(element: T)** to obtain the index of the first occurrence of the specified element.|
| Accessing elements| Use **getLastIndexOf(element: T)** to obtain the index of the last occurrence of the specified element.|
| Accessing elements| Use **forEach(callbackFn: (value: T, index?: number, list?: LinkedList&lt;T&gt;) =&gt; void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;T&gt;** for data access.|
| Modifying elements| Use **list\[index]=xxx** to change the value at a given position (specified by **index**).|
| Modifying elements| Use **set(index: number, element: T)** to replace an element at a given position (specified by **index**) with a given element.|
| Deleting elements| Use **removeBylndex(index: number)** to remove the value at a given position (specified by **index**).|
| Deleting elements| Use **removeByIndex(index: number)** to remove the value at a given position (specified by **index**).|
| Deleting elements| Use **remove(element: T)** to remove the first occurrence of the specified element.|
......
......@@ -12,7 +12,7 @@ Due to the memory isolation feature of the actor model, cross-thread serializati
## Data Transfer Objects
Data objects that can be transferred are classified into three types: [common objects](#common-objects), [transferable objects](#transferable-objects), and [shared objects](#shared-objects).
Data objects that can be transferred are classified into the following types: [common objects](#common-objects), [transferable objects](#transferable-objects), [shared objects](#shared-objects), and [native binding objects](#native-binding-objects).
### Common Objects
......@@ -24,7 +24,7 @@ The following object types are supported: basic types except Symbol, Date, Strin
### Transferable Objects
Transferable objects are serialized through address transfer. It transfers the ownership of an object of the ArrayBuffer type object, rather than the content in it. After the ownership is transferred, the object becomes unavailable in the sender and can be used only in the receiver.
Transferable objects are serialized through address transfer. It transfers the ownership of an object of the ArrayBuffer type, rather than the content in it. After the ownership is transferred, the object becomes unavailable in the sender and can be used only in the receiver.
```js
......@@ -47,6 +47,15 @@ If multiple operations are simultaneously performed to modify data stored in an
let sharedBuffer = new SharedArrayBuffer(1024);
```
### Native Binding Objects
Native binding objects are provided by the system. They are bound to underlying system services and enables direct access to these services.
Currently, native bound objects that support serialization include [Context](../application-models/application-context-stage.md) and [RemoteObject](../reference/apis/js-apis-rpc.md#remoteobject).
The **Context** object provides the context information about an application component. It provides a way to access system services and resources so that the application component can interact with the system. For details about how to obtain context information, see [Context (Stage Model)](../application-models/application-context-stage.md).
The **RemoteObject** object implements remote communication. It transfers the reference of an object between processes so that these processes can share the status and methods of the object. The service provider must inherit this class. For details about how to create a **RemoteObject** object, see [RemoteObject](../reference/apis/js-apis-rpc.md#remoteobject).
## TaskPool and Worker
......
......@@ -52,9 +52,9 @@ You are advised to use **HashSet** when you need a set that has only unique elem
| Adding elements| Use **add(value: T)** to add a value to this container.|
| Accessing elements| Use **values()** to return an iterator that contains all the values in this container.|
| Accessing elements| Use **entries()** to return an iterator that contains all the elements in this container.|
| Accessing elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: HashSet<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: HashSet\<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;T&gt;** for data access.|
| Modifying elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: HashSet<T>) => void, thisArg?: Object)** to change a value in this container.|
| Modifying elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: HashSet\<T>) => void, thisArg?: Object)** to change a value in this container.|
| Deleting elements| Use **remove(value: T)** to remove a value.|
| Deleting elements| Use **clear()** to clear this container.|
......@@ -109,9 +109,9 @@ You are advised to use **TreeSet** when you need to store data in sorted order.
| Accessing elements| Use **entries()** to return an iterator that contains all the elements in this container.|
| Accessing elements| Use **getFirstValue()** to obtain the first value in this container.|
| Accessing elements| Use **getLastValue()** to obtain the last value in this container.|
| Accessing elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: TreeSet<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: TreeSet\<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;T&gt;** for data access.|
| Modifying elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: TreeSet<T>) => void, thisArg?: Object)** to change a value in this container.|
| Modifying elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: TreeSet\<T>) => void, thisArg?: Object)** to change a value in this container.|
| Deleting elements| Use **remove(value: T)** to remove a value.|
| Deleting elements| Use **clear()** to clear this container.|
......@@ -169,9 +169,9 @@ You are advised to use **LightWeightSet** when you need a set that has only uniq
| Accessing elements| Use **values()** to return an iterator that contains all the values in this container.|
| Accessing elements| Use **entries()** to return an iterator that contains all the elements in this container.|
| Accessing elements| Use **getValueAt(index: number)** to obtain the value of an element at a given position (specified by **index**).|
| Accessing elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: LightWeightSet<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: LightWeightSet\<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;T&gt;** for data access.|
| Modifying elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: LightWeightSet<T>) => void, thisArg?: Object)** to change a value in this container.|
| Modifying elements| Use **forEach(callbackFn: (value?: T, key?: T, set?: LightWeightSet\<T>) => void, thisArg?: Object)** to change a value in this container.|
| Deleting elements| Use **remove(key: K)** to remove an element with the specified key.|
| Deleting elements| Use **removeAt(index: number)** to remove an element at a given position (specified by **index**).|
| Deleting elements| Use **clear()** to clear this container.|
......@@ -197,10 +197,10 @@ You are advised to use PlainArray when you need to store KV pairs whose keys are
| Accessing elements| Use **getIndexOfValue(value: T)** to obtain the index of the specified value.|
| Accessing elements| Use **getKeyAt(index: number)** to obtain the key of an element at a given position (specified by **index**).|
| Accessing elements| Use **getValueAt(index: number)** to obtain the value of an element at a given position (specified by **index**).|
| Accessing elements| Use **forEach(callbackFn: (value: T, index?: number, PlainArray?: PlainArray<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **forEach(callbackFn: (value: T, index?: number, PlainArray?: PlainArray\<T>) => void, thisArg?: Object)** to traverse the elements in this container.|
| Accessing elements| Use **\[Symbol.iterator]():IterableIterator&lt;[number, T]&gt;** for data access.|
| Modifying elements| Use **setValueAt(index:number, value: T)** to change the value of an element at a given position (specified by **index**).|
| Modifying elements| Use **forEach(callbackFn: (value: T, index?: number, PlainArray?: PlainArray<T>) => void, thisArg?: Object)** to modify an element in this container.|
| Modifying elements| Use **forEach(callbackFn: (value: T, index?: number, PlainArray?: PlainArray\<T>) => void, thisArg?: Object)** to modify an element in this container.|
| Deleting elements| Use **remove(key: number)** to remove an element with the specified key.|
| Deleting elements| Use **removeAt(index: number)** to remove an element at a given position (specified by **index**).|
| Deleting elements| Use **removeRangeFrom(index: number, size: number)** to remove elements in a specified range.|
......
......@@ -4,7 +4,7 @@
Data transferred in XML format must be parsed in actual use. Generally, three types of elements need to be parsed, as described in [Parsing XML Tags and Tag Values](#parsing-xml-tags-and-tag-values), [Parsing XML Attributes and Attribute Values](#parsing-xml-attributes-and-attribute-values), and [Parsing XML Event Types and Element Depths](#parsing-xml-event-types-and-element-depths).
The **xml** module provides the **XmlPullParser** class to parse XML files. The input is an object of the ArrayBufffer or DataView type containing XML text, and the output is the parsed information.
The **xml** module provides the **XmlPullParser** class to parse XML files. The input is an object of the ArrayBuffer or DataView type containing XML text, and the output is the parsed information.
**Table 1** XML parsing options
......
......@@ -9,6 +9,8 @@
- [Ethernet Connection](net-ethernet.md)
- [Network Connection Management](net-connection-manager.md)
- [mDNS Management](net-mdns.md)
- [Traffic Management](net-statistics.md)
- [VPN Management](net-vpn.md)
- IPC & RPC
- [IPC & RPC Overview](ipc-rpc-overview.md)
- [IPC & RPC Development](ipc-rpc-development-guideline.md)
......
# HTTP Data Request
## When to Use
## Overview
An application can initiate a data request over HTTP. Common HTTP methods include **GET**, **POST**, **OPTIONS**, **HEAD**, **PUT**, **DELETE**, **TRACE**, and **CONNECT**.
......@@ -18,7 +18,7 @@ The following table provides only a simple description of the related APIs. For
| ----------------------------------------- | ----------------------------------- |
| createHttp() | Creates an HTTP request. |
| request() | Initiates an HTTP request to a given URL. |
| request2()<sup>10+</sup> | Initiates an HTTP network request based on the URL and returns a streaming response.|
| requestInStream()<sup>10+</sup> | Initiates an HTTP network request to a given URL and returns a streaming response.|
| destroy() | Destroys an HTTP request. |
| on(type: 'headersReceive') | Registers an observer for HTTP Response Header events. |
| off(type: 'headersReceive') | Unregisters the observer for HTTP Response Header events.|
......@@ -27,8 +27,8 @@ The following table provides only a simple description of the related APIs. For
| off\('dataReceive'\)<sup>10+</sup> | Unregisters the observer for events indicating receiving of HTTP streaming responses. |
| on\('dataEnd'\)<sup>10+</sup> | Registers an observer for events indicating completion of receiving HTTP streaming responses. |
| off\('dataEnd'\)<sup>10+</sup> | Unregisters the observer for events indicating completion of receiving HTTP streaming responses.|
| on\('dataProgress'\)<sup>10+</sup> | Registers an observer for events indicating progress of receiving HTTP streaming responses. |
| off\('dataProgress'\)<sup>10+</sup> | Unregisters the observer for events indicating progress of receiving HTTP streaming responses.|
| on\('dataReceiveProgress'\)<sup>10+</sup> | Registers an observer for events indicating progress of receiving HTTP streaming responses. |
| off\('dataReceiveProgress'\)<sup>10+</sup> | Unregisters the observer for events indicating progress of receiving HTTP streaming responses.|
## How to Develop request APIs
......@@ -53,6 +53,7 @@ httpRequest.on('headersReceive', (header) => {
});
httpRequest.request(
// Customize EXAMPLE_URL in extraData on your own. It is up to you whether to add parameters to the URL.
"EXAMPLE_URL",
{
method: http.RequestMethod.POST, // Optional. The default value is http.RequestMethod.GET.
// You can add header fields based on service requirements.
......@@ -81,7 +82,7 @@ httpRequest.request(
// Call the destroy() method to release resources after HttpRequest is complete.
httpRequest.destroy();
} else {
console.info('error:' + JSON.stringify(err));
console.error('error:' + JSON.stringify(err));
// Unsubscribe from HTTP Response Header events.
httpRequest.off('headersReceive');
// Call the destroy() method to release resources after HttpRequest is complete.
......@@ -91,12 +92,12 @@ httpRequest.request(
);
```
## How to Develop request2 APIs
## How to Develop requestInStream APIs
1. Import the **http** namespace from **@ohos.net.http.d.ts**.
2. Call **createHttp()** to create an **HttpRequest** object.
3. Depending on your need, call **on()** of the **HttpRequest** object to subscribe to HTTP response header events as well as events indicating receiving of HTTP streaming responses, progress of receiving HTTP streaming responses, and completion of receiving HTTP streaming responses.
4. Call **request2()** to initiate a network request. You need to pass in the URL and optional parameters of the HTTP request.
4. Call **requestInStream()** to initiate a network request. You need to pass in the URL and optional parameters of the HTTP request.
5. Parse the returned response code as needed.
6. Call **off()** of the **HttpRequest** object to unsubscribe from the related events.
7. Call **httpRequest.destroy()** to release resources after the request is processed.
......@@ -122,11 +123,11 @@ httpRequest.on('dataEnd', () => {
console.info('No more data in response, data receive end');
});
// Subscribe to events indicating progress of receiving HTTP streaming responses.
httpRequest.on('dataProgress', (data) => {
console.log("dataProgress receiveSize:" + data.receiveSize + ", totalSize:" + data.totalSize);
httpRequest.on('dataReceiveProgress', (data) => {
console.log("dataReceiveProgress receiveSize:" + data.receiveSize + ", totalSize:" + data.totalSize);
});
httpRequest.request2(
httpRequest.requestInStream(
// Customize EXAMPLE_URL in extraData on your own. It is up to you whether to add parameters to the URL.
"EXAMPLE_URL",
{
......@@ -146,14 +147,14 @@ httpRequest.request2(
readTimeout: 60000, // Optional. The default value is 60000, in ms. If a large amount of data needs to be transmitted, you are advised to set this parameter to a larger value to ensure normal data transmission.
usingProtocol: http.HttpProtocol.HTTP1_1, // Optional. The default protocol type is automatically specified by the system.
}, (err, data) => {
console.info('error:' + JSON.stringify(err));
console.error('error:' + JSON.stringify(err));
console.info('ResponseCode :' + JSON.stringify(data));
// Unsubscribe from HTTP Response Header events.
httpRequest.off('headersReceive');
// Unregister the observer for events indicating receiving of HTTP streaming responses.
httpRequest.off('dataReceive');
// Unregister the observer for events indicating progress of receiving HTTP streaming responses.
httpRequest.off('dataProgress');
httpRequest.off('dataReceiveProgress');
// Unregister the observer for events indicating completion of receiving HTTP streaming responses.
httpRequest.off('dataEnd');
// Call the destroy() method to release resources after HttpRequest is complete.
......@@ -161,10 +162,3 @@ httpRequest.request2(
}
);
```
## Samples
The following sample is provided to help you better understand how to develop the HTTP data request feature:
- [`Http`: Data Request (ArkTS) (API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Connectivity/Http)
- [HTTP Communication (ArkTS) (API9)](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/SmartChatEtsOH)
# IPC & RPC Development Guidelines
## When to Use
## Overview
IPC/RPC enables a proxy and a stub that run on different processes to communicate with each other, regardless of whether they run on the same or different devices.
......
# Network Connection Management
## Introduction
## Overview
The Network Connection Management module provides basic network management capabilities, including management of Wi-Fi/cellular/Ethernet connection priorities, network quality evaluation, subscription to network connection status changes, query of network connection information, and DNS resolution.
> **NOTE**
>
> To maximize the application running efficiency, most API calls are called asynchronously in callback or promise mode. The following code examples use the callback mode. For details about the APIs, see [sms API Reference](../reference/apis/js-apis-net-connection.md).
## Basic Concepts
......@@ -107,7 +108,7 @@ conn.on('netAvailable', (data => {
// Listen to network status change events. If the network is unavailable, an on_netUnavailable event is returned.
conn.on('netUnavailable', (data => {
console.log("net is unavailable, netId is " + data.netId);
console.log("net is unavailable, data is " + JSON.stringify(data));
}));
// Register an observer for network status changes.
......
# Ethernet Connection
## Introduction
## Overview
The Ethernet Connection module allows a device to access the Internet through a network cable. After a device is connected to the Ethernet through a network cable, the device can obtain a series of network attributes, such as the dynamically allocated IP address, subnet mask, gateway, and DNS. You can manually configure and obtain the network attributes of the device in static mode.
> **NOTE**
>
> To maximize the application running efficiency, most API calls are called asynchronously in callback or promise mode. The following code examples use the callback mode. For details about the APIs, see [sms API Reference](../reference/apis/js-apis-net-ethernet.md).
## **Constraints**
......
# MDNS Management
## Introduction
## Overview
Multicast DNS (mDNS) provides functions such as adding, removing, discovering, and resolving local services on a LAN.
- Local service: a service provider on a LAN, for example, a printer or scanner.
......
# Network Sharing
## Introduction
## Overview
The Network Sharing module allows you to share your device's Internet connection with other connected devices by means of Wi-Fi hotspot, Bluetooth, and USB sharing. It also allows you to query the network sharing state and shared mobile data volume.
> **NOTE**
>
> To maximize the application running efficiency, most API calls are called asynchronously in callback or promise mode. The following code examples use the callback mode. For details about the APIs, see [sms API Reference](../reference/apis/js-apis-net-sharing.md).
## Basic Concepts
......
# Traffic Management
## Introduction
## Overview
The traffic management module allows you to query real-time or historical data traffic by the specified network interface card (NIC) or user ID (UID).
......@@ -11,6 +11,7 @@ Its functions include:
- Subscribing to traffic change events by NIC or UID
> **NOTE**
>
> To maximize the application running efficiency, most API calls are called asynchronously in callback or promise mode. The following code examples use the callback mode. For details about the APIs, see [Traffic Management](../reference/apis/js-apis-net-statistics.md).
The following describes the development procedure specific to each application scenario.
......
# VPN Management
## Overview
A virtual private network (VPN) is a dedicated network established on a public network. On a VPN, the connection between any two nodes does not have an end-to-end physical link required by the traditional private network. Instead, user data is transmitted over a logical link because a VPN is a logical network deployed over the network platform (such as the Internet) provided by the public network service provider.
> **NOTE**
>
> To maximize the application running efficiency, most API calls are called asynchronously in callback or promise mode. The following code examples use the callback mode. For details about the APIs, see [Traffic Management](../reference/apis/js-apis-net-vpn.md).
The following describes the development procedure specific to each application scenario.
## Available APIs
For the complete list of APIs and example code, see [VPN Management](../reference/apis/js-apis-net-vpn.md).
| Type| API| Description|
| ---- | ---- | ---- |
| ohos.net.vpn | setUp(config: VpnConfig, callback: AsyncCallback\<number\>): void | Establishes a VPN. This API uses an asynchronous callback to return the result.|
| ohos.net.vpn | protect(socketFd: number, callback: AsyncCallback\<void\>): void | Enables VPN tunnel protection. This API uses an asynchronous callback to return the result.|
| ohos.net.vpn | destroy(callback: AsyncCallback\<void\>): void | Destroys a VPN. This API uses an asynchronous callback to return the result.|
## Starting a VPN
1. Establish a VPN tunnel. The following uses the UDP tunnel as an example.
2. Enable protection for the UDP tunnel.
3. Establish a VPN.
4. Process data of the virtual network interface card (vNIC), such as reading or writing data.
5. Destroy the VPN.
This example shows how to develop an application using native C++ code. For details, see [Simple Native C++ Example (ArkTS) (API9)] (https://gitee.com/openharmony/codelabs/tree/master/NativeAPI/NativeTemplateDemo).
The sample application consists of two parts: JS code and C++ code.
## JS Code
The JS code is used to implement the service logic, such as creating a tunnel, establishing a VPN, enabling VPN protection, and destroying a VPN.
```js
import hilog from '@ohos.hilog';
import vpn from '@ohos.net.vpn';
import UIAbility from '@ohos.app.ability.UIAbility';
import vpn_client from "libvpn_client.so"
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
globalThis.context = this.context;
}
}
let TunnelFd = -1
let VpnConnection = vpn.createVpnConnection(globalThis.context)
@Entry
@Component
struct Index {
@State message: string = 'Test VPN'
//1. Establish a VPN tunnel. The following uses the UDP tunnel as an example.
CreateTunnel() {
TunnelFd = vpn_client.udpConnect("192.168.43.208", 8888)
}
// 2. Enable protection for the UDP tunnel.
Protect() {
VpnConnection.protect(TunnelFd).then(function () {
console.info("vpn Protect Success.")
}).catch(function (err) {
console.info("vpn Protect Failed " + JSON.stringify(err))
})
}
SetupVpn() {
let config = {
addresses: [{
address: {
address: "10.0.0.5",
family: 1
},
prefixLength: 24,
}],
routes: [],
mtu: 1400,
dnsAddresses: [
"114.114.114.114"
],
acceptedApplications: [],
refusedApplications: []
}
try {
// 3. Create a VPN.
VpnConnection.setUp(config, (error, data) => {
console.info("tunfd: " + JSON.stringify(data));
// 4. Process data of the virtual vNIC, such as reading or writing data.
vpn_client.startVpn(data, TunnelFd)
})
} catch (error) {
console.info("vpn setUp fail " + JSON.stringify(error));
}
}
// 5. Destroy the VPN.
Destroy() {
vpn_client.stopVpn(TunnelFd)
VpnConnection.destroy().then(function () {
console.info("vpn Destroy Success.")
}).catch(function (err) {
console.info("vpn Destroy Failed " + JSON.stringify(err))
})
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
console.info("vpn Client")
})
Button('CreateTunnel').onClick(() => {
this.CreateTunnel()
}).fontSize(50)
Button('Protect').onClick(() => {
this.Protect()
}).fontSize(50)
Button('SetupVpn').onClick(() => {
this.SetupVpn()
}).fontSize(50)
Button('Destroy').onClick(() => {
this.Destroy()
}).fontSize(50)
}
.width('100%')
}
.height('100%')
}
}
```
## C++ Code
The C++ code is used for underlying service implementation, such as UDP tunnel client implementation and vNIC data read and write.
```c++
#include "napi/native_api.h"
#include "hilog/log.h"
#include <cstring>
#include <thread>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <thread>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 2048
#define VPN_LOG_TAG "NetMgrVpn"
#define VPN_LOG_DOMAIN 0x15b0
#define MAKE_FILE_NAME (strrchr(__FILE__, '/') + 1)
#define NETMANAGER_VPN_LOGE(fmt, ...) \
OH_LOG_Print(LOG_APP, LOG_ERROR, VPN_LOG_DOMAIN, VPN_LOG_TAG, "vpn [%{public}s %{public}d] " fmt, MAKE_FILE_NAME, \
__LINE__, ##__VA_ARGS__)
#define NETMANAGER_VPN_LOGI(fmt, ...) \
OH_LOG_Print(LOG_APP, LOG_INFO, VPN_LOG_DOMAIN, VPN_LOG_TAG, "vpn [%{public}s %{public}d] " fmt, MAKE_FILE_NAME, \
__LINE__, ##__VA_ARGS__)
#define NETMANAGER_VPN_LOGD(fmt, ...) \
OH_LOG_Print(LOG_APP, LOG_DEBUG, VPN_LOG_DOMAIN, VPN_LOG_TAG, "vpn [%{public}s %{public}d] " fmt, MAKE_FILE_NAME, \
__LINE__, ##__VA_ARGS__)
struct FdInfo {
int32_t tunFd = 0;
int32_t tunnelFd = 0;
struct sockaddr_in serverAddr;
};
static FdInfo fdInfo;
static bool threadRunF = false;
static std::thread threadt1;
static std::thread threadt2;
// Obtain the IP address of the UDP server.
static constexpr const int MAX_STRING_LENGTH = 1024;
std::string GetStringFromValueUtf8(napi_env env, napi_value value) {
std::string result;
char str[MAX_STRING_LENGTH] = {0};
size_t length = 0;
napi_get_value_string_utf8(env, value, str, MAX_STRING_LENGTH, &length);
if (length > 0) {
return result.append(str, length);
}
return result;
}
void HandleReadTunfd(FdInfo fdInfo) {
uint8_t buffer[BUFFER_SIZE] = {0};
while (threadRunF) {
int ret = read(fdInfo.tunFd, buffer, sizeof(buffer));
if (ret <= 0) {
if (errno != 11) {
NETMANAGER_VPN_LOGE("read tun device error: %{public}d, tunfd: %{public}d", errno, fdInfo.tunFd);
}
continue;
}
// Read data from the vNIC and send the data to the UDP server through the UDP tunnel.
NETMANAGER_VPN_LOGD("buffer: %{public}s, len: %{public}d", buffer, ret);
ret = sendto(fdInfo.tunnelFd, buffer, ret, 0, (struct sockaddr *)&fdInfo.serverAddr, sizeof(fdInfo.serverAddr));
if (ret <= 0) {
NETMANAGER_VPN_LOGE("send to server[%{public}s:%{public}d] failed, ret: %{public}d, error: %{public}s",
inet_ntoa(fdInfo.serverAddr.sin_addr), ntohs(fdInfo.serverAddr.sin_port), ret,
strerror(errno));
continue;
}
}
}
void HandleTcpReceived(FdInfo fdInfo) {
int addrlen = sizeof(struct sockaddr_in);
uint8_t buffer[BUFFER_SIZE] = {0};
while (threadRunF) {
int length = recvfrom(fdInfo.tunnelFd, buffer, sizeof(buffer), 0, (struct sockaddr *)&fdInfo.serverAddr,
(socklen_t *)&addrlen);
if (length < 0) {
if (errno != 11) {
NETMANAGER_VPN_LOGE("read tun device error: %{public}d, tunnelfd: %{public}d", errno, fdInfo.tunnelFd);
}
continue;
}
// Receive data from the UDP server and write the data to the vNIC.
NETMANAGER_VPN_LOGD("from [%{public}s:%{public}d] data: %{public}s, len: %{public}d",
inet_ntoa(fdInfo.serverAddr.sin_addr), ntohs(fdInfo.serverAddr.sin_port), buffer, length);
int ret = write(fdInfo.tunFd, buffer, length);
if (ret <= 0) {
NETMANAGER_VPN_LOGE("error Write To Tunfd, errno: %{public}d", errno);
}
}
}
static napi_value UdpConnect(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int32_t port = 0;
napi_get_value_int32(env, args[1], &port);
std::string ipAddr = GetStringFromValueUtf8(env, args[0]);
NETMANAGER_VPN_LOGI("ip: %{public}s port: %{public}d", ipAddr.c_str(), port);
// Establish a UDP tunnel.
int32_t sockFd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockFd == -1) {
NETMANAGER_VPN_LOGE("socket() error");
return 0;
}
struct timeval timeout = {1, 0};
setsockopt(sockFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval));
memset(&fdInfo.serverAddr, 0, sizeof(fdInfo.serverAddr));
fdInfo.serverAddr.sin_family = AF_INET;
fdInfo.serverAddr.sin_addr.s_addr = inet_addr(ipAddr.c_str()); // server's IP addr
fdInfo.serverAddr.sin_port = htons(port); // port
NETMANAGER_VPN_LOGI("Connection successful");
napi_value tunnelFd;
napi_create_int32(env, sockFd, &tunnelFd);
return tunnelFd;
}
static napi_value StartVpn(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
napi_get_value_int32(env, args[0], &fdInfo.tunFd);
napi_get_value_int32(env, args[1], &fdInfo.tunnelFd);
if (threadRunF) {
threadRunF = false;
threadt1.join();
threadt2.join();
}
// Start two threads. One is used to read data from the vNIC, and the other is used to receive data from the server.
threadRunF = true;
std::thread tt1(HandleReadTunfd, fdInfo);
std::thread tt2(HandleTcpReceived, fdInfo);
threadt1 = std::move(tt1);
threadt2 = std::move(tt2);
NETMANAGER_VPN_LOGI("StartVpn successful");
napi_value retValue;
napi_create_int32(env, 0, &retValue);
return retValue;
}
static napi_value StopVpn(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int32_t tunnelFd;
napi_get_value_int32(env, args[0], &tunnelFd);
if (tunnelFd) {
close(tunnelFd);
tunnelFd = 0;
}
// Stop the two threads.
if (threadRunF) {
threadRunF = false;
threadt1.join();
threadt2.join();
}
NETMANAGER_VPN_LOGI("StopVpn successful");
napi_value retValue;
napi_create_int32(env, 0, &retValue);
return retValue;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"udpConnect", nullptr, UdpConnect, nullptr, nullptr, nullptr, napi_default, nullptr},
{"startVpn", nullptr, StartVpn, nullptr, nullptr, nullptr, napi_default, nullptr},
{"stopVpn", nullptr, StopVpn, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void *)0),
.reserved = {0},
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
napi_module_register(&demoModule);
}
```
# Socket Connection
## Introduction
## Overview
The Socket Connection module allows an application to transmit data over a socket connection through the TCP, UDP, or TLS protocol.
......
# Subscribing to State Changes of a Remote Object
## Overview
IPC/RPC allows you to subscribe to the state changes of a remote stub object. When the remote stub object dies, a death notification will be sent to your local proxy object. Such subscription and unsubscription are controlled by APIs. To be specific, you need to implement the **DeathRecipient** interface and the **onRemoteDied** API to clear resources. This callback is invoked when the process accommodating the remote stub object dies, or the device accommodating the remote stub object leaves the network. It is worth noting that these APIs should be called in the following order: The proxy object must first subscribe to death notifications of the stub object. If the stub object is in the normal state, the proxy object can cancel the subscription as required. If the process of the stub object exits or the device hosting the stub object goes offline, subsequent operations customized by the proxy object will be automatically triggered.
## When to Use
......
# WebSocket Connection
## When to Use
## Overview
You can use WebSocket to establish a bidirectional connection between a server and a client. Before doing this, you need to use the **createWebSocket()** API to create a **WebSocket** object and then use the **connect()** API to connect to the server. If the connection is successful, the client will receive a callback of the **open** event. Then, the client can communicate with the server using the **send()** API. When the server sends a message to the client, the client will receive a callback of the **message** event. If the client no longer needs this connection, it can call the **close()** API to disconnect from the server. Then, the client will receive a callback of the **close** event.
......
......@@ -68,7 +68,7 @@ The following table lists the APIs used for persisting user preference data. For
return;
}
console.info('Succeeded in getting preferences.');
// Perform related data operations.
// Before performing related data operations, obtain a Preferences instance.
})
} catch (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
......@@ -93,7 +93,7 @@ The following table lists the APIs used for persisting user preference data. For
return;
}
console.info('Succeeded in getting preferences.');
// Perform related data operations.
// Before performing related data operations, obtain a Preferences instance.
})
} catch (err) {
console.error(`Failed to get preferences. Code is ${err.code},message:${err.message}`);
......@@ -220,4 +220,4 @@ The following table lists the APIs used for persisting user preference data. For
} catch (err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
}
```
```
\ No newline at end of file
......@@ -171,7 +171,7 @@ The following uses a single KV store as an example to describe how to implement
return;
}
console.info('Succeeded in getting KVStore.');
// Perform related data operations.
// Before performing related data operations, obtain a KV store instance.
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
......@@ -275,4 +275,4 @@ The following uses a single KV store as an example to describe how to implement
}
}
});
```
```
\ No newline at end of file
# DFX
- [Development of Application Event Logging](hiappevent-guidelines.md)
- [Development of Performance Tracing](hitracemeter-guidelines.md)
- [Development of Distributed Call Chain Tracing](hitracechain-guidelines.md)
- [HiLog Development (Native)](hilog-guidelines.md)
- Performance Tracing
......
# Application Freeze (appfreeze) Log Analysis
## Introduction
## Overview
Application freeze (appfreeze) means that an application does not respond to user operations (for example, clicking) within a given period of time. OpenHarmony provides a mechanism for detecting appfreeze faults and generates appfreeze logs for fault analysis.
......
# Application Recovery Development
## When to Use
## Overview
During application running, some unexpected behaviors are inevitable. For example, unprocessed exceptions and errors are thrown, and the call or running constraints of the recovery framework are violated.
......@@ -99,9 +99,12 @@ import AbilityConstant from '@ohos.app.ability.AbilityConstant'
- Define and register the [ErrorObserver](../reference/apis/js-apis-inner-application-errorObserver.md) callback. For details about its usage, see [errorManager](../reference/apis/js-apis-app-ability-errorManager.md).
```ts
var registerId = -1;
var callback = {
onUnhandledException(errMsg) {
export let abilityWant : Want // file1
import * as G form "../file1"
let registerId = -1;
let callback: Callback = {
onUnhandledException(errMsg: string): void {
console.log(errMsg);
appRecovery.saveAppState();
appRecovery.restartApp();
......@@ -112,7 +115,7 @@ import AbilityConstant from '@ohos.app.ability.AbilityConstant'
// Main window is created, set main page for this ability
console.log("[Demo] MainAbility onWindowStageCreate")
globalThis.registerObserver = (() => {
G.registerObserver = (() => {
registerId = errorManager.on('error', callback);
})
......@@ -138,13 +141,16 @@ After the callback triggers **appRecovery.saveAppState()**, **onSaveState(state,
After the callback triggers **appRecovery.restartApp()**, the application is restarted. After the restart, **onCreate(want, launchParam)** of **MainAbility** is called, and the saved data is in **parameters** of **want**.
```ts
export let abilityWant : Want // file1
import * as GlobalWant form "../file1"
storage: LocalStorage
onCreate(want, launchParam) {
console.log("[Demo] MainAbility onCreate")
globalThis.abilityWant = want;
GlobalWant.abilityWant = want;
if (launchParam.launchReason == AbilityConstant.LaunchReason.APP_RECOVERY) {
this.storage = new LocalStorage();
let recoveryData = want.parameters["myData"];
let recoveryData: string = want.parameters["myData"];
this.storage.setOrCreate("myData", recoveryData);
this.context.restoreWindowStage(this.storage);
}
......@@ -154,12 +160,15 @@ onCreate(want, launchParam) {
- Unregister the **ErrorObserver** callback.
```ts
export let abilityWant : Want // file1
import * as G form "../file1"
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
console.log("[Demo] MainAbility onWindowStageDestroy")
globalThis.unRegisterObserver = (() => {
errorManager.off('error', registerId, (err) => {
G.unRegisterObserver = (() => {
errorManager.off(type: 'error', registerId: number, (err:Error) => {
console.error("[Demo] err:", err);
});
})
......@@ -171,20 +180,22 @@ onWindowStageDestroy() {
This is triggered by the recovery framework. You do not need to register an **ErrorObserver** callback. You only need to implement **onSaveState** for application state saving and **onCreate** for data restore.
```ts
export let abilityWant : Want // file1
import * as GlobalWant form "../file1"
export default class MainAbility extends Ability {
storage: LocalStorage
onCreate(want, launchParam) {
onCreate(want: Want, launchParam:AbilityConstant.LaunchParam):void {
console.log("[Demo] MainAbility onCreate")
globalThis.abilityWant = want;
GlobalWant.abilityWant = want;
if (launchParam.launchReason == AbilityConstant.LaunchReason.APP_RECOVERY) {
this.storage = new LocalStorage();
let recoveryData = want.parameters["myData"];
this.storage.setOrCreate("myData", recoveryData);
let recoveryData: string = want.parameters["myData"];
this.storage.setOrCreate<string>("myData", recoveryData);
this.context.restoreWindowStage(this.storage);
}
}
onSaveState(state, wantParams) {
onSaveState(state: AbilityConstant.StateType, wantParams: { [key: string]: Object }) : AbilityConstant.OnSaveResult{
// Ability has called to save app data
console.log("[Demo] MainAbility onSaveState")
wantParams["myData"] = "my1234567";
......
# cppcrash Log Analysis
# Process Crash (cppcrash) Log Analysis
## Introduction
## Overview
A process crash refers to a C/C++ runtime crash. The FaultLogger module of OpenHarmony provides capabilities such as process crash detection, log collection, log storage, and log reporting, helping you to locate faults more effectively.
......@@ -23,7 +23,7 @@ Process crash detection is implemented based on the Linux signal mechanism. Curr
## Crash Log Collection
Process crash log is the fault log managed together with the app freeze and JS application crash logs by the FaultLogger module. You can collect process crash logs in any of the following ways:
Process crash log is a type of fault logs managed together with the app freeze and JS application crash logs by the FaultLogger module. You can collect process crash logs in any of the following ways:
### Collecting Logs by Using Shell
......@@ -72,17 +72,14 @@ Thread name:crasher <- Abnormal thread
### Locating Faults Through Logs
1. Determine the faulty module and fault type based on fault logs.
Generally, you can identify the faulty module based on the crash process name and identify the crash cause based on the signal. Besides, you can restore the function call chain of the crash stack based on the method name in the stack.
In the example, **SIGSEGV** is thrown by the Linux kernel because of access to an invalid memory address. The problem occurs in the **TriggerSegmentFaultException** function.
In most scenarios, a crash is caused by the top layer of the crash stack, such as null pointer access and proactive program abort.
- Determine the faulty module and fault type based on fault logs.
Generally, you can identify the faulty module based on the crash process name and identify the crash cause based on the signal. Besides, you can restore the function call chain of the crash stack based on the method name in the stack.\
In the example, **SIGSEGV** is thrown by the Linux kernel because of access to an invalid memory address. The problem occurs in the **TriggerSegmentFaultException** function.\
In most scenarios, a crash is caused by the top layer of the crash stack, such as null pointer access and proactive program abort.\
If the cause cannot be located through the call stack, you need to check for other faults, for example, memory corruption or stack overflow.
2. Use the addr2line tool of Linux to parse the code line number to restore the call stack at the time of process crash.
- Use the addr2line tool of Linux to parse the code line number to restore the call stack at the time of process crash.
When using the addr2line tool to parse the code line number of the crash stack, make sure that binary files with debugging information is used. Generally, such files are generated during version build or application build.
......@@ -94,17 +91,17 @@ Thread name:crasher <- Abnormal thread
\code root directory\out\product\exe.unstripped
```
You can run `apt-get install addr2line` to install the addr2line tool on Linux.
You can run `apt-get install addr2line` to install the addr2line tool on Linux.\
On On DevEco Studio, you can also use the llvm-addr2line tool archived in the SDK to parse code line numbers. The usage method is the same.
The following example shows how to use the addr2line tool to parse the code line number based on the offset address:
The following example shows how to use the addr2line tool to parse the code line number based on the offset address.
**[product name]** indicates the device name.
```
root:~/OpenHarmony/out/rk3568/exe.unstripped/hiviewdfx/faultloggerd$ addr2line -e crasher 0000332c
root:~/OpenHarmony/out/[product name]/exe.unstripped/hiviewdfx/faultloggerd$ addr2line -e crasher 0000332c
base/hiviewdfx/faultloggerd/tools/crasher/dfx_crasher.c:57
```
In this example, the crash is caused by assignment of a value to an unwritable area. It is in code line 57 in the **dfx_crasher.c** file. You can modify it to avoid the crash.
In this example, the crash is caused by assignment of a value to an unwritable area. It is in code line 57 in the **dfx_crasher.c** file. You can modify it to avoid the crash.\
If the obtained code line number is seemingly incorrect, you can fine-tune the address (for example, subtract the address by 1) or disable some compilation optimization items. It is known that the obtained code line number may be incorrect when Link Time Optimization (LTO) is enabled.
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册