积分582 / 贡献0

提问0答案被采纳0文章52

[经验分享] OpenHarmony_Ability框架详解 原创 精华

Laval社区小助手 显示全部楼层 发表于 2023-12-4 11:03:52

详情:OpenHarmony_Ability框架详解

开发环境

IDE:DevEco Studio 3.0 Release(Build Version: 3.0.0.993)

SDK:Api Version 8和9

Ability框架

概述

Ability是应用所具备能力的抽象,也是应用程序的重要组成部分。Ability是系统调度应用的最小单元,是能够完成一个独立功能的组件。一个应用可以包含一个或多个Ability。

类型

Ability的类型存在差异:

Ability框架模型具有两种形态:

  • FA模型 API 8及其更早版本的应用程序只能使用FA模型进行开发。FA模型将Ability分为FA(Feature Ability)和PA(Particle Ability)两种类型,其中其中FA支持PageAbility,PA支持ServiceAbility、DataAbility、以及FormAbility。
  • Stage模型 从API 9开始,Ability框架引入了Stage模型作为第二种应用框架形态,Stage模型将Ability分为PageAbility和ExtensionAbility两大类,其中ExtensionAbility又被扩展为ServiceExtensionAbility、FormExtensionAbility、DataShareExtensionAbility等一系列ExtensionAbility,以便满足更多的使用场景。

基础用法

FA模型

PageAbility

PageAbility是具备ArkUI实现的Ability,是开发者具体可见并可以与之交互的Ability实例。开发者通过DevEco Studio创建Ability时,DevEco Studio会自动创建相关模板代码。 PageAbility相关能力通过单独的featureAbility实现,生命周期相关回调则通过app.js/app.ets中各个回调函数实现。

创建PageAbility
  1. 首次创建工程,打开DevEco Studio选择Create Project(如果在已有的工程界面创建新工程,则选择File->New->Create Project),然后选择OpenHarmony下的Empty Ability模板,点击Next进行下一步配置。

  1. 进入工程配置界面,按默认参数设置即可,点击Finish,工具会自动生成示例代码和相关资源,等待工程创建成功。

  1. 工程创建成功后生成一个默认的PageAbility,名为MainAbility。

  1. 如需在工程中创建多个PageAbility,点击File->New->Ability->Page Ability即可。

featureAbility接口说明
接口名 描述
void: startAbility(parameter: StartAbilityParameter) 启动Ability。
Context: getContext(): 获取应用Context。
void: terminateSelf() 结束Ability。
bool: hasWindowFocus() 是否获取焦点。
启动PageAbility

使用featureAbility.startAbility方法可以启动本地设备的PageAbility。

  1. 导入模块
import featureAbility from '@ohos.ability.featureAbility'
  1. 调用featureAbility.startAbility方法,由该方法的Want传递bundleName和abilityName属性来启动PageAbility。
  • bundleName:表示包名称。可在config.json中的app字段查看bundleName,或者通过File->Project Structure查看。
  • abilityName:表示待启动的Ability名称,由config.json中的package字段与abilities字段下的name字段组成。
featureAbility.startAbility({
    want: {
        bundleName: "com.samples.myapplication",
        // FA模型abilityName由config.json中的package + Ability name组成
        abilityName: "com.samples.myapplication.MainAbility"
    }
});

ServiceAbility

基于Service模板的Ability(以下简称“Service”),主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户UI交互界面。Service可由其他应用或Ability启动。即使用户切换到其他应用,Service仍将在后台继续运行。

接口说明

Service中相关生命周期API功能介绍

接口名 描述
onStart?(): void 该方法在创建Service的时候调用,用于Service的初始化。在Service的整个生命周期只会调用一次,调用时传入的Want应为空。
onCommand?(want: Want, startId: number): void 在Service创建完成之后调用,该方法在客户端每次启动该Service时都会调用,开发者可以在该方法中做一些调用统计、初始化类的操作。
onConnect?(want: Want): rpc.RemoteObject 在Ability和Service连接时调用。
onDisconnect?(want: Want): void 在Ability与绑定的Service断开连接时调用。
onStop?(): void 在Service销毁时调用。开发者应通过实现此方法来清理资源,如关闭线程、注册的侦听器等。
创建与注册Service
  1. 在工程中,选择File->New->Ability->Service Ability,点击Finish,成功创建ServiceAbility,默认生成service.ts文件。

  1. 重写Service的生命周期方法,来添加其他Ability请求与ServiceAbility交互时的处理方法。
// service.ts
import hilog from '@ohos.hilog';

export default {
    onStart() {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onStart');
    },
    onStop() {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onStop');
    },
    onCommand(want, startId) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onCommand');
    },
    onConnect(want) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onConnect');
        return null;
    },
    onDisconnect(want) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility OnDisConnect');
    }
};
  1. 注册Service。FA模型在创建ServiceAbility时已在config.json中自动注册,注册类型type为service,只需手动添加visible字段并设置为true。

