一、Ability 概述
Ability 是应用所具备能力的抽象,也是应用程序的重要组成部分。一个应用可以具备多种能力(即可以包含多个 Ability),HarmonyOS 支持应用以 Ability 为单位进行部署。 Ability 可以分为 FA(Feature Ability)和 PA(Particle Ability)两种类型,每种类型为开发者提供了不同的模板,以便实现不同的业务功能。 FA 支持 Page Ability:Page 模板是 FA 唯一支持的模板,用于提供与用户交互的能力。一个 Page 实例可以包含一组相关页面,每个页面用一个 AbilitySlice 实例表示。 PA 支持 Service Ability 和 Data Ability:
Service 模板:用于提供后台运行任务的能力;
Data 模板:用于对外部提供统一的数据访问抽象。 在配置文件(config.json)中注册 Ability 时,可以通过配置 Ability 元素中的“type”属性来指定 Ability 模板类型,其中,“type”的取值可以为“page”、“service”或“data”,分别代表 Page 模板、Service 模板、Data 模板,如下所示:
{
"module" : {
. . .
"abilities" : [
{
. . .
"type" : "page"
. . .
}
]
. . .
}
. . .
}
二、Page Ability
① Page Ability 基本概念
Page 模板(以下简称“Page”)是 FA 唯一支持的模板,用于提供与用户交互的能力。 一个 Page 可以由一个或多个 AbilitySlice 构成,AbilitySlice 是指应用的单个页面及其控制逻辑的总和。 当一个 Page 由多个 AbilitySlice 共同构成时,这些 AbilitySlice 页面提供的业务能力应具有高度相关性。例如,新闻浏览功能可以通过一个 Page 来实现,其中包含了两个 AbilitySlice:一个 AbilitySlice 用于展示新闻列表,另一个 AbilitySlice 用于展示新闻详情。 Page 和 AbilitySlice 的关系下图所示:
相比于桌面场景,移动场景下应用之间的交互更为频繁。通常,单个应用专注于某个方面的能力开发,当它需要其他能力辅助时,会调用其他应用提供的能力。例如,外卖应用提供了联系商家的业务功能入口,当用户在使用该功能时,会跳转到通话应用的拨号页面。 与此类似,HarmonyOS 支持不同 Page 之间的跳转,并可以指定跳转到目标 Page 中某个具体的 AbilitySlice。 虽然一个 Page 可以包含多个 AbilitySlice,但是 Page 进入前台时界面默认只展示一个 AbilitySlice,默认展示的 AbilitySlice 是通过 setMainRoute() 方法来指定的。 如果需要更改默认展示的 AbilitySlice,可以通过 addActionRoute() 方法为此 AbilitySlice 配置一条路由规则,此时,当其它 Page 实例期望导航到此 AbilitySlice 时,可以在 Intent 中指定 Action。 setMainRoute() 方法与 addActionRoute() 方法的使用示例如下:
public class MyAbility extends Ability {
@Override
public void onStart ( Intent intent) {
super. onStart ( intent) ;
setMainRoute ( MainSlice. class. getName ( ) ) ;
addActionRoute ( "action.pay" , PaySlice. class. getName ( ) ) ;
addActionRoute ( "action.scan" , ScanSlice. class. getName ( ) ) ;
}
}
addActionRoute() 方法中使用的动作命名,需要在应用配置文件(config.json)中注册:
{
"module" : {
"abilities" : [
{
"skills" : [
{
"actions" : [
"action.pay" ,
"action.scan"
]
}
]
. . .
}
]
. . .
}
. . .
}
② Page Ability 生命周期
Page 生命周期的不同状态转换及其对应的回调,如下图所示:
onStart()
对于一个 Page 实例,该回调在其生命周期过程中仅触发一次,Page 在该逻辑后将进入 INACTIVE 状态。
开发者必须重写该方法,并在此配置默认展示的 AbilitySlice。
@Override
public void onStart ( Intent intent) {
super. onStart ( intent) ;
super. setMainRoute ( FooSlice. class. getName ( ) ) ;
}
onActive()
Page 会在进入INACTIVE状态后来到前台,然后系统调用此回调。
Page 在此之后进入 ACTIVE 状态,该状态是应用与用户交互的状态。Page 将保持在此状态,除非某类事件发生导致 Page 失去焦点,比如用户点击返回键或导航到其他 Page。当此类事件发生时,会触发 Page 回到 INACTIVE 状态,系统将调用 onInactive() 回调。此后,Page 可能重新回到 ACTIVE 状态,系统将再次调用 onActive() 回调。
因此,开发者通常需要成对实现 onActive() 和 onInactive(),并在 onActive() 中获取在 onInactive() 中被释放的资源。 onInactive()
当 Page 失去焦点时,系统将调用此回调,此后 Page 进入 INACTIVE状态。
开发者可以在此回调中实现 Page 失去焦点时应表现的恰当行为。 onBackground()
如果 Page 不再对用户可见,系统将调用此回调通知开发者用户进行相应的资源释放,此后 Page 进入 BACKGROUND 状态。
开发者应该在此回调中释放 Page 不可见时无用的资源,或在此回调中执行较为耗时的状态保存操作。 onForeground()
处于 BACKGROUND 状态的 Page 仍然驻留在内存中,当重新回到前台时(比如用户重新导航到此 Page),系统将先调用 onForeground() 回调通知开发者,而后 Page 的生命周期状态回到 INACTIVE 状态。
开发者应当在此回调中重新申请在 onBackground() 中释放的资源,最后 Page 的生命周期状态进一步回到 ACTIVE 状态,系统将通过 onActive() 回调通知开发者用户。 onStop()
系统将要销毁 Page 时,将会触发此回调函数,通知用户进行系统资源的释放。
用户通过系统管理能力关闭指定 Page,例如使用任务管理器关闭 Page。
用户行为触发 Page 的 terminateAbility() 方法调用,例如使用应用的退出功能。
系统出于资源管理目的,自动触发对处于 BACKGROUND 状态 Page 的销毁。 AbilitySlice 作为 Page 的组成单元,其生命周期是依托于其所属 Page 生命周期的。AbilitySlice 和 Page 具有相同的生命周期状态和同名的回调,当 Page 生命周期发生变化时,它的 AbilitySlice 也会发生相同的生命周期变化。 此外,AbilitySlice 还具有独立于 Page 的生命周期变化,这发生在同一 Page 中的 AbilitySlice 之间导航时,此时 Page 的生命周期状态不会改变。 AbilitySlice 生命周期回调与 Page 的相应回调类似,因此不再赘述。由于 AbilitySlice 承载具体的页面,开发者必须重写 AbilitySlice的onStart() 回调,并在此方法中通过 setUIContent() 方法设置页面,如下所示:
@Override
protected void onStart ( Intent intent) {
super. onStart ( intent) ;
setUIContent ( ResourceTable. Layout_main_layout) ;
}
AbilitySlice 实例创建和管理通常由应用负责,系统仅在特定情况下会创建 AbilitySlice 实例。例如,通过导航启动某个 AbilitySlice 时,是由系统负责实例化;但是在同一个 Page 中不同的 AbilitySlice 间导航时则由应用负责实例化。 当 AbilitySlice 处于前台且具有焦点时,其生命周期状态随着所属 Page 的生命周期状态的变化而变化。当一个 Page 拥有多个 AbilitySlice 时,例如:MyAbility 下有 FooAbilitySlice 和 BarAbilitySlice,当前 FooAbilitySlice 处于前台并获得焦点,并即将导航到 BarAbilitySlice,在此期间的生命周期状态变化顺序为:
FooAbilitySlice 从 ACTIVE 状态变为 INACTIVE 状态;
BarAbilitySlice 则从 INITIAL 状态首先变为 INACTIVE 状态,然后变为 ACTIVE 状态(假定此前 BarAbilitySlice 未曾启动);
FooAbilitySlice 从 INACTIVE 状态变为 BACKGROUND 状态。 对应两个slice的生命周期方法回调顺序为: FooAbilitySlice.onInactive() --> BarAbilitySlice.onStart() --> BarAbilitySlice.onActive() --> FooAbilitySlice.onBackground() 在整个流程中,MyAbility 始终处于 ACTIVE 状态。但是,当 Page 被系统销毁时,其所有已实例化的 AbilitySlice 将联动销毁,而不仅是处于前台的 AbilitySlice。
③ AbilitySlice 间导航
同一 Page 内导航:当发起导航的 AbilitySlice 和导航目标的 AbilitySlice 处于同一个 Page 时,您可以通过 present() 方法实现导航。如下代码片段展示通过点击按钮导航到其他 AbilitySlice 的方法:
@Override
protected void onStart ( Intent intent) {
. . .
Button button = . . . ;
button. setClickedListener ( listener -> present ( new TargetSlice ( ) , new Intent ( ) ) ) ;
. . .
}
如果开发者希望在用户从导航目标 AbilitySlice 返回时,能够获得其返回结果,则应当使用 presentForResult() 实现导航。用户从导航目标 AbilitySlice 返回时,系统将回调 onResult() 来接收和处理返回结果,开发者需要重写该方法。返回结果由导航目标 AbilitySlice 在其生命周期内通过 setResult() 进行设置。
@Override
protected void onStart ( Intent intent) {
. . .
Button button = . . . ;
button. setClickedListener ( listener -> presentForResult ( new TargetSlice ( ) , new Intent ( ) , 0 ) ) ;
. . .
}
@Override
protected void onResult ( int requestCode, Intent resultIntent) {
if ( requestCode == 0 ) {
}
}
系统为每个 Page 维护了一个 AbilitySlice 实例的栈,每个进入前台的 AbilitySlice 实例均会入栈。当开发者在调用 present() 或 presentForResult() 时指定的 AbilitySlice 实例已经在栈中存在时,则栈中位于此实例之上的 AbilitySlice 均会出栈并终止其生命周期。 前面的示例代码中,导航时指定的 AbilitySlice 实例均是新建的,即便重复执行此代码(此时作为导航目标的这些实例是同一个类),也不会导致任何 AbilitySlice 出栈。 不同 Page 间导航:AbilitySlice 作为 Page 的内部单元,以 Action 的形式对外暴露,因此可以通过配置 Intent 的 Action 导航到目标 AbilitySlice。Page 间的导航可以使用 startAbility() 或 startAbilityForResult() 方法,获得返回结果的回调为 onAbilityResult()。在 Ability 中调用 setResult() 可以设置返回结果。
④ 跨设备迁移
跨设备迁移(下文简称“迁移”)支持将 Page 在同一用户的不同设备间迁移,以便支持用户无缝切换的诉求。 以 Page 从设备 A 迁移到设备 B 为例,迁移动作主要步骤如下:
HarmonyOS 处理迁移任务,并回调设备 A 上 Page 的保存数据方法,用于保存迁移必须的数据;
HarmonyOS 在设备 B 上启动同一个 Page,并回调其恢复数据方法。 实现 IAbilityContinuation 接口:
onStartContinuation():Page 请求迁移后,系统首先回调此方法,开发者可以在此回调中决策当前是否可以执行迁移,比如,弹框让用户确认是否开始迁移;
onSaveData():如果 onStartContinuation() 返回 true,则系统回调此方法,开发者在此回调中保存必须传递到另外设备上以便恢复 Page 状态的数据。
onRestoreData():源侧设备上 Page 完成保存数据后,系统在目标侧设备上回调此方法,开发者在此回调中接受用于恢复 Page 状态的数据。注意,在目标侧设备上的Page会重新启动其生命周期,无论其启动模式如何配置。且系统回调此方法的时机在 onStart() 之前。
onCompleteContinuation():目标侧设备上恢复数据一旦完成,系统就会在源侧设备上回调 Page 的此方法,以便通知应用迁移流程已结束。开发者可以在此检查迁移结果是否成功,并在此处理迁移结束的动作,例如,应用可以在迁移完成后终止自身生命周期。
onRemoteTerminated():如果开发者使用 continueAbilityReversibly() 而不是 continueAbility(),则此后可以在源侧设备上使用 reverseContinueAbility() 进行回迁。这种场景下,相当于同一个 Page(的两个实例)同时在两个设备上运行,迁移完成后,如果目标侧设备上 Page 因任何原因终止,则源侧 Page 通过此回调接收终止通知。 请求迁移:
实现 IAbilityContinuation 的 Page 可以在其生命周期内,调用 continueAbility() 或 continueAbilityReversibly() 请求迁移。两者的区别是,通过后者发起的迁移此后可以进行回迁。
try {
continueAbility ( ) ;
} catch ( IllegalStateException e) {
. . .
}
以 Page 从设备 A 迁移到设备 B 为例,详细的流程如下:
系统回调设备 A 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onStartContinuation() 方法,以确认当前是否可以立即迁移。
如果可以立即迁移,则系统回调设备 A 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onSaveData() 方法,以便保存迁移后恢复状态必须的数据。
如果保存数据成功,则系统在设备 B 上启动同一个 Page,并恢复 AbilitySlice 栈,然后回调 IAbilityContinuation.onRestoreData() 方法,传递此前保存的数据;此后设备 B 上此 Page从onStart() 开始其生命周期回调。
系统回调设备 A 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onCompleteContinuation() 方法,通知数据恢复成功与否。 请求回迁
使用 continueAbilityReversibly() 请求迁移并完成后,源侧设备上已迁移的 Page 可以发起回迁,以便使用户活动重新回到此设备。
try {
reverseContinueAbility ( ) ;
} catch ( IllegalStateException e) {
. . .
}
以 Page 从设备 A 迁移到设备 B 后并请求回迁为例,详细的流程如下:
系统回调设备 B 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onStartContinuation() 方法,以确认当前是否可以立即迁移;
如果可以立即迁移,则系统回调设备 B 上 Page 及其 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onSaveData() 方法,以便保存回迁后恢复状态必须的数据;
如果保存数据成功,则系统在设备 A 上 Page 恢复 AbilitySlice 栈,然后回调 IAbilityContinuation.onRestoreData() 方法,传递此前保存的数据;
如果数据恢复成功,则系统终止设备 B 上 Page 的生命周期。 参考示例:Page模板的Ability与用户交互的能力 。
三、Service Ability
① Service Ability 基本概念
基于 Service 模板的 Ability(以下简称“Service”)主要用于后台运行任务(如执行音乐播放、文件下载等),但不提供用户交互界面。 Service 可由其他应用或Ability启动,即使用户切换到其他应用,Service 仍将在后台继续运行。 Service 是单实例的,在一个设备上,相同的 Service 只会存在一个实例。如果多个 Ability 共用这个实例,只有当与 Service 绑定的所有 Ability 都退出后,Service 才能够退出。 由于 Service 是在主线程里执行的,因此,如果在 Service 里面的操作时间过长,开发者必须在 Service 里创建新的线程来处理,防止造成主线程阻塞,应用程序无响应。
② 创建 Service
创建 Ability 的子类,实现 Service 相关的生命周期方法。Service 也是一种 Ability,Ability 为 Service 提供了以下生命周期方法,用户可以重写这些方法,来添加其他 Ability 请求与 Service Ability 交互时的处理方法。
onStart():该方法在创建 Service 的时候调用,用于 Service 的初始化。在 Service 的整个生命周期只会调用一次,调用时传入的 Intent 应为空。
onCommand():在 Service 创建完成之后调用,该方法在客户端每次启动该 Service 时都会调用,用户可以在该方法中做一些调用统计、初始化类的操作。
onConnect():在 Ability 和 Service 连接时调用,该方法返回 IRemoteObject 对象,用户可以在该回调函数中生成对应 Service 的 IPC 通信通道,以便 Ability 与 Service 交互。Ability 可以多次连接同一个 Service,系统会缓存该 Service 的 IPC 通信对象,只有第一个客户端连接 Service 时,系统才会调用 Service 的 onConnect 方法来生成 IRemoteObject 对象,而后系统会将同一个 RemoteObject 对象传递至其他连接同一个 Service 的所有客户端,而无需再次调用 onConnect 方法。
onDisconnect():在 Ability 与绑定的 Service 断开连接时调用。
onStop():在 Service 销毁时调用,Service 应通过实现此方法来清理任何资源,如关闭线程、注册的侦听器等。 创建 Service 的代码示例如下:
public class ServiceAbility extends Ability {
@Override
public void onStart ( Intent intent) {
super. onStart ( intent) ;
}
@Override
public void onCommand ( Intent intent, boolean restart, int startId) {
super. onCommand ( intent, restart, startId) ;
}
@Override
public IRemoteObject onConnect ( Intent intent) {
return super. onConnect ( intent) ;
}
@Override
public void onDisconnect ( Intent intent) {
super. onDisconnect ( intent) ;
}
@Override
public void onStop ( ) {
super. onStop ( ) ;
}
}
注册 Service:Service 也需要在应用配置文件中进行注册,注册类型 type 需要设置为 service:
{
"module" : {
"abilities" : [
{
"name" : ".ServiceAbility" ,
"type" : "service" ,
"visible" : true
. . .
}
]
. . .
}
. . .
}
③ 启动 Service
Ability 为开发者提供了 startAbility() 方法来启动另外一个 Ability,因为 Service 也是 Ability 的一种,开发者同样可以通过将 Intent 传递给该方法来启动 Service。不仅支持启动本地 Service,还支持启动远程 Service。 开发者可以通过构造包含 DeviceId、BundleName 与 AbilityName 的 Operation 对象来设置目标 Service 信息,这三个参数的含义如下:
DeviceId:表示设备 ID,如果是本地设备,则可以直接留空;如果是远程设备,可以通过 ohos.distributedschedule.interwork.DeviceManager 提供的 getDeviceList 获取设备列表;
AbilityName:表示待启动的 Ability 名称。 启动本地设备 Service 的代码示例如下:
Intent intent = new Intent ( ) ;
Operation operation = new Intent. OperationBuilder ( )
. withDeviceId ( "" )
. withBundleName ( "com.domainname.hiworld.himusic" )
. withAbilityName ( "com.domainname.hiworld.himusic.ServiceAbility" )
. build ( ) ;
intent. setOperation ( operation) ;
startAbility ( intent) ;
Intent intent = new Intent ( ) ;
Operation operation = new Intent. OperationBuilder ( )
. withDeviceId ( "deviceId" )
. withBundleName ( "com.domainname.hiworld.himusic" )
. withAbilityName ( "com.domainname.hiworld.himusic.ServiceAbility" )
. withFlags ( Intent. FLAG_ABILITYSLICE_MULTI_DEVICE)
. build ( ) ;
intent. setOperation ( operation) ;
startAbility ( intent) ;
执行上述代码后,Ability 将通过 startAbility() 方法来启动 Service。如果 Service 尚未运行,则系统会先调用 onStart() 来初始化 Service,再回调 Service 的 onCommand() 方法来启动 Service。如果 Service 正在运行,则系统会直接回调 Service 的 onCommand() 方法来启动 Service。 Service 一旦创建就会一直保持在后台运行,除非必须回收内存资源,否则系统不会停止或销毁 Service。开发者可以在 Service 中通过 terminateAbility() 停止本 Service 或在其他 Ability 调用 stopAbility() 来停止 Service。 停止 Service 同样支持停止本地设备 Service 和停止远程设备 Service,使用方法与启动 Service 一样。一旦调用停止 Service 的方法,系统便会尽快销毁 Service。
④ 链接 Service
如果 Service 需要与 Page Ability 或其他应用的 Service Ability 进行交互,则须创建用于连接的 Connection。Service 支持其他 Ability 通过 connectAbility() 方法与其进行连接。 在使用 connectAbility() 处理回调时,需要传入目标 Service 的 Intent 与 IAbilityConnection 的实例。IAbilityConnection 提供了两个方法供开发者实现:onAbilityConnectDone() 是用来处理连接 Service 成功的回调, onAbilityDisconnectDone() 是用来处理 Service 异常死亡的回调。 创建连接 Service 回调实例的代码示例如下:
private IAbilityConnection connection = new IAbilityConnection ( ) {
@Override
public void onAbilityConnectDone ( ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
}
@Override
public void onAbilityDisconnectDone ( ElementName elementName, int resultCode) {
}
} ;
Intent intent = new Intent ( ) ;
Operation operation = new Intent. OperationBuilder ( )
. withDeviceId ( "deviceId" )
. withBundleName ( "com.domainname.hiworld.himusic" )
. withAbilityName ( "com.domainname.hiworld.himusic.ServiceAbility" )
. build ( ) ;
intent. setOperation ( operation) ;
connectAbility ( intent, connection) ;
同时,Service 侧也需要在 onConnect() 时返回 IRemoteObject,从而定义与 Service 进行通信的接口。onConnect() 需要返回一个 IRemoteObject 对象,HarmonyOS 提供了 IRemoteObject 的默认实现,用户可以通过继承 LocalRemoteObject 来创建自定义的实现类。 Service 侧把自身的实例返回给调用侧的代码示例如下:
private class MyRemoteObject extends LocalRemoteObject {
MyRemoteObject ( ) {
}
}
@Override
protected IRemoteObject onConnect ( Intent intent) {
return new MyRemoteObject ( ) ;
}
⑤ Service Ability 生命周期
与 Page 类似,Service 也拥有生命周期,如下图所示:
根据调用方法的不同,其生命周期有以下两种路径:
启动 Service:该 Service 在其他 Ability 调用 startAbility() 时创建,然后保持运行。其他 Ability 通过调用 stopAbility() 来停止 Service,Service 停止后,系统会将其销毁。
连接 Service:该 Service 在其他 Ability 调用 connectAbility() 时创建,客户端可通过调用 disconnectAbility() 断开连接。多个客户端可以绑定到相同 Service,而且当所有绑定全部取消后,系统即会销毁该 Service。
⑥ 前台 Service
一般情况下,Service都是在后台运行的,后台 Service 的优先级都是比较低的,当资源不足时,系统有可能回收正在运行的后台 Service。 在一些场景下(如播放音乐),用户希望应用能够一直保持运行,此时就需要使用前台 Service,前台 Service 会始终保持正在运行的图标在系统状态栏显示。 使用前台 Service 并不复杂,开发者只需在 Service 创建的方法里,调用 keepBackgroundRunning() 将 Service 与通知绑定。调用 keepBackgroundRunning() 方法前需要在配置文件中声明 ohos.permission.KEEP_BACKGROUND_RUNNING 权限,同时还需要在配置文件中添加对应的 backgroundModes 参数。在 onStop() 方法中调用 cancelBackgroundRunning() 方法可停止前台 Service。 使用前台 Service的onStart() 代码示例如下:
NotificationRequest request = new NotificationRequest ( 1005 ) ;
NotificationRequest. NotificationNormalContent content = new NotificationRequest. NotificationNormalContent ( ) ;
content. setTitle ( "title" ) . setText ( "text" ) ;
NotificationRequest. NotificationContent notificationContent = new NotificationRequest. NotificationContent ( content) ;
request. setContent ( notificationContent) ;
keepBackgroundRunning ( 1005 , request) ;
在配置文件中,“module > abilities”字段下对当前 Service 做如下配置:
{
"name" : ".ServiceAbility" ,
"type" : "service" ,
"visible" : true,
"backgroundModes" : [ "dataTransfer" , "location" ]
}
四、Data Ability
① Data Ability 基本概念
使用 Data 模板的 Ability(以下简称“Data”)有助于应用管理其自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。Data 既可用于同设备不同应用的数据共享,也支持跨设备不同应用的数据共享。 数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data 对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。 Data 的提供方和使用方都通过 URI(Uniform Resource Identifier)来标识一个具体的数据,例如数据库中的某个表或磁盘上的某个文件。 HarmonyOS 的 URI 仍基于 URI 通用标准,格式如下:
参数说明:
scheme:协议方案名,固定为“dataability”,代表 Data Ability 所使用的协议类型。
authority:设备 ID,如果为跨设备场景,则为目标设备的 ID;如果为本地设备场景,则不需要填写。
path:资源的路径信息,代表特定资源的位置信息。
② 创建 Data
确定数据的存储方式,Data 支持以下两种数据形式:
实现 UserDataAbility
UserDataAbility 用于接收其他应用发送的请求,提供外部程序访问的入口,从而实现应用间的数据访问。
实现 UserDataAbility,需要在“Project”窗口当前工程的主目录(“entry > src > main > java > com.xxx.xxx”)选择“File > New > Ability > Empty Data Ability”,设置“Data Name”后完成 UserDataAbility 的创建。 Data 提供了文件存储和数据库存储两组接口供用户使用。
开发者需要在 Data 中重写 FileDescriptor openFile(Uri uri, String mode)方法来操作文件:uri 为客户端传入的请求目标路径;mode为开发者对文件的操作选项,可选方式包含“r”(读), “w”(写), “rw”(读写)等。
ohos.rpc.MessageParcel 类提供了一个静态方法,用于获取 MessageParcel 实例。开发者可通过获取到的 MessageParcel 实例,使用 dupFileDescriptor() 函数复制待操作文件流的文件描述符,并将其返回,供远端应用访问文件。
private static final HiLogLabel LABEL_LOG = new HiLogLabel ( HiLog. LOG_APP, 0xD00201 , "Data_Log" ) ;
@Override
public FileDescriptor openFile ( Uri uri, String mode) throws FileNotFoundException {
MessageParcel messageParcel = MessageParcel. obtain ( ) ;
File file = new File ( uri. getDecodedPathList ( ) . get ( 0 ) ) ;
if ( mode == null || ! "rw" . equals ( mode) ) {
file. setReadOnly ( ) ;
}
FileInputStream fileIs = new FileInputStream ( file) ;
FileDescriptor fd = null;
try {
fd = fileIs. getFD ( ) ;
} catch ( IOException e) {
HiLog. info ( LABEL_LOG, "failed to getFD" ) ;
}
return messageParcel. dupFileDescriptor ( fd) ;
}
初始化数据库连接,系统会在应用启动时调用 onStart() 方法创建 Data 实例。在此方法中,开发者应该创建数据库连接,并获取连接对象,以便后续和数据库进行操作。为了避免影响应用启动速度,开发者应当尽可能将非必要的耗时任务推迟到使用时执行,而不是在此方法中执行所有初始化。
private static final String DATABASE_NAME = "UserDataAbility.db" ;
private static final String DATABASE_NAME_ALIAS = "UserDataAbility" ;
private static final HiLogLabel LABEL_LOG = new HiLogLabel ( HiLog. LOG_APP, 0xD00201 , "Data_Log" ) ;
private OrmContext ormContext = null;
@Override
public void onStart ( Intent intent) {
super. onStart ( intent) ;
DatabaseHelper manager = new DatabaseHelper ( this) ;
ormContext = manager. getOrmContext ( DATABASE_NAME_ALIAS, DATABASE_NAME, BookStore. class) ;
}
编写数据库操作方法,Ability 定义了6个方法供用户处理对数据库表数据的增删改查,这6个方法在 Ability 中已默认实现,开发者可按需重写:
方法 描述 ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) 查询数据库 int insert(Uri uri, ValuesBucket value) 向数据库中插入单条数据 int batchInsert(Uri uri, ValuesBucket[] values) 向数据库中插入多条数据 int delete(Uri uri, DataAbilityPredicates predicates) 删除一条或多条数据 int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) 更新数据库 DataAbilityResult[] executeBatch(ArrayList operations) 批量操作数据库
query():该方法接收三个参数,分别是查询的目标路径,查询的列名,以及查询条件,查询条件由类 DataAbilityPredicates 构建。根据传入的列名和查询条件查询用户表的代码示例如下:
public ResultSet query ( Uri uri, String[ ] columns, DataAbilityPredicates predicates) {
if ( ormContext == null) {
HiLog. error ( LABEL_LOG, "failed to query, ormContext is null" ) ;
return null;
}
OrmPredicates ormPredicates = DataAbilityUtils. createOrmPredicates ( predicates, User. class) ;
ResultSet resultSet = ormContext. query ( ormPredicates, columns) ;
if ( resultSet == null) {
HiLog. info ( LABEL_LOG, "resultSet is null" ) ;
}
return resultSet;
}
insert():该方法接收两个参数,分别是插入的目标路径和插入的数据值。其中,插入的数据由 ValuesBucket 封装,服务端可以从该参数中解析出对应的属性,然后插入到数据库中。此方法返回一个 int 类型的值用于标识结果。接收到传过来的用户信息并把它保存到数据库中的代码示例如下:
public int insert ( Uri uri, ValuesBucket value) {
if ( ormContext == null) {
HiLog. error ( LABEL_LOG, "failed to insert, ormContext is null" ) ;
return - 1 ;
}
User user = new User ( ) ;
user. setUserId ( value. getInteger ( "userId" ) ) ;
user. setFirstName ( value. getString ( "firstName" ) ) ;
user. setLastName ( value. getString ( "lastName" ) ) ;
user. setAge ( value. getInteger ( "age" ) ) ;
user. setBalance ( value. getDouble ( "balance" ) ) ;
boolean isSuccessful = ormContext. insert ( user) ;
if ( ! isSuccessful) {
HiLog. error ( LABEL_LOG, "failed to insert" ) ;
return - 1 ;
}
isSuccessful = ormContext. flush ( ) ;
if ( ! isSuccessful) {
HiLog. error ( LABEL_LOG, "failed to insert flush" ) ;
return - 1 ;
}
DataAbilityHelper. creator ( this, uri) . notifyChange ( uri) ;
int id = Math. toIntExact ( user. getRowId ( ) ) ;
return id;
}
batchInsert():该方法为批量插入方法,接收一个 ValuesBucket 数组用于单次插入一组对象。它的作用是提高插入多条重复数据的效率。该方法系统已实现,开发者可以直接调用。 delete():该方法用来执行删除操作,删除条件由类 DataAbilityPredicates 构建,服务端在接收到该参数之后可以从中解析出要删除的数据,然后到数据库中执行。根据传入的条件删除用户表数据的代码示例如下:
public int delete ( Uri uri, DataAbilityPredicates predicates) {
if ( ormContext == null) {
HiLog. error ( LABEL_LOG, "failed to delete, ormContext is null" ) ;
return - 1 ;
}
OrmPredicates ormPredicates = DataAbilityUtils. createOrmPredicates ( predicates, User. class) ;
int value = ormContext. delete ( ormPredicates) ;
DataAbilityHelper. creator ( this, uri) . notifyChange ( uri) ;
return value;
}
update():此方法用来执行更新操作,用户可以在 ValuesBucket 参数中指定要更新的数据,在 DataAbilityPredicates 中构建更新的条件等。更新用户表的数据的代码示例如下:
public int update ( Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
if ( ormContext == null) {
HiLog. error ( LABEL_LOG, "failed to update, ormContext is null" ) ;
return - 1 ;
}
OrmPredicates ormPredicates = DataAbilityUtils. createOrmPredicates ( predicates, User. class) ;
int index = ormContext. update ( ormPredicates, value) ;
HiLog. info ( LABEL_LOG, "UserDataAbility update value:" + index) ;
DataAbilityHelper. creator ( this, uri) . notifyChange ( uri) ;
return index;
}
executeBatch():此方法用来批量执行操作,DataAbilityOperation 中提供了设置操作类型、数据和操作条件的方法,用户可自行设置自己要执行的数据库操作。该方法系统已实现,开发者可以直接调用。 注册 UserDataAbility,和 Service 类似,开发者必须在配置文件中注册 Data。配置文件中该字段在创建 Data Ability 时会自动创建,name 与创建的 Data Ability 一致。需要关注以下属性:
permissions: 访问该 data ability 时需要申请的访问权限。
{
"name" : ".UserDataAbility" ,
"type" : "data" ,
"visible" : true,
"uri" : "dataability://com.example.myapplication5.DataAbilityTest" ,
"permissions" : [
"com.example.myapplication5.DataAbility.DATA"
]
}
③ 访问 Data
开发者可以通过 DataAbilityHelper 类来访问当前应用或其他应用提供的共享数据。DataAbilityHelper 作为客户端,与提供方的 Data 进行通信。 Data 接收到请求后,执行相应的处理,并返回结果。DataAbilityHelper 提供了一系列与 Data Ability 对应的方法。 如果待访问的 Data 声明了访问需要权限,则访问此 Data 需要在配置文件中声明需要此权限:
"reqPermissions" : [
{
"name" : "com.example.myapplication5.DataAbility.DATA"
} ,
{
"name" : "ohos.permission.READ_USER_STORAGE"
} ,
{
"name" : "ohos.permission.WRITE_USER_STORAGE"
}
]
DataAbilityHelper 为开发者提供了 creator() 方法来创建 DataAbilityHelper 实例。该方法为静态方法,有多个重载。最常见的方法是通过传入一个 context 对象来创建 DataAbilityHelper 对象。获取 helper 对象示例:
DataAbilityHelper helper = DataAbilityHelper. creator ( this) ;
访问 Data Ability:DataAbilityHelper 为开发者提供了一系列的接口来访问不同类型的数据(文件、数据库等)。
访问文件:DataAbilityHelper 为开发者提供了 FileDescriptor openFile(Uri uri, String mode)方法来操作文件。此方法需要传入两个参数,其中uri用来确定目标资源路径,mode用来指定打开文件的方式,可选方式包含“r”(读), “w”(写), “rw”(读写),“wt”(覆盖写),“wa”(追加写),“rwt”(覆盖写且可读)。该方法返回一个目标文件的 FD(文件描述符),把文件描述符封装成流,开发者就可以对文件流进行自定义处理。访问文件示例:
FileDescriptor fd = helper. openFile ( uri, "r" ) ;
FileInputStream fis = new FileInputStream ( fd) ;
访问数据库:DataAbilityHelper 为开发者提供了增、删、改、查以及批量处理等方法来操作数据库,见上文中的对数据库表数据的增删改查表。
五、Intent
① 基本概念
Intent 是对象之间传递信息的载体。 当一个 Ability 需要启动另一个 Ability 时,或者一个 AbilitySlice 需要导航到另一个 AbilitySlice 时,可以通过 Intent 指定启动的目标同时携带相关数据。 Intent 的构成元素包括 Operation 与 Parameters。 Operation 的子属性如下:
子属性 描述 Action 表示动作,通常使用系统预置Action,应用也可以自定义Action。例如IntentConstants.ACTION_HOME表示返回桌面动作 Entity 表示类别,通常使用系统预置Entity,应用也可以自定义Entity。例如Intent.ENTITY_HOME表示在桌面显示图标 Uri 表示Uri描述。如果在Intent中指定了Uri,则Intent将匹配指定的Uri信息,包括scheme, schemeSpecificPart, authority和path信息 Flags 表示处理Intent的方式。例如Intent.FLAG_ABILITY_CONTINUATION标记在本地的一个Ability是否可以迁移到远端设备继续运行 BundleName 表示包描述。如果在Intent中同时指定了BundleName和AbilityName,则Intent可以直接匹配到指定的Ability AbilityName 表示待启动的Ability名称。如果在Intent中同时指定了BundleName和AbilityName,则Intent可以直接匹配到指定的Ability DeviceId 表示运行指定Ability的设备ID
Parameters 是一种支持自定义的数据结构,开发者可以通过 Parameters 传递某些请求所需的额外信息。 当 Intent 用于发起请求时,根据指定元素的不同,分为两种类型:
如果同时指定了 BundleName 与 AbilityName,则根据 Ability 的全称(例如“com.demoapp.FooAbility”)来直接启动应用;
如果未同时指定 BundleName 和 AbilityName,则根据 Operation 中的其他属性来启动应用。 Intent 设置属性时,必须先使用 Operation 来设置属性。如果需要新增或修改属性,必须在设置 Operation 后再执行操作。
② 根据 Ability 的全称启动应用
通过构造包含 BundleName 与 AbilityName 的 Operation 对象,可以启动一个 Ability、并导航到该 Ability。示例代码如下:
Intent intent = new Intent ( ) ;
Operation operation = new Intent. OperationBuilder ( )
. withDeviceId ( "" )
. withBundleName ( "com.demoapp" )
. withAbilityName ( "com.demoapp.FooAbility" )
. build ( ) ;
intent. setOperation ( operation) ;
startAbility ( intent) ;
作为处理请求的对象,会在相应的回调方法中接收请求方传递的 Intent 对象。以导航到另一个 Ability 为例,导航的目标 Ability 可以在其 onStart() 回调的参数中获得 Intent 对象。
③ 根据 Operation 的其他属性启动应用
有些场景下,开发者需要在应用中使用其他应用提供的某种能力,而不感知提供该能力的具体是哪一个应用。例如开发者需要通过浏览器打开一个链接,而不关心用户最终选择哪一个浏览器应用,则可以通过 Operation 的其他属性(除 BundleName 与 AbilityName 之外的属性)描述需要的能力。如果设备上存在多个应用提供同种能力,系统则弹出候选列表,由用户选择由哪个应用处理请求。 以下示例展示使用 Intent 跨 Ability 查询天气信息:
在 Ability 中构造 Intent 以及包含 Action 的 Operation 对象,并调用 startAbilityForResult() 方法发起请求,然后重写 onAbilityResult() 回调方法,对请求结果进行处理。
private void queryWeather ( ) {
Intent intent = new Intent ( ) ;
Operation operation = new Intent. OperationBuilder ( )
. withAction ( Intent. ACTION_QUERY_WEATHER)
. build ( ) ;
intent. setOperation ( operation) ;
startAbilityForResult ( intent, REQ_CODE_QUERY_WEATHER) ;
}
@Override
protected void onAbilityResult ( int requestCode, int resultCode, Intent resultData) {
switch ( requestCode) {
case REQ_CODE_QUERY_WEATHER:
. . .
return ;
default :
. . .
}
}
作为处理请求的对象,首先需要在配置文件中声明对外提供的能力,以便系统据此找到自身并作为候选的请求处理者:
{
"module" : {
. . .
"abilities" : [
{
. . .
"skills" : [
{
"actions" : [
"ability.intent.QUERY_WEATHER"
]
}
]
. . .
}
]
. . .
}
. . .
}
在 Ability 中配置路由以便支持以此 action 导航到对应的 AbilitySlice:
@Override
protected void onStart ( Intent intent) {
. . .
addActionRoute ( Intent. ACTION_QUERY_WEATHER, DemoSlice. class. getName ( ) ) ;
. . .
}
在 Ability 中处理请求,并调用 setResult() 方法暂存返回结果:
@Override
protected void onActive ( ) {
. . .
Intent resultIntent = new Intent ( ) ;
setResult ( 0 , resultIntent) ;
. . .
}