HarmonyOS

文章40 |   阅读 20968 |   点赞0

来源:https://blog.csdn.net/forever_wj/category_11128883.html

HarmonyOS之深入解析Ability的功能和使用

x33g5p2x  于2022-03-07 转载在 其他  
字(23.3k)|赞(0)|评价(0)|浏览(574)
一、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);
	        // set the main route
	        setMainRoute(MainSlice.class.getName());
	
	        // set the action route
	        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 实例,该回调在其生命周期过程中仅触发一次,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。
      • 用户行为触发 Page 的 terminateAbility() 方法调用,例如使用应用的退出功能。
      • 配置变更导致系统暂时销毁 Page 并重建。
      • 系统出于资源管理目的,自动触发对处于 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) {
	        // Process resultIntent here.
	    }
	}
  • 系统为每个 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 为例,迁移动作主要步骤如下:
    • 设备 A 上的 Page 请求迁移;
    • 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) {
	    // Maybe another continuation in progress.
	    ...
	}
    • 以 Page 从设备 A 迁移到设备 B 为例,详细的流程如下:
      • 设备 A 上的 Page 请求迁移;
      • 系统回调设备 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) {
	    // Maybe another continuation in progress.
	    ...
	}
    • 以 Page 从设备 A 迁移到设备 B 后并请求回迁为例,详细的流程如下:
      • 设备 A 上的 Page 请求回迁;
      • 系统回调设备 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 获取设备列表;
    • BundleName:表示包名称;
    • 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);
  • 启动远程设备 Service 的代码示例如下:
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 回调实例的代码示例如下:
// 创建连接Service回调实例
	private IAbilityConnection connection = new IAbilityConnection() {
	    // 连接到Service的回调
	    @Override
	    public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
	        // Client侧需要定义与Service侧相同的IRemoteObject实现类。开发者获取服务端传过来IRemoteObject对象,并从中解析出服务端传过来的信息。
	    }
	
	    // Service异常死亡的回调
	    @Override
	    public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
	    }
	};
  • 连接 Service 的代码示例如下:
// 连接Service
	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 侧把自身的实例返回给调用侧的代码示例如下:
// 创建自定义IRemoteObject实现类
	private class MyRemoteObject extends LocalRemoteObject {
	    MyRemoteObject(){
	    }
	}
	
	// 把IRemoteObject返回给客户端
	@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() 代码示例如下:
// 创建通知,其中1005为notificationId
	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);
	
	// 绑定通知,1005为创建通知时传入的notificationId
	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:资源的路径信息,代表特定资源的位置信息。
    • query:查询参数。
    • fragment:可以用于指示要访问的子资源。
② 创建 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() 函数复制待操作文件流的文件描述符,并将其返回,供远端应用访问文件。
      • 根据传入的 uri 打开对应的文件:
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 = MessageParcel.obtain();
	    File file = new File(uri.getDecodedPathList().get(0)); // get(0)是获取URI完整字段中查询参数字段。
	    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 一致。需要关注以下属性:
    • type: 类型设置为 data;
    • uri: 对外提供的访问路径,全局唯一;
    • 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();
	
	// 通过Intent中的OperationBuilder类构造operation对象,指定设备标识(空串表示当前设备)、应用包名、Ability名称
	Operation operation = new Intent.OperationBuilder()
	        .withDeviceId("")
	        .withBundleName("com.demoapp")
	        .withAbilityName("com.demoapp.FooAbility")
	        .build();
	
	// 把operation设置到intent中
	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:
	            // Do something with result.
	            ...
	            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);   // 0为当前Ability销毁后返回的resultCode。
	    ...
	}

相关文章