config.json配置样例

// config.json
"module": {
    ...
    "abilities": [
      ...
      {
        "name": ".ServiceAbility",
        "srcLanguage": "ets",
        "srcPath": "ServiceAbility",
        "icon": "$media:icon",
        "description": "$string:ServiceAbility_desc",
        "type": "service",
        "visible": true
      }
      ...
    ]
    ...
}

Json重要字段

字段名 备注说明
"name" Ability名称,对应Ability派生的Service类名。
"type" Ability类型,Service对应的Ability类型为”service“。
"visible" 指定能力是否可被其他应用调用,默认值为false,设置为true时,Service才能与其他应用进行调用。
启动Service

Ability为开发者提供了startAbility()方法来启动另外一个Ability,因为Service也是Ability的一种,开发者同样可以通过将want传递给该方法来启动Service,通过构造包含bundleName与abilityName的Want对象来设置目标Service信息。

启动本地设备Service示例如下:

import featureAbility from '@ohos.ability.featureAbility';

featureAbility.startAbility({
    want:
        {
            bundleName: "com.samples.myapplication",
            // FA模型abilityName由config.json中的package + Ability name组成
            abilityName: "com.samples.myapplication.ServiceAbility",
        }
    }
); 

执行上述代码后,Ability将通过startAbility()方法来启动Service。

  • 如果Service尚未运行,则系统会先调用onStart()来初始化Service,再回调Service的onCommand()方法来启动Service。
  • 如果Service正在运行,则系统会直接回调Service的onCommand()方法来启动Service。
连接Service

如果Service需要与Page Ability或其他应用的Service Ability进行交互,则须创建用于连接的Connection。Service支持其他Ability通过connectAbility()方法将当前Ability连接到指定ServiceAbility。在使用connectAbility()处理回调时,需要传入目标Service的Want与ConnectOptions远程对象实例。

ConnectOptions远程对象实例提供了以下方法:

  • onConnect():处理连接Service成功的回调。
  • onDisconnect():处理Service异常死亡的回调。
  • onFailed():处理连接Service失败的回调。

创建连接本地Service回调实例以及连接本地Service的示例如下:

import prompt from '@ohos.prompt';
import featureAbility from '@ohos.ability.featureAbility';
// 导入rpc进程通信模块
import rpc from "@ohos.rpc";

connectService() {
    // ConnectOptions远程对象实例
    let connectOptions = {
        // 连接成功时回调
        onConnect: function onConnectCallback(element, proxy) {
            console.log(`onConnectLocalService onConnectDone element:${element}`)
            if (proxy === null) {
                prompt.showToast({
                    message: 'Connect service failed'
                })
                return
            }
            // 携带客户端调用参数的MessageParcel对象
            let data = rpc.MessageParcel.create()
            // 接收应答数据的MessageParcel对象
            let reply = rpc.MessageParcel.create()
            // 本次请求的同异步模式,默认为TF_SYNC同步调用
            let option = new rpc.MessageOption()
            // 将字符串值写入MessageParcel实例
            data.writeString("connect")
            // 以同步模式向服务端发送MessageParcel消息,发送的消息代码为1,同步模式下该接口将等待服务端的响应,直到请求超时,响应的结果在reply中
            proxy.sendRequest(1, data, reply, option).then((result) => {
                console.log(`sendRequest: ${result}`);
                // 客户端以reply从MessageParcel实例中读取服务端处理后的字符串值
                let msg = reply.readString();
                console.log(`sendRequest msg: ${msg}`);
            }).catch((err) => {
                console.log(`sendRequest error: ${err}`);
            });
            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"
            })
        }
    }
    // want参数
    let want = {
        bundleName: 'com.samples.myapplication',
        // FA模型abilityName由config.json中的package + Ability name组成
        abilityName: 'com.samples.myapplication.ServiceAbility'
    };
    // 连接本地Service
    featureAbility.connectAbility(want, connectOptions);
}

同时,Service服务端也需要在onConnect()时返回IRemoteObject,从而定义与Service进行通信的接口。OpenHarmony提供了IRemoteObject的默认实现,可以通过继承rpc.RemoteObject来创建自定义的实现类。

Service服务端把自身的实例返回给调用侧的代码示例如下:

// service.ts
import rpc from '@ohos.rpc';
import hilog from '@ohos.hilog';

