HarmonyOS 第2章 Ability的开发,鸿蒙HarmonyOS 应用开发入门
第2章 Ability的开发
本章内容
本章介绍HarmonyOS的核心组件Ability的开发。
2.1 Ability概述
2.2 FA模型介绍
2.3 Stage模型介绍
2.4 Ability内页面的跳转和数据传递
2.5 Want概述
2.6 实战:显式Want启动Ability
2.7 实战:隐式Want打开应用管理
2.8 小结
2.9 习题
2.1 Ability概述
Ability翻译成中文就是“能力”的意思。在HarmonyOS中,Ability是应用所具备能力的抽象,也是应用程序的重要组成部分。
2.1.1 单Ability应用和多Ability应用
2.1.2 HarmonyOS应用模型
2.1.1 单Ability应用和多Ability应用
一个应用可以具备多种能力,也就是说可以包含多个Ability。HarmonyOS支持应用以Ability为单位进行部署。
2.1.2 HarmonyOS应用模型
HarmonyOS应用模型的构成要素
应用组件
应用进程模型
应用线程模型
应用任务管理模型
应用配置文件
截至目前,在HarmonyOS中,Ability框架模型结构具有以下两种形态:
FA模型:API 8及更早版本的应用程序只能使用FA模型进行开发。
Stage模型:从API 9开始,Ability框架引入并支持使用Stage模型进行开发,也是目前HarmonyOS所推荐的开发方式。
FA模型和Stage模型的工程目录结构存在差异,Stage模型目前只支持使用ArkTS语言进行开发。本书示例也是采用Stage模型开发的。
2.2 FA模型介绍
FA(Feature Ability)模型是HarmonyOS早期版本(API 8及更早版本)开始支持的模型,目前已经不再主推。
2.2.1 FA模型中的Ability
FA模型中的Ability分为PageAbility、ServiceAbility、DataAbility、FormAbility四种类型。其中:
PageAbility是具备UI实现的Ability,是用户具体可见并可以交互的Ability实例。
ServiceAbility也是Ability的一种,但是没有UI,为其他Ability提供调用自定义的服务,在后台运行。
DataAbility也是没有UI的Ability,为其他Ability提供进行数据增、删、查的服务,在后台运行。
FormAbility是卡片Ability,是一种界面展示形式。
2.2.2 FA模型的生命周期
2.2.3 FA模型的进程线程模型
2.3 Stage模型介绍
Stage模型是HarmonyOS 3.1版本开始新增的模型,也是目前HarmonyOS主推且会长期演进的模型。在该模型中,由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此称这种应用模型为Stage模型。
本节主要介绍以Stage模型为主的开发方式。
2.3.1 Stage模型的设计思想
2.3.2 Stage模型的Ability生命周期
2.3.3 Stage模型的Ability启动模式
2.3.1 Stage模型的设计思想
Stage模型的设计基于如下3个出发点:
为复杂应用而设计
支持多设备和多窗口形态
平衡应用能力和系统管控成本
2.3.2 Stage模型的Ability生命周期
Ability的生命周期包括Create、Foreground、Background、Destroy四个状态,WindowStageCreate和WindowStageDestroy为窗口管理器(WindowStage)在Ability中管理UI界面功能的两个生命周期回调,从而实现Ability与窗口之间的弱耦合
2.3.3 Stage模型的Ability启动模式
Ability的启动模式是指Ability实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了3种启动模式:
singleton(单实例模式)。
standard(标准实例模式)。
specified(指定实例模式)。
singleton启动模式
每次调用startAbility()方法时,如果应用进程中该类型的Ability实例已经存在,则复用系统中的Ability实例。系统中只存在唯一一个该Ability实例,即在最近任务列表中只存在一个该类型的Ability实例。此时,应用的Ability实例已创建,当再次调用startAbility()方法启动该Ability实例时,只会进入该Ability的onNewWant()回调,不会进入其onCreate()和onWindowStageCreate()生命周期回调。
如果需要使用singleton启动模式,将module.json5配置文件中的"launchType"字段配置为"singleton’'即可。
{"module": {..."abilities": [{"launchType": "singleton",...}]}
}
standard启动模式
在standard启动模式下,每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型的Ability实例,即在最近任务列表中可以看到有多个该类型的Ability实例。这种情况下,可以将Ability配置为standard。
如果需要使用standard启动模式,将module.json5配置文件中的"launchType"字段配置为"standard"即可。
在specified启动模式下,在Ability实例创建之前,允许开发者为该实例创建一个唯一的字符串Key,创建的Ability实例绑定Key之后,后续每次调用startAbility()方法时,都会询问应用使用哪个Key对应的Ability实例来响应startAbility请求。运行时由Ability内部业务决定是否创建多个实例,如果匹配有该Ability实例的Key,则直接拉起与之绑定的Ability实例,否则创建一个新的Ability实例。
例如,用户在应用中重复打开同一个文档时,启动的均是最近任务列表中的同一个任务,以及在应用中重复新建文档时,启动的均是最近任务列表中的新任务。这种情况下,可以将Ability配置为specified。当再次调用startAbility()方法启动该Ability实例,且AbilityStage的onAcceptWant()回调匹配到一个已创建的Ability实例时,再次启动该Ability,只会进入该Ability的onNewWant()回调,不会进入其onCreate()和onWindowStageCreate()生命周期回调。
如果需要使用specified启动模式,将module.json5配置文件的"launchType"字段配置为"specified"即可。
2.4 Ability内页面的跳转和数据传递
Ability的数据传递包括Ability内页面的跳转和数据传递、Ability间的数据跳转和数据传递。本节主要讲解Ability内页面的跳转和数据传递。
2.4.1 新建Ability内页面
2.4.2 页面跳转及传参
2.4.3 参数接收
2.4.4 运行
2.4.1 新建Ability内页面
初始化工程之后,会生成以下内容:
在src/main/ets/entryability目录下,初始会生成一个Ability文件EntryAbility.ts。可以在EntryAbility.ts文件中根据业务需要实现Ability的生命周期回调内容。
在src/main/ets/pages目录下,会生成一个Index页面。这也是基于Ability实现的应用的入口页面。可以在Index页面中根据业务需要实现入口页面的功能。
为了实现页面的跳转和数据传递,需要新建一个页面。在src/main/ets/pages目录下,可以通过右击New→Page来新建页面
在原有Index页面的基础上,新建一个名为Second的页面
Second页面创建完成之后,会自动做两个动作。一个动作是在src/main/ets/pages目录
下创建一个Second.ets文件。Second.ets文件内容如下:
@Entry
@Component
struct Second {@State message: string = 'Hello World'build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')}
}
另一个动作是将Second页面信息配置到
src/main/resources/base/profile/main_pages.json文件中。main_pages.json文件内
容如下:
{"src": ["pages/Index","pages/Second"]
}
分别把Index.ets和Second.ets的message变量值改为“Index页面”和“Second页面”以
示区别。
2.4.2 页面跳转及传参
在使用页面路由之前,需要先导入router模块,代码如下:
//导入router模块
import router from ‘@ohos.router’;
页面跳转有以下几种方式,根据需要选择一种方式跳转即可。
router.push()
通过调用router.push()方法,跳转到Ability内的指定页面。每调用一次router.push()方法,均会新建一个页面。默认情况下,页面栈数量会加1,页面栈支持的最大页面数量为32。
当页面栈数量较大或者超过32时,可以通过调用router.clear()方法清除页面栈中的所
有历史页面,仅保留当前页面作为栈顶页面。
用法示例如下:
router.push({url: 'pages/Second',params: {src: 'Index页面传来的数据',}
})
router.push()加mode参数
router.push()方法新增了mode参数,可以将mode参数配置为router.RouterMode.Single
在单实例模式下,如果目标页面在页面栈中已经存在同URL的页面,离栈顶最近的同URL的页面会被移动到栈顶,移动后的页面为新建页,原来的页面仍然保存在栈中,页面栈数量不变;如果目标页面在页面栈中不存在同URL的页面,那么按照标准模式跳转,页面栈数量会加1。
用法示例如下:
router.push({url: 'pages/Second',params: {src: 'Index页面传来的数据',}
}, router.RouterMode.Single)
router.replace()
通过调用router.replace()方法,跳转到Ability内的指定页面。即使用新的页面替换当前页面,并销毁被替换的当前页面,页面栈数量依然不变。
用法示例如下:
router.replace({url: 'pages/Second',params: {src: 'Index页面传来的数据',}
})
router.replace()加mode参数
router.replace()方法新增了mode参数,可以将mode参数配置为
router.RouterMode.Single单实例模式和router.RouterMode.Standard标准模式。
在单实例模式下,如果目标页面在页面栈中已经存在同URL的页面,离栈顶最近的同RUL的页面会被移动到栈顶,替换当前页面,并销毁被替换的当前页面,移动后的页面为新建页,页面栈数量会减1;如果目标页面在页面栈中不存在同URL的页面,那么按照标准模式跳转,页面栈数量不变。
用法示例如下:
router.replace({url: 'pages/Second',params: {src: 'Index页面传来的数据',}
}, router.RouterMode.Single)
最后,在Index.ets文件中添加按钮以触发跳转。Index.ets代码如下:
//导入router模块
import router from '@ohos.router';@Entry
@Component
struct Index {@State message: string = 'Index页面'build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)
//添加按钮,触发跳转Button('跳转').fontSize(40).onClick(() => {router.push({url: 'pages/Second',params: {src: 'Index页面传来的数据',}});})}.width('100%')}.height('100%')}
}
2.4.3 参数接收
通过调用router.getParams()方法获取Index页面传递过来的自定义参数。
import router from '@ohos.router';@Entry
@Component
struct Second {@State src: string = router.getParams()?.['src'];//页面刷新展示...
}
可以调用router.back()方法返回上一个页面。
最终,完整的Index.ets代码如下:
//导入router模块
import router from '@ohos.router';@Entry
@Component
struct Index {@State message: string = 'Index页面';build() {RelativeContainer() {Text(this.message).id('HelloWorld').fontSize($r('app.float.page_text_font_size')).fontWeight(FontWeight.Bold).alignRules({center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {this.message = 'Welcome';})//添加按钮,触发跳转Button('跳转').fontSize(40).alignRules({top: { anchor: 'HelloWorld', align: VerticalAlign.Bottom }, // 顶部锚点为HelloWorld的底部//center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {router.pushUrl({ url: 'pages/Second' })})}.height('100%').width('100%')}
}
完整的Second.ets代码如下:
//导入router模块
import router from '@ohos.router';@Entry
@Component
struct Second {@State message: string = 'Second页面';build() {RelativeContainer() {Text(this.message).id('SecondHelloWorld').fontSize($r('app.float.page_text_font_size')).fontWeight(FontWeight.Bold).alignRules({center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {this.message = 'Welcome';})//添加按钮,触发返回Button('返回').fontSize(40).alignRules({top: { anchor: 'SecondHelloWorld', align: VerticalAlign.Bottom }, // 顶部锚点为SecondHelloWorld的底部//center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {router.back();})}.height('100%').width('100%')}
}
2.4.4 运行
2.5 Want概述
在Stage模型中,Want是对象间信息传递的载体,可以用于应用组件间的信息传递。而在FA模型中,Intent是与之有相同概念的类。
2.5.1 Want的用途
2.5.2 Want的类型
2.5.3 Want参数属性
2.5.1 Want的用途
Want的使用场景之一是作为startAbility的参数,其包含指定的启动目标,以及启动时需携带的相关数据,如bundleName和bilityName字段分别指明目标Ability所在应用的包名以及对应包内的Ability名称。当AbilityA启动AbilityB并需要传入一些数据给AbilityB时,Want可以作为一个数据载体将数据传给AbilityB。
2.5.2 Want的类型
Want的类型主要分为显式和隐式。
显式Want
在启动Ability时指定了abilityName和bundleName的Want称为显式Want。
当有明确处理请求的对象时,通过提供目标Ability所在应用的包名信息(bundleName)
,并在Want内指定abilityName便可启动目标Ability。显式Want通常在启动当前应用开
发中某个已知Ability时被用到,示例如下:
let want = {deviceId: '',bundleName: 'com.example.myapplication',abilityName: 'calleeAbility',
};
隐式Want
在启动Ability时未指定abilityName的Want称为隐式Want。
当请求处理的对象不明确时,如开发者希望在当前应用中使用其他应用提供的某个能力(通过skills定义),而不关心提供该能力的具体应用时,可以使用隐式Want。例如使用隐式Want描述需要打开一个链接的请求,而不关心通过具体哪个应用打开,系统将匹配声明支持该请求的所有应用。当未匹配到支持的应用时,系统将弹窗说明无法打开;当仅匹配到一个应用时,系统将自动拉起对应应用;当匹配到多个应用时,系统将弹出候选列表,由用户选择拉起哪个应用,示例如下:
let want = {action: 'ohos.want.action.search',entities: [ 'entity.system.browsable' ],uri: 'https://www.test.com:8080/query/student',type: 'text/plain',
};
常见的action:
ACTION_HOME:启动应用入口组件的动作,需要和ENTITY_HOME配合使用。系统桌面应用图标就是显式的入口组件,单击也是启动入口组件。入口组件可以配置多个。
ACTION_CHOOSE:选择本地资源数据,例如联系人、相册等。系统一般对不同类型的数据有对应的Picker应用,例如联系人和图库。
ACTION_VIEW_DATA:查看数据,当使用网址uri时,表示显示该网址对应的内容。
ACTION_VIEW_MULTIPLE_DATA:发送多个数据记录的操作。
常用的entities:
ENTITY_DEFAULT:默认类别无实际意义。
ENTITY_HOME:主屏幕有图标单击入口类别。
ENTITY_BROWSABLE:指示浏览器类别。
2.5.3 Want参数属性
2.6 显式Want启动Ability
本节演示如何通过显式Want拉起应用内一个指定Ability组件。
打开DevEco Studio,选择一个Empty Ability工程模板,创建一个名为ArkUIWantStartAbility的工程为演示示例。
2.6.1 新建Ability内页面
初始化工程之后,在原有代码的基础上新建一个页面。在src/main/ets/pages目录下,通过右击New→Page来新建一个名为Second的页面。对Second.ets文件中的message变量值进行修改,最终文件内容如下:
@Entry
@Component
struct Second {//修改变量值为Second@State message: string = 'Second'build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')}
}
2.6.2 新建Ability
在原有代码的基础上新建一个Ability。在src/main/ets目录下,通过右击ew→Ability来新建一个名为SecondAbility的Ability。
创建完成之后,会自动在module.json5文件中添加该Ability的信息:
{"name": "SecondAbility","srcEntrance": "./ets/secondability/SecondAbility.ts","description": "$string:SecondAbility_desc","icon": "$media:icon","label": "$string:SecondAbility_label","startWindowIcon": "$media:icon","startWindowBackground": "$color:start_window_background","visible": true
}
此时,在src/main/ets目录下会初始化一个secondability目录,并在secondability目录下生成一个SecondAbility.ts文件。修改该文件,将’pages/Index’改为’pages/Second’,最终文件内容如下:
onWindowStageCreate(windowStage: Window.WindowStage) {//Main window is created, set main page for this abilityhilog.isLoggable(0x0000, 'testTag', hilog.LogLevel.INFO);hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');//加载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', .stringify(data) ?? '');});}
2.6.3 使用显式Want启动Ability