// 实现远程对象,服务提供者必须继承RemoteObject类
class FirstServiceAbilityStub extends rpc.RemoteObject {
    // 用于创建RemoteObject实例的构造函数,参数des为指定接口描述符
    constructor(des: any) {
        super(des)
    }
    // sendRequest请求的响应处理函数,服务端在该函数里处理请求,回复结果
    onRemoteRequest(code, data, reply, option) {
        console.log(`onRemoteRequest called`)
        // 判断客户端发送过来的消息代码是否为1
        if (code === 1) {
            // 从MessageParcel实例中读取由客户端写入的字符串值
            let string = data.readString()
            console.log(`string=${string}`)
            // 将读取的字符串作拆分和排序处理
            let result = Array.from(string).sort().join('')
            console.log(`result=${result}`)
            // 将处理后的字符串值写入MessageParcel实例
            reply.writeString(result)
        } else {
            console.log(`unknown request code`)
        }
        return true;
    }
};

export default {
    onStart() {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onStart');
    },
    onStop() {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onStop');
    },
    onCommand(want, startId) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onCommand');
    },
    onConnect(want) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility onConnect');
        // 返回RemoteObject对象与描述符
        return new FirstServiceAbilityStub('first ts service stub');
    },
    onDisconnect(want) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'ServiceAbility OnDisConnect');
    }
};

DataAbility

基于Data模板的Ability(以下简称“Data”),有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法,但不提供用户UI交互界面。Data既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。

Data提供方可以自定义数据的增、删、改、查,以及文件打开等功能,并对外提供这些接口。

接口说明

Data中相关生命周期API功能介绍

接口名 描述
onInitialized?(info: AbilityInfo): void 在Ability初始化调用,通过此回调方法执行RDB等初始化操作。
update?(uri: string, valueBucket: rdb.ValuesBucket, predicates: dataAbility.DataAbilityPredicates, callback: AsyncCallback<number>): void 更新数据库中的数据。
query?(uri: string, columns: Array<string>, predicates: dataAbility.DataAbilityPredicates, callback: AsyncCallback<ResultSet>): void 查询数据库中的数据。
delete?(uri: string, predicates: dataAbility.DataAbilityPredicates, callback: AsyncCallback<number>): void 删除一条或多条数据。
normalizeUri?(uri: string, callback: AsyncCallback<string>): void 对uri进行规范化。一个规范化的uri可以支持跨设备使用、持久化、备份和还原等,当上下文改变时仍然可以引用到相同的数据项。
batchInsert?(uri: string, valueBuckets: Array<rdb.ValuesBucket>, callback: AsyncCallback<number>): void 向数据库中插入多条数据。
denormalizeUri?(uri: string, callback: AsyncCallback<string>): void 将一个由normalizeUri生产的规范化URI转换成非规范化的uri。
insert?(uri: string, valueBucket: rdb.ValuesBucket, callback: AsyncCallback<number>): void 向数据中插入一条数据。
openFile?(uri: string, mode: string, callback: AsyncCallback<number>): void 打开一个文件。
getFileTypes?(uri: string, mimeTypeFilter: string, callback: AsyncCallback<Array<string>>): void 获取文件的MIME类型。
getType?(uri: string, callback: AsyncCallback<string>): void 获取uri指定数据相匹配的MIME类型。
executeBatch?(ops: Array<DataAbilityOperation>, callback: AsyncCallback<Array<DataAbilityResult>>): void 批量操作数据库中的数据。
call?(method: string, arg: string, extras: PacMap, callback: AsyncCallback<PacMap>): void 自定义方法。
创建与注册Data
  1. 在工程中,选择File->New->Ability->Data Ability,点击Finish,成功创建DataAbility,默认生成data.ts文件。

  1. 在data.ts文件中,实现Data的insert、query、update、delete等相关接口的业务内容,保证能够满足数据库存储业务的基本需求。batchInsert与executeBatch接口在系统中实现遍历逻辑,依赖insert、query、update、delete接口逻辑,来实现数据的批量处理。

创建Data示例如下:

// data.ts
import hilog from '@ohos.hilog';
import featureAbility from '@ohos.ability.featureAbility';
// 导入DataAbility谓词相关模块
import dataAbility from '@ohos.data.dataAbility';
// 导入rdb关系数据库模块
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)';
// rdb库对象
let rdbStore: dataRdb.RdbStore = undefined;

export default {
    // 初始化库和表
    onInitialized(abilityInfo) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'DataAbility onInitialized');
        // 获取应用上下文
        let context = featureAbility.getContext();
        // 获取一个相关的RdbStore,操作关系型数据库
        dataRdb.getRdbStore(context, STORE_CONFIG, 1).then((store) => {
            console.info('DataAbility getRdbStore callback');
            // 执行包含指定参数但不返回值的SQL语句
            store.executeSql(SQL_CREATE_TABLE, null);
            // 把获得的数据库存入本地变量,防止后面多次操作读取创建
            rdbStore = store;
        });
    },
    // 向数据库中插入一条数据
    insert(uri, valueBucket, callback) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'DataAbility insert');
        rdbStore.insert(TABLE_NAME, valueBucket, callback);
    },
    // 向数据库中插入多条数据
    batchInsert(uri, valueBuckets, callback) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'DataAbility batchInsert');
        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) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'DataAbility query');
        let rdbPredicates = dataAbility.createRdbPredicates(TABLE_NAME, predicates);
        rdbStore.query(rdbPredicates, columns, callback);
    },
    // 更新数据库中的数据
    update(uri, valueBucket, predicates, callback) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'DataAbility update');
        let rdbPredicates = dataAbility.createRdbPredicates(TABLE_NAME, predicates);
        rdbStore.update(valueBucket, rdbPredicates, callback);
    },
    // 删除一条或多条数据
    delete(uri, predicates, callback) {
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'DataAbility delete');
        let rdbPredicates = dataAbility.createRdbPredicates(TABLE_NAME, predicates);
        rdbStore.delete(rdbPredicates, callback);
    }
};
  1. 注册Data。FA模型在创建DataAbility时已在config.json中自动注册,注册类型type为data,visible字段需手动添加并设置为true。注册后会生成默认权限defPermissions字段,表明调用Data之前须配置这些权限,添加reqPermissions字段设置相应权限。

config.json配置样例

"module": {
    "package": "com.samples.myapplication",
    ...
    "abilities": [
        ... 
        {
            "name": ".DataAbility",
            "srcLanguage": "ets",
            "srcPath": "DataAbility",
            "icon": "$media:icon",
            "description": "$string:DataAbility_desc",
            "type": "data",
            "uri": "dataability://com.samples.myapplication.DataAbility",
            "visible": true
        }
    ],
    ...
    "defPermissions": [
        {
            "name": "com.example.entry.DataAbilityShellProvider.PROVIDER"
        }
    ],
    "reqPermissions": [
        {
            "name": "com.example.entry.DataAbilityShellProvider.PROVIDER"
        }
    ]
    ...
}

Json重要字段

字段名 备注说明
"name" Ability名称,对应Ability派生的Data类名。
"type" Ability类型,Data对应的Ability类型为”data“。
"uri" 通信使用的uri。URL后缀由"package"字段和"name"字段组成。
"visible" 指定能力是否可被其他应用调用,默认值为false,设置为true时,Data才能与其他应用进行通信传输数据。
"defPermissions" 表示应用程序默认需要的权限,调用者在调用Data之前必须获取相应权限。
访问Data

需导入基础模块,以及获取与Data子模块通信的uri字符串。基础模块包括:

  • @ohos.ability.featureAbility
  • @ohos.data.dataability
  • @ohos.data.rdb
  1. 创建工具接口类对象。
import featureAbility from '@ohos.ability.featureAbility';
import dataAbility from '@ohos.data.dataAbility';
import dataRdb from '@ohos.data.rdb';

// 定义数据访问uri常量
// 注意:作为参数传递的uri,比config.json中定义的uri多了一个"/",因为在第二个与第三个"/"中间,存在一个DeviceID的参数
let BASE_URI = "dataability:///com.example.entry.DataAbility";
// 获取dataAbilityHelper,用来协助其他Ability访问DataAbility的工具类。
let DA_HELPER = featureAbility.acquireDataAbilityHelper(BASE_URI);
// 定义表的列名
const COLUMNS = ['id', 'name', 'introduction'];
// 获取谓词
let predicates = new dataAbility.DataAbilityPredicates();
  1. 构建数据库相关的RDB数据。
// 创建一个BookModel数据模型
export class BookModel {
  id: number
  name: string
  introduction: string

  constructor(id: number, name: string, introduction: string) {
    this.id = id;
    this.name = name;
    this.introduction = introduction;
  }
}

//初始化测试数据
export function initValuesBuckets() {
  let valuesBuckets = [
    { 'name': 'Book name1', 'introduction': 'Book introduction1' },
    { 'name': 'Book name2', 'introduction': 'Book introduction2' },
    { 'name': 'Book name3', 'introduction': 'Book introduction3' }];
  return valuesBuckets;
}

// 将数据转换为BookModel数组,用于处理DA_HELPER返回的数据。
// 获取一个BookModel的数据列表
export function getListFromResultSet(resultSet) {
  let bookList = [];
  for (let i = 0;i < resultSet.rowCount; i++) {
    //按照构造函数进行封装
    let book = new BookModel(
        resultSet.getDouble(resultSet.getColumnIndex('id'))
      , resultSet.getString(resultSet.getColumnIndex('name'))
      , resultSet.getString(resultSet.getColumnIndex('introduction')));
    //添加到列表中
    bookList.push(book);
  }
  return bookList;
}

let valuesBucket = { 'name': 'Book name', 'introduction': 'Book introduction' };
let valuesBuckets = [
    { 'name': 'Book name1', 'introduction': 'Book introduction1' },
    { 'name': 'Book name2', 'introduction': 'Book introduction2' },
    { 'name': 'Book name3', 'introduction': 'Book introduction3' }
];
  1. 调用insert方法向指定的Data子模块插入数据。
let bookList: Array<BookModel> = [];

let valuesBucket = { name: 'Book name', introduction: 'Book introduction' };
DA_HELPER.insert(BASE_URI, valuesBucket, predicates).then((data) => {
    console.log("DAHelper insert result: " + data);
    bookList.push({ id: data, name: 'Book name', introduction: 'Book introduction' });
}).catch((err) => {
    console.log('DAHelper insert fail: ' + err);
});
  1. 调用delete方法删除Data子模块中指定的数据。
let book: BookModel = null;

predicates.equalTo('id', book.id);
DA_HELPER.delete(BASE_URI, predicates).then((data) => {
    console.log("DAHelper delete result: " + data);
}).catch((err) => {
    console.log('DAHelper delete fail: ' + err);
});
  1. 调用update方法更新指定Data子模块中的数据。
let book: BookModel = null;

predicates.equalTo('id', book.id);
let valuesBucket = {
    'name': book.name,
    'introduction': book.introduction
};
DA_HELPER.update(BASE_URI, valuesBucket, predicates).then((data) => {
    console.log("DAHelper update result: " + data);
}).catch((err) => {
    console.log('DAHelper update fail: ' + err);
});
  1. 调用query方法在指定的Data子模块中查找数据。
let bookList: Array<BookModel> = [];

//根据名字或介绍模糊匹配
predicates.contains('name', 'Book name1')
    .or()
    .contains('introduction', 'Book introduction1');
DA_HELPER.query(BASE_URI, COLUMNS, predicates).then((data) => {
    console.log("DAHelper query result: " + data);
    bookList = getListFromResultSet(resultSet);
}).catch((err) => {
    console.log('DAHelper query fail: ' + err);
});
  1. 调用batchInsert方法向指定的数据子模块批量插入数据。
let valuesBuckets = initValuesBuckets();

DA_HELPER.batchInsert(BASE_URI, valuesBuckets, predicates).then((data) => {
    console.log("DAHelper batchInsert result: " + data);
}).catch((err) => {
    console.log('DAHelper batchInsert fail: ' + err);
});

Stage模型

Ability开发指导

开发Stage模型应用时,需要在module.json5和app.json配置文件中对应用的包结构进行声明。基于Stage模型的Ability应用开发,主要涉及如下功能逻辑:

  • 创建支持使用屏幕浏览及人机交互的Ability应用,包括实现Ability的生命周期、获取Ability配置信息、向用户申请授权及环境变化通知等场景。
  • 启动Ability应用,包括相同设备启动Ability、跨设备启动Ability以及指定页面启动Ability等场景。
  • 通用组件Call功能。
  • 连接、断开ServiceExtensionAbility。
  • 应用迁移。
创建Ability
  1. 点击Create Project,选择OpenHarmony下的Empty Ability模板,点击Next进行下一步配置。在工程配置项中, "Compile API"选择"9","Model"默认为"Stage",其他参数可保持默认设置。点击Finish,工具会自动生成示例代码和相关资源,等待工程创建成功。

  1. 工程创建成功后生成默认的Ability,名为MainAbility。

启动Ability

应用可以通过this.context获取Ability实例的上下文,进而使用AbilityContext中的StartAbility相关接口启动Ability。启动Ability由Wan传递bundleName和abilityName属性,通过callback形式或promise形式实现。

  • bundleName:表示包名称。可在AppScope->app.json5中查看bundleName,或者通过File->Project Structure查看。
  • abilityName:表示待启动的Ability名称。

启动本地设备Ability示例如下:

// MainAbility.ts
import hilog from '@ohos.hilog';
import Ability from '@ohos.application.Ability';
import Window from '@ohos.window';

export default class MainAbility extends Ability {
    ...
    onWindowStageCreate(windowStage: Window.WindowStage) {
        // Main window is created, set main page for this ability
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

        // want参数配置
        let want = {
            bundleName: "com.samples.myapplication",
            abilityName: "MainAbility"
        };
        // 拉起指定的Ability
        this.context.startAbility(want).then((data) => {
            console.log("Succeed to start ability with data: " + JSON.stringify(data))
        }).catch((error) => {
            console.error("Failed to start ability with error: " + JSON.stringify(error))
        });

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

ExtensionAbility机制

ExtensionAbility是Stage模型中新增的扩展组件的基类,一般用于处理无界面的任务,生命周期较简单,没有前后台生命周期。

不同于用于页面展示的Ability,ExtensionAbility提供的是一种受限的服务运行环境。ExtensionAbility具有如下特点:

  • 独立于主进程的单独进程运行,与主进程无IPC,共享一个存储沙箱。
  • 独立的Context提供基于业务场景的api能力。
  • 由系统触发创建,应用不能直接创建。
  • ExtensionAbility和进程的生命周期受系统管理。

ServiceExtensionAbility开发指导

ServiceExtensionAbility是ExtensionAbility的扩展类。开发者可以自定义类继承ServiceExtensionAbility,通过重写基类中相关生命周期方法,来做初始化、连接中、断开连接时相关业务逻辑操作。

接口说明

ServiceExtensionAbility中相关生命周期API功能介绍

接口名 描述
onCreate(want: Want): void 首次调用startAbility、connectAbility时触发,执行初始化业务逻辑操作。
onRequest(want: Want, startId: number): void 每次调用startAbility拉起服务都会触发,首次调用时startId为1,重复调用时startId会递增。
onConnect(want: Want): rpc.RemoteObject 调用connectAbility拉起服务时触发,重复调用不会再次触发,除非调用disconnectAbility解除绑定后再调用;onConnect返回一个进程通信类RemoteObject,用于和客户端进行通信。
onDestroy(): void 调用停止当前Ability的接口terminateSelf时触发,执行资源清理等操作。
onDisconnect(want: Want): void 调用disconnectAbility断开服务连接时触发,Extension如果是用connectAbility拉起的,并且已经没有其他应用绑定这个Extension,则会触发onDestroy生命周期销毁组件。
创建与注册ServiceExtensionAbility
  1. 在ets目录下选择New->Directory创建ServiceExtAbility目录,然后在ServiceExtAbility目录选择New->ts File创建ServiceExtAbility.ts文件。

  1. 在ServiceExtAbility.ts文件中自定义类继承ServiceExtensionAbility,重写基类回调函数。

说明:'@ohos.application.ServiceExtensionAbility'模块从SDK 3.2.5.5版本起,被提取到了full-sdk当中,使用时需手动下载并安装full-sdk。

// ServiceExtAbility.ts
import ServiceExtensionAbility from '@ohos.application.ServiceExtensionAbility';

export default 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 null;
    }
    onDisconnect(want) {
        console.log(`onDisconnect, want: ${want.abilityName}`);
    }
    onDestroy() {
        console.log(`onDestroy`);
    }
}
  1. 注册ServiceExtensionAbility。在配置文件module.json5中进行注册,手动添加"extensionAbilities"配置项,将注册类型type设置为service。

module.json5配置样例

"module": {
    ...
    "extensionAbilities": [
        {
            "name": "ServiceExtAbility",
            "srcEntrance": "./ets/ServiceExtAbility/ServiceExtAbility.ts",
            "icon": "$media:icon",
            "description": "service",
            "type": "service",
            "visible": true   
        }
    ]
    ...
}
启动ServiceExtensionAbility

应用通过this.context获取Ability实例的上下文,调用startAbility()方法来启动本地ServiceExtensionAbility,该方法的Want传递bundleName和abilityName属性。将this.context挂载到全局对象globalThis上,用于外部自定义方法调用AbilityContext中的startAbility接口。

启动本地设备ServiceExtensionAbility示例如下:

// index.ets
@Entry
@Component
struct Index {
    private context = getContext(this) as any;

    startService() {
        // want参数配置
        let want = {
            bundleName: "com.samples.myapplication",
            abilityName: "ServiceExtAbility"
        };
        // 拉起指定的ServiceExtensionAbility
        this.context.startAbility(want).then((data) => {
            console.log(`startAbility success: ${JSON.stringify(data)}`);
        }).catch((error) => {
            console.error(`startAbility failed: ${JSON.stringify(error)}`);
        })
    }

    build() {
        ...
    }
}
连接ServiceExtensionAbility

由this.context获取Ability实例的上下文,调用connectAbility方法连接本地ServiceExtensionAbility,由该方法的Want传递连接目标的bundleName和abilityName属性,ConnectOptions远程对象实例提供onConnect()、onDisconnect()和onFailed()方法,用于监听不同连接状态并回调。

创建连接本地ServiceExtensionAbility回调实例以及连接本地ServiceExtensionAbility的示例如下:

import prompt from '@ohos.prompt';
// 导入rpc进程通信模块
import rpc from '@ohos.rpc';

connectService() {
    // ConnectOptions远程对象实例
    let connectOptions = {
        // 连接成功时回调
        onConnect: function onConnectCallback(element, proxy) {
            console.log(`onConnectLocalService onConnectDone element:${element} proxy:${proxy}`);
            if (proxy === null) {
                prompt.showToast({
                    message: 'Connect service failed'
                })
                return;
            }
            // 携带客户端调用参数的MessageParcel对象
            let data = rpc.MessageParcel.create();
            // 接收应答数据的MessageParcel对象
            let reply = rpc.MessageParcel.create();
            // 本次请求的同异步模式,默认为TF_SYNC同步调用
            let option = new rpc.MessageOption();
            // 将两个整数值写入MessageParcel实例
            data.writeInt(100);
            data.writeInt(200);
            // 以同步模式向服务端发送MessageParcel消息,发送的消息代码为1,同步模式下该接口将等待服务端的响应,直到请求超时,响应的结果在reply中
            proxy.sendRequest(1, data, reply, option).then((result) => {
                console.log(`sendRequest: ${result}`);
                // 客户端以reply从MessageParcel实例中读取服务端处理后的整数值
                let msg = reply.readInt();
                console.log(`sendRequest msg: ${msg}`);
            }).catch((err) => {
                console.log(`sendRequest error: ${err}`);
            });
            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"
            })
        }
    }
    // want参数配置
    let want = {
        bundleName: 'com.samples.myapplication',
        abilityName: 'ServiceExtAbility'
    };
    // 连接指定的ServiceExtensionAbility
    this.context.connectAbility(want, connectOptions);
}

ServiceExtensionAbility服务端通过继承rpc.RemoteObject来创建自定义的实现类StubTest,在onConnect()中返回RemoteObject对象与描述符参数。在StubTest类中调用onRemoteRequest()方法响应客户端发送的请求,并对数据作处理。

Service服务端把自身的实例返回给调用侧的代码示例如下:

// ServiceExtAbility.ts
import ServiceExtensionAbility from '@ohos.application.ServiceExtensionAbility';
import rpc from '@ohos.rpc';

// 实现远程对象,服务提供者必须继承RemoteObject类
class StubTest extends rpc.RemoteObject {
    // 用于创建RemoteObject实例的构造函数,参数des为指定接口描述符
    constructor(des) {
        super(des);
    }
    // sendRequest请求的响应处理函数,服务端在该函数里处理请求,回复结果
    onRemoteRequest(code, data, reply, option) {
        console.log(`onRemoteRequest`);
        // 判断客户端发送过来的消息代码是否为1
        if (code === REQUEST_VALUE) {
            // 从MessageParcel实例中读取由客户端写入的整数值
            let dataFirst = data.readInt();
            let dataSecond = data.readInt();
            // 将读取的整数值相加并写入MessageParcel实例
            reply.writeInt(dataFirst + dataSecond);
            console.log(`onRemoteRequest: dataFirst: ${dataFirst}, dataSecond: ${dataSecond}`);
        }
        return true;
    }
}

export default 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}`);
        // 返回RemoteObject对象与描述符
        return new StubTest("test");
    }
    onDisconnect(want) {
        console.log(`onDisconnect, want: ${want.abilityName}`);
    }
    onDestroy() {
        console.log(`onDestroy`);
    }
}

Ability间的跳转

应用场景描述

实现Ability之间的跳转将调用startAbility()方法,此方法根据want参数传递Abillity名称,拉起指定的Ability,达到跳转效果。

说明:Ability跳转效果需在设备上验证。

FA模型Ability跳转

  1. 在工程中,选择File->New->Ability->Page Ability,新建一个PageAbility命名为MainAbilitySecond,并在其pages目录选择New->Page新建second.ets页面。目录结构如下:

  1. 在MainAbility/pages/index.ets中导入featureAbility模块。
import featureAbility from '@ohos.ability.featureAbility';
  1. 在MainAbility/pages/index.ets页面添加Button组件,给Butom添加文本、样式和点击事件,在点击事件中调用featureAbility.startAbility拉起指定的Ability实现跳转。startAbility中的want参数主要传递bundleName和abilityName,在want中添加parameters参数,设置路由url为指定页面,点击Button按钮即可跳转到MainAbilitySecond的指定页面。

说明:want中如果不添加parameters参数,则默认指向Ability的index页面。

// MainAbility/pages/index.ets
import featureAbility from '@ohos.ability.featureAbility';

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button() {
          Text('to MainAbilitySecond')
            .fontSize(25)
            .fontWeight(FontWeight.Bolder)
        }
        .height(60)
        .onClick(() => {
          let parameter = {
            want: {
              bundleName: "com.samples.myapplication",
              // FA模型abilityName由config.json中的package + Ability name组成
              abilityName: "com.samples.myapplication.MainAbilitySecond",
              parameters: {
                url: 'pages/second'
              }
            }
          }
          featureAbility.startAbility(parameter).then((data) => {
            console.info('startAbility successful. Data: ' + JSON.stringify(data))
          }).catch((error) => {
            console.error('startAbility failed. Cause: ' + JSON.stringify(error))
          })
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Stage模型Ability跳转

  1. 在工程中,选择File->New->Ability,新建一个Ability命名为MainAbilitySecond,在pages目录选择New->Page新建second.ets。目录结构如下:

  1. 将MainAbilitySecond.ts页面中loadContent方法加载的主页面改为pages/second,与MainAbility中加载的index主页面区分开。
// MainAbilitySecond.ts
import hilog from '@ohos.hilog';
import Ability from '@ohos.application.Ability'
import Window from '@ohos.window'

export default class MainAbilitySecond extends Ability {
    ...
    onWindowStageCreate(windowStage: Window.WindowStage) {
        // Main window is created, set main page for this ability
        hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
        hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

        // 默认指向pages/index,这里修改为pages/second
        windowStage.loadContent('pages/second', (err, data) => {
            if (err.code) {
                hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.ERROR);
                hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
                return;
            }
            hilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);
            hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
        });
    }
    ...
}
  1. 在MainAbility/pages/index.ets页面添加Button组件,给Butom添加文本、样式和点击事件,创建context对象获取应用上下文, 并调用startAbility拉起指定的Ability实现跳转。其中startAbility的want参数传递bundleName和abilityName。
// MainAbility/pages/index.ets
@Entry
@Component
struct Index {
  private context = getContext(this) as any;

  build() {
    Row() {
      Column() {
        Button() {
          Text('to MainAbilitySecond')
            .fontSize(25)
            .fontWeight(FontWeight.Bolder)
        }
        .height(60)
        .onClick(() => {
          let want = {
            bundleName: "com.samples.myapplication",
            abilityName: "MainAbilitySecond"
          }
          this.context.startAbility(want).then((data) => {
            console.info('startAbility successful. Data: ' + JSON.stringify(data))
          }).catch((error) => {
            console.error('startAbility failed. Cause: ' + JSON.stringify(error))
          })
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}

Ability生命周期

生命周期差异对比

FA模型与Stage模型的Ability生命周期存在差异:

生命周期接口说明

FA模型生命周期回调函数

接口名 描述
onCreate() Ability第一次启动创建Ability时调用onCreate方法,开发者可以在该方法里做一些应用初始化工作。
onActive() Ability切换到前台,并且已经获取焦点时调用onActive方法。
onInactive() Ability失去焦点时调用onInactive方法,Ability在进入后台状态时会先失去焦点,再进入后台。
onHide() Ability由前台切换到后台不可见状态时调用onHide方法,此时用户在屏幕看不到该Ability。
onShow() Ability由后台不可见状态切换到前台可见状态调用onShow方法,此时用户在屏幕可以看到该Ability。
onDestroy() 应用退出,销毁Ability对象前调用onDestroy方法,开发者可以在该方法里做一些回收资源、清空缓存等应用退出前的准备工作。

Stage模型生命周期回调函数

接口名 描述
onCreate(want: Want, param: AbilityConstant.LaunchParam): void Ability生命周期回调,Ability启动时被调用。
onWindowStageCreate(windowStage: window.WindowStage): void Ability生命周期回调,创建window stage时被调用,应用开发者可通过window.WindowStage的接口执行页面加载等操作。
onForeground(): void Ability生命周期回调,Ability切换至前台时被调用。
onBackground(): void Ability生命周期回调,Ability切换至后台时被调用。
onWindowStageDestroy(): void Ability生命周期回调,销毁window stage时被调用。
onDestroy(): void Ability生命周期回调,Ability销毁时被调用。

Ability启动模式

FA模型启动模式

FA模型Ability支持单实例和多实例两种启动模式,在config.json中通过launchType配置项,配置具体的启动模式;如果不配置,则默认为standard模式。

启动模式 描述 说明
singleton 单实例 系统中只存在唯一一个实例,startAbility时,如果已存在,则复用系统中的唯一一个实例。
standard 多实例 每次startAbility调用都会启动一个新的实例。

Stage模型启动模式

Stage模型Ability支持单实例、多实例和指定实例3种启动模式,在module.json5中默认没有launchType配置项,需手动添加;如果不添加,则默认为singleton模式。

启动模式 描述 说明
singleton 单实例 系统中只存在唯一一个实例,startAbility时,如果已存在,则复用系统中的唯一一个实例。
standard 多实例 每次startAbility调用都会启动一个新的实例。
specified 指定实例 运行时由ability内部业务决定是否创建多实例。

module.json5示例:

{
  "module": {
    "abilities": [
      {
        ...
        "launchType": "standard"
        ...
      }
    ]
  }
}

参考文献

[1] OpenHarmony Ability开发. https://gitee.com/openharmony/docs/tree/master/zh-cn/application-dev/ability

[2] OpenHarmony接口开发. https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/Readme-CN.md#/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-inputmethod-extension-ability.md

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部