铁山靠之——HarmonyOS基础 - 1.0
HarmonyOS学习第一章
参考了华为鸿蒙开发文档
一、HarmonyOS简介
1.1 安装和使用DevEco Studio
下载完成后,双击下载的“deveco-studio-xxxx.exe”,进入DevEco Studio安装向导,在如下界面选择安装路径,默认安装于“C:\Program Files”
下,也可以单击“Browse…”指定其他安装路径,然后单击“Next”。
如下安装选项界面勾选DevEco Studio后,单击“Next”,直至安装完成。
接下来就是一直next
1.2 环境配置
进入DevEco Studio配置页面,首先需要进行基础配置,包括Node.js与Ohpm的安装路径设置,选择从华为镜像下载至合适的路径。
单击’Next’进入SDK配置,设置为合适的路径,
点击’Next’后会显示’SDK License Agreement’,阅读相关协议后,勾选’Accept’。
点击next,等待配置自动下载完成,完成后,单击’Finish’,IDE会进入欢迎页,我们也就成功配置好了开发环境。
1.3 项目创建
在欢迎页中单击Create Project,进入项目创建页面。
选择‘Application’,然后选择‘Empty Ability’,单击‘Next’进入工程配置页。
配置页中,详细信息如下:
- Project name是开发者可以自行设置的项目名称,这里根据自己选择修改为自己项目名称。
- Bundle name是包名称,默认情况下应用ID也会使用该名称,应用发布时对应的ID需要保持一致。
- Save location为工程保存路径,建议用户自行设置相应位置。
- Compile SDK是编译的API版本,这里默认选择API9。
- Model选择Stage模型,其他保持默认即可。
然后单击“Finish”完成工程创建,等待工程同步完成。
预览器提供了一些基本功能,包括旋转屏幕,切换显示设备及多设备预览等。单击旋转按钮,可以切换竖屏和横屏显示的效果。
也可以单击如下列表按钮,切换显示的设备类型。弹出框内会显示Available Profiles,即可用的设备类型。
如单击Foldable切换设备,也可以单击旋转按钮切换Foldable的横竖屏显示模式。
打开Muti-profile preview开关,可以实现多个尺寸设备的实时预览。
单击预览器右上角组件预览按钮,可以进入组件预览界面。
组件预览模式可以预览当前组件对应的代码块。
点击相应组件,代码文件中会框选对应的组件代码部分,下方则对应当前组件的基本属性。
1.4 运行程序
IDE提供了本地模拟器供开发者使用,我们首先需要下载安装本地模拟器,然后进行运行工程。
- 单击顶部工具栏Tools>Device Manager。
- 选择Local Emulator,设置合适的Local Emulator Location存储地址,然后单击’+New Emulator’。
选择Huawei_Phone手机模拟器,单击’Next’,进入模拟器系统下载页。
选择下载api9
的系统镜像,然后单击’Next’,等待下载完成。
下载完成后,进行创建相应的手机模拟器,单击Finish完成创建。
下载完成后,在Local Emulator页面中会出现创建的手机模拟器,点击Actions按钮,就能够启动模拟器。
模拟器启动后,点击上方启动按钮,将Hello World工程运行到模拟器上。
IDE构建完成后,即可在模拟器上看到运行效果,我们也就完成了Hello World工程在模拟器上的运行。
1.5 基本工程目录
1.5.1 工程级目录
其中详细如下:
AppScope
中存放应用全局所需要的资源文件。
entry是应用的主模块,存放HarmonyOS应用的代码、资源等。oh_modules
是工程的依赖包,存放工程依赖的源文件。build-profile.json5
是工程级配置信息,包括签名、产品配置等。hvigorfile.ts
是工程级编译构建任务脚本,hvigor是基于任务管理机制实现的一款全新的自动化构建工具,主要提供任务注册编排,工程模型管理、配置管理等核心能力。oh-package.json5
是工程级依赖配置文件,用于记录引入包的配置信息。
在AppScope,其中有resources文件夹和配置文件app.json5。AppScope>resources>base中包含element和media两个文件夹,
- 其中element文件夹主要存放公共的字符串、布局文件等资源。
- media存放全局公共的多媒体资源文件。
1.5.2 模块级目录
entry>src目录中主要包含总的main文件夹,单元测试目录ohosTest,以及模块级的配置文件。
main
文件夹中,ets文件夹用于存放ets代码,resources文件存放模块内的多媒体及布局文件等,module.json5文件为模块的配置文件。ohosTest
是单元测试目录。build-profile.json5
是模块级配置信息,包括编译构建配置项。hvigorfile.ts
文件是模块级构建脚本。oh-package.json5
是模块级依赖配置信息文件。
进入src>main>ets目录中,其分为entryability、pages两个文件夹。
- entryability存放ability文件,用于当前ability应用逻辑和生命周期管理。
- pages存放UI界面相关代码文件,初始会生成一个Index页面。
resources目录下存放模块公共的多媒体、字符串及布局文件等资源,分别存放在element、media文件夹中。
1.5.3 app.json5
AppScope>app.json5是应用的全局的配置文件,用于存放应用公共的配置信息。
其中配置信息如下:
- bundleName是包名。
- vendor是应用程序供应商。
- versionCode是用于区分应用版本。
- versionName是版本号。
- icon对应于应用的显示图标。
- label是应用名。
1.5.4 module.json5
entry>src>main>module.json5是模块的配置文件,包含当前模块的配置信息。
其中module对应的是模块的配置信息,一个模块对应一个打包后的hap包,hap包全称是HarmonyOS Ability Package,其中包含了ability、第三方库、资源和配置文件。其具体属性及其描述可以参照下表1。
- 表1 module.json5默认配置属性及描述
属性 | 描述 |
---|---|
name | 该标签标识当前module的名字,module打包成hap后,表示hap的名称,标签值采用字符串表示(最大长度31个字节),该名称在整个应用要唯一。 |
type | 表示模块的类型,类型有三种,分别是entry、feature和har。 |
srcEntry | 当前模块的入口文件路径。 |
description | 当前模块的描述信息。 |
mainElement | 该标签标识hap的入口ability名称或者extension名称。只有配置为mainElement的ability或者extension才允许在服务中心露出。 |
deviceTypes | 该标签标识hap可以运行在哪类设备上,标签值采用字符串数组的表示。 |
deliveryWithInstall | 标识当前Module是否在用户主动安装的时候安装,表示该Module对应的HAP是否跟随应用一起安装。- true:主动安装时安装。- false:主动安装时不安装。 |
installationFree | 标识当前Module是否支持免安装特性。- true:表示支持免安装特性,且符合免安装约束。- false:表示不支持免安装特性。 |
pages | 对应的是main_pages.json文件,用于配置ability中用到的page信息。 |
abilities | 是一个数组,存放当前模块中所有的ability元能力的配置信息,其中可以有多个ability。 |
对于abilities中每一个ability的属性项,其描述信息如下表2。
- 表2 abilities中对象的默认配置属性及描述
属性 | 描述 |
---|---|
name | 该标签标识当前ability的逻辑名,该名称在整个应用要唯一,标签值采用字符串表示(最大长度127个字节)。 |
srcEntry | ability的入口代码路径。 |
description | ability的描述信息。 |
icon | ability的图标。该标签标识ability图标,标签值为资源文件的索引。该标签可缺省,缺省值为空。如果ability被配置为MainElement,该标签必须配置。 |
labe | lability的标签名。 |
startWindowIcon | 启动页面的图标。 |
startWindowBackground | 启动页面的背景色。 |
visible | ability是否可以被其他应用程序调用,true表示可以被其它应用调用, false表示不可以被其它应用调用。 |
skills | 标识能够接收的意图的action值的集合,取值通常为系统预定义的action值,也允许自定义。 |
entities | 标识能够接收Want的Entity值的集合。 |
actions | 标识能够接收的Want的Action值的集合,取值通常为系统预定义的action值,也允许自定义。 |
1.5.5 main_pages.json
src/main/resources/base/profile/main_pages.json
文件保存的是页面page的路径配置信息,所有需要进行路由跳转的page页面都要在这里进行配置。
二、TypeScript快速入门
如果没有搭建TypeScript的开发环境,也可以直接使用在线Playground平台进行编码练习
2.1 简介
ArkTS是HarmonyOS优选的主力应用开发语言。它在TypeScript(简称TS)的基础上,匹配ArkUI框架,扩展了声明式UI、状态管理等相应的能力,让开发者以更简洁、更自然的方式开发跨端应用。要了解什么是ArkTS,我们首先要了解下ArkTS、TypeScript和JavaScript之间的关系:
- JavaScript是一种属于网络的高级脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。
- TypeScript 是 JavaScript 的一个超集,它扩展了 JavaScript 的语法,通过在JavaScript的基础上添加静态类型定义构建而成,是一个开源的编程语言。
- ArkTS兼容TypeScript语言,拓展了声明式UI、状态管理、并发任务等能力。
由此可知,TypeScript是JavaScript的超集,ArkTS则是TypeScript的超集,他们的关系如下图所示:
2.2 基础类型
TypeScript支持一些基础的数据类型,如布尔型、数组、字符串等,下文举例几个较为常用的数据类型,我们来了解下他们的基本使用。
2.2.1 布尔值
TypeScript中可以使用boolean来表示这个变量是布尔值,可以赋值为true或者false。
let isDone: boolean = false;
2.2.2 数字
TypeScript里的所有数字都是浮点数,这些浮点数的类型是 number。除了支持十进制,还支持二进制、八进制、十六进制。
最后把数字通过日志的形式打印出来,都会转换成十进制,结果都是2023
2.2.3 字符串
TypeScript里使用 string表示文本数据类型, 可以使用双引号"
或单引号'
表示字符串
2.2.4 数组
TypeScrip有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组。
第二种方式是使用数组泛型,Array<元素类型>
。
2.2.5 元组
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组。
2.2.6 枚举
enum类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字。
2.2.7 Unknown
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。那么我们可以使用unknown类型来标记这些变量。
2.2.8 Void
当一个函数没有返回值时,你通常会见到其返回值类型是 void。
2.2.9 Null 和 Undefined
TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。
2.2.10 联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种
2.3 条件语句
2.3.1 if语句
TypeScript if 语句由一个布尔表达式后跟一个或多个语句组成。
var num:number = 5
if (num > 0) {
console.log('数字是正数')
}
2.3.2 if…else语句
一个 if 语句后可跟一个可选的 else 语句,else 语句在布尔表达式为 false 时执行。
var num:number = 12;
if (num % 2==0) {
console.log('偶数');
} else {
console.log('奇数');
}
2.3.3 if…else if…else 语句
if…else if…else 语句在执行多个判断条件的时候很有用。
var num:number = 2
if(num > 0) {
console.log(num+' 是正数')
} else if(num < 0) {
console.log(num+' 是负数')
} else {
console.log(num+' 为0')
}
2.3.4 switch…case 语句
一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。
var grade:string = 'A';
switch(grade) {
case 'A': {
console.log('优');
break;
}
case 'B': {
console.log('良');
break;
}
case 'C': {
console.log('及格');
break;
}
case 'D': {
console.log('不及格');
break;
}
default: {
console.log('非法输入');
break;
}
}
2.4 函数
函数是一组一起执行一个任务的语句,函数声明要告诉编译器函数的名称、返回类型和参数。TypeScript可以创建有名字的函数和匿名函数,其创建方法如下:
// 有名函数
function add(x, y) {
return x + y;
}
// 匿名函数
let myAdd = function (x, y) {
return x + y;
};
2.4.1 为函数定义类型
为了确保输入输出的准确性,我们可以为上面那个函数添加类型:
// 有名函数:给变量设置为number类型
function add(x: number, y: number): number {
return x + y;
}
// 匿名函数:给变量设置为number类型
let myAdd = function (x: number, y: number): number {
return x + y;
};
2.4.2 可选参数
在TypeScript里我们可以在参数名旁使用 ?实现可选参数的功能。 比如,我们想让lastName是可选的:
2.4.3 剩余参数
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 可以使用省略号( …)进行定义:
2.4.4 箭头函数
ES6版本的TypeScript提供了一个箭头函数,它是定义匿名函数的简写语法,用于函数表达式,它省略了function关键字。箭头函数的定义如下,其函数是一个语句块:
其中,括号内是函数的入参,可以有0到多个参数,箭头后是函数的代码块。我们可以将这个箭头函数赋值给一个变量,如下所示:
如何要主动调用这个箭头函数,可以按如下方法去调用:
-
接下来我们看看如何将我们熟悉的函数定义方式转换为箭头函数。我们可以定义一个判断正负数的函数,如下:
- 其调用方法如下:
- 其调用方法如下:
-
如果将这个函数定义为箭头函数,定义如下所示:
- 其调用方法如下:
- 其调用方法如下:
后面,我们在学习HarmonyOS应用开发时会经常用到箭头函数。例如,给一个按钮添加点击事件,其中onClick事件中的函数就是箭头函数。
2.5 类
TypeScript支持基于类的面向对象的编程方式,定义类的关键字为 class,后面紧跟类名。类描述了所创建的对象共同的属性和方法。
2.5.1 类的定义
例如,我们可以声明一个Person类,这个类有3个成员:一个是属性(包含name和age),一个是构造函数,一个是getPersonInfo方法,其定义如下所示。
class Person {
private name: string
private age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public getPersonInfo(): string {
return `My name is ${this.name} and age is ${this.age}`;
}
}
通过上面的Person类,我们可以定义一个人物Jacky并获取他的基本信息,其定义如下:
let person1 = new Person('Jacky', 18);
person1.getPersonInfo();
2.5.2 继承
继承就是子类继承父类的特征和行为,使得子类具有父类相同的行为。TypeScript中允许使用继承来扩展现有的类,对应的关键字为extends。
class Employee extends Person {
private department: string
constructor(name: string, age: number, department: string) {
super(name, age);
this.department = department;
}
public getEmployeeInfo(): string {
return this.getPersonInfo() + ` and work in ${this.department}`;
}
}
通过上面的Employee类,我们可以定义一个人物Tom,这里可以获取他的基本信息,也可以获取他的雇主信息,其定义如下:
let person2 = new Employee('Tom', 28, 'HuaWei');
person2.getPersonInfo();
person2.getEmployeeInfo();
在TypeScript中,有public、private、protected修饰符,其功能和具体使用场景,与java类似
2.6 模块
随着应用越来越大,通常要将代码拆分成多个文件,即所谓的模块(module)。模块可以相互加载,并可以使用特殊的指令 export 和 import 来交换功能,从另一个模块调用一个模块的函数。
两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。
2.6.1 导出
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出,例如我们要把NewsData这个类导出,代码示意如下:
export class NewsData {
title: string;
content: string;
imagesUrl: Array<NewsFile>;
source: string;
constructor(title: string, content: string, imagesUrl: Array<NewsFile>, source: string) {
this.title = title;
this.content = content;
this.imagesUrl = imagesUrl;
this.source = source;
}
}
2.6.2 导入
模块的导入操作与导出一样简单。 可以使用以下 import形式之一来导入其它模块中的导出内容。
import { NewsData } from '../common/bean/NewsData';
2.7 迭代器
当一个对象实现了Symbol.iterator属性时,我们认为它是可迭代的。一些内置的类型如Array,Map,Set,String,Int32Array,Uint32Array等都具有可迭代性。
2.7.1 for…of 语句
for…of会遍历可迭代的对象,调用对象上的Symbol.iterator方法。 下面是在数组上使用for…of的简单例子:
let someArray = [1, "string", false];
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}
2.7.2 for…of vs. for…in 语句
for…of和for…in均可迭代一个列表,但是用于迭代的值却不同:for…in迭代的是对象的键,而for…of则迭代的是对象的值。
let list = [4, 5, 6];
for (let i in list) {
console.log(i); // "0", "1", "2",
}
for (let i of list) {
console.log(i); // "4", "5", "6"
}
三、ArkTS基础
3.1 ArkTS简介
ArkTS是HarmonyOS优选的主力应用开发语言。它在TypeScript(简称TS)的基础上,扩展了声明式UI、状态管理等相应的能力,让开发者可以以更简洁、更自然的方式开发高性能应用。TS是JavaScript(简称JS)的超集,ArkTS则是TS的超集。ArkTS会结合应用开发和运行的需求持续演进,包括但不限于引入分布式开发范式、并行和并发能力增强、类型系统增强等方面的语言特性
- ArkUI开发框架
- ArkTS声明式开发范式代码案例
这个示例中所包含的ArkTS声明式开发范式的基本组成说明如下:- 装饰器
用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中 @Entry 、 @Component 、 @State 都是装饰器。具体而言, @Component 表示这是个自定义组件; @Entry 则表示这是个入口组件; @State 表示组件中的状态变量,此状态变化会引起 UI 变更。 - 自定义组件
可复用的 UI 单元,可组合其它组件,如上述被 @Component 装饰的 struct Hello。 - UI 描述
声明式的方式来描述 UI 的结构,如上述 build() 方法内部的代码块。 - 内置组件
框架中默认内置的基础和布局组件,可直接被开发者调用,比如示例中的 Column、Text、Divider、Button。 - 事件方法
用于添加组件对事件的响应逻辑,统一通过事件方法进行设置,如跟随在Button后面的onClick()。 - 属性方法
用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color() 等,可通过链式调用的方式设置多项属性。
- 装饰器
从UI框架的需求角度,ArkTS在TS的类型系统的基础上,做了进一步的扩展:定义了各种装饰器、自定义组件和UI描述机制
,再配合UI开发框架中的UI内置组件、事件方法、属性方法等共同构成了应用开发的主体。在应用开发中,除了UI的结构化描述之外,还有一个重要的方面:状态管理
。如上述示例中,用 @State 装饰过的变量 myText ,包含了一个基础的状态管理机制,即 myText 的值的变化会自动触发相应的 UI 变更 (Text组件)。ArkUI 中进一步提供了多维度的状态管理机制
。和 UI 相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传
递。开发者可以灵活的利用这些能力来实现数据和 UI 的联动。
3.2 快速入门
将UI进行封装,实现自定义组件是不可或缺的
- 例如:TitleComponent对应其顶部在第一个
排行榜
中的部分 - RankPage中,其就是父组件,使用了叫TitleComponen的子t组件
3.2.1 常用装饰器@Component与@Entry
3.2.2 自定义组件生命周期回调函数
- aboutToAppear创建资源
- aboutToDisapper释放资源
@Entry修饰的页面入口组件生命周期回调函数
3.1.3 渲染控制
-
渲染控制语法:
条件渲染
:使用if/else进行条件渲染。
Column() { i f (this.count > 0) { Text('count is positive') } }
循环渲染
:开发框架提供循环渲染(ForEach组件)来迭代数组,并为每个数组项创建相应的组件。
ForEach( arr: any[], // 用于迭代的数组 itemGenerator: (item: any, index?: number) => void, // 生成子组件的lambda函数 keyGenerator?: (item: any, index?: number) => string // 用于给定数组项生成唯一且稳定的键值 )
循环渲染是三个参数,
第一个参数必须是数组;
第二个参数是子组件的生成函数,为数据源中的每个数组项生成子组件;
第三个参数是键值生成器,用于给数据项生成唯一且稳定的键值
3.1.4 状态管理
组件状态管理装饰器用来管理组件中的状态,它们分别是:@State、@Prop、@Link。
-
@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
-
@Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。
-
@Link装饰的变量可以和父组件的@State变量建立双向数据绑定,需要注意的是:@Link变量
不能在组件内部进行初始化
。 -
@Builder装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。
@State、@Prop、@Link三者关系如图所示:
3.3 ArkTS开发实践
3.3.1 声明式UI基本概念
应用界面是由一个个页面组成,ArkTS是由ArkUI框架提供,用于以声明式开发范式开发界面的语言。
声明式UI构建页面的过程,其实是组合组件的过程,声明式UI的思想,主要体现在两个方面:
- 描述UI的呈现结果,而不关心过程
- 状态驱动视图更新
类似苹果的SwiftUI中通过组合视图View,安卓Jetpack Compose中通过组合@Composable函数,ArkUI作为HarmonyOS应用开发的UI开发框架,其使用ArkTS语言构建自定义组件,通过组合自定义组件完成页面的构建。
3.3.2 自定义组件的组成
ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。
使用@Entry和@Component装饰的自定义组件作为页面的入口,会在页面加载时首先进行渲染。
@Entry
@Component
struct ToDoList {...}
-
例如ToDoList组件对应如下整个代办页面。
使用@Component装饰的自定义组件,如ToDoItem这个自定义组件则对应如下内容,作为页面的组成部分。@Component struct ToDoItem {...}
-
ToDoItem
在自定义组件内需要使用build方法来进行UI描述。@Entry @Component struct ToDoList ... build() { ... } }
build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,ToDoItem为自定义组件,需要开发者使用ArkTS自行声明。
@Entry
@Component
struct ToDoList {
...
build() {
Column(...) {
Text(...)
...
ForEach(...{
TodoItem(...)
},...)
}
...
}
}
3.3.3 属性配置
自定义组件的组成使用基础组件和容器组件等内置组件进行组合。但有时内置组件的样式并不能满足我们的需求,ArkTS提供了属性方法用于描述界面的样式。属性方法支持以下使用方式:
-
常量传递
例如使用fontSize()来配置字体大小。Text('待办') .fontSize(28)
-
变量传递
在组件内定义了相应的变量后,例如组件内部成员变量size,就可以使用this.size方式使用该变量。Text('待办') .fontSize(this.size)
-
链式调用
在配置多个属性时,ArkTS提供了链式调用的方式,通过’.'方式连续配置。Text('待办') .fontSize(this.size) .width(100) .height(100)
-
表达式传递
属性中还可以传入普通表达式以及三目运算表达式。Text('待办') .fontSize(this.size) .width(this.count + 100) .height(this.count % 2 === 0 ? 100 : 200)
-
内置枚举类型
除此之外,ArkTS中还提供了内置枚举类型,如Color,FontWeight等,例如设置fontColor改变字体颜色为红色,并私有fontWeight为加粗。Text('Hello World') .fontSize(this.size) .width(this.count + 100) .height(this.count % 2 === 0 ? 100 : 200) .fontColor(Color.Red) .fontWeight(FontWeight.Bold)
对于有多种组件需要进行组合时,容器组件则是描述了这些组件应该如何排列的结果。
ArkUI中的布局容器有很多种,在不同的适用场合选择不同的布局容器实现,ArkTS使用容器组件采用花括号语法,内部放置UI描述。
3.3.4 列布局和行布局
对于如下每一项的布局,两个元素为横向排列,选择Row布局
-
Row布局
Row() { Image($r('app.media.ic_default')) ... Text(this.content) ... } ...
-
Column布局
Column() { Text($r('待办')) ... ForEach(this.totalTasks,(item) => { TodoItem({content:item}) },...) }
3.3.5 改变组件状态
实际开发中由于交互,页面的内容可能需要产生变化,以每一个ToDoItem为例,其在完成时的状态与未完成时的展示效果是不一样的。
-
不同状态的视图
声明式UI的特点就是UI是随数据更改而自动刷新的,我们这里定义了一个类型为boolean的变量isComplete,其被@State装饰后,框架内建立了数据和视图之间的绑定,其值的改变影响UI的显示。@State isComplete : boolean = false;
-
@State装饰器的作用
用圆圈和对勾这样两个图片,分别来表示该项是否完成,这部分涉及到内容的切换,需要使用条件渲染if / else语法来进行组件的显示与消失,当判断条件为真时,组件为已完成的状态,反之则为未完成。if (this.isComplete) { Image($r('app.media.ic_ok')) .objectFit(ImageFit.Contain) .width($r('app.float.checkbox_width')) .height($r('app.float.checkbox_width')) .margin($r('app.float.checkbox_margin')) } else { Image($r('app.media.ic_default')) .objectFit(ImageFit.Contain) .width($r('app.float.checkbox_width')) .height($r('app.float.checkbox_width')) .margin($r('app.float.checkbox_margin')) }
由于两个Image的实现具有大量重复代码,ArkTS提供了@Builder装饰器,来修饰一个函数,快速生成布局内容,从而可以避免重复的UI描述内容。这里使用@Bulider声明了一个labelIcon的函数,参数为url,对应要传给Image的图片路径。
@Builder labelIcon(url) {
Image(url)
.objectFit(ImageFit.Contain)
.width($r('app.float.checkbox_width'))
.height($r('app.float.checkbox_width'))
.margin($r('app.float.checkbox_margin'))
}
使用时只需要使用this关键字访问@Builder装饰的函数名,即可快速创建布局。
if (this.isComplete) {
this.labelIcon($r('app.media.ic_ok'))
} else {
this.labelIcon($r('app.media.ic_default'))
}
为了让待办项带给用户的体验更符合已完成的效果,给内容的字体也增加了相应的样式变化,这里使用了三目运算符来根据状态变化修改其透明度和文字样式,如opacity是控制透明度,decoration是文字是否有划线。通过isComplete的值来控制其变化。
Text(this.content)
...
.opacity(this.isComplete ? CommonConstants.OPACITY_COMPLETED : CommonConstants.OPACITY_DEFAULT)
.decoration({ type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None })
最后,为了实现与用户交互的效果,在组件上添加了onClick点击事件,当用户点击该待办项时,数据isComplete的更改就能够触发UI的更新。
@Component
struct ToDoItem {
@State isComplete : boolean = false;
@Builder labelIcon(icon) {...}
...
build() {
Row() {
if (this.isComplete) {
this.labelIcon($r('app.media.ic_ok'))
} else {
this.labelIcon($r('app.media.ic_default'))
}
...
}
...
.onClick(() => {
this.isComplete= !this.isComplete;
})
}
}
3.3.6 循环渲染列表数据
刚刚只是完成了一个ToDoItem组件的开发,当我们有多条待办数据需要显示在页面时,就需要使用到ForEach循环渲染语法。
例如这里我们有五条待办数据需要展示在页面上。
total_Tasks:Array<string> = [
'早起晨练',
'准备早餐',
'阅读名著',
'学习ArkTS',
'看剧放松'
]
ForEach基本使用中,只需要了解要渲染的数据以及要生成的UI内容两个部分,例如这里要渲染的数组为以上的五条待办事项,要渲染的内容是ToDoItem这个自定义组件,也可以是其他内置组件。
- ForEach基本使用
ToDoItem这个自定义组件中,每一个ToDoItem要显示的文本参数content需要外部传入,参数传递使用花括号的形式,用content接受数组内的内容项item。
最终完成的代码及其效果如下:
@Entry
@Component
struct ToDoList {
...
build() {
Row() {
Column() {
Text(...)
...
ForEach(this.totalTasks,(item) => {
TodoItem({content:item})
},...)
}
.width('100%')
}
.height('100%')
}
}
├──entry/src/main/ets // 代码区
│ ├──common
│ │ └──constants
│ │ └──CommonConstants.ets // 公共常量类
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口类
│ ├──pages
│ │ └──ToDoListPage.ets // 主页面
│ ├──view
│ │ └──ToDoItem.ets // 自定义单项待办组件
│ └──viewmodel
│ └──DataModel.ets // 列表数据获取文件
└──entry/src/main/resources // 资源文件目录
四、UIAbility应用程序入口
4.1 概述
UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互。UIAbility也是系统调度的单元,为应用提供窗口在其中绘制界面。
每一个UIAbility实例,都对应于一个最近任务列表中的任务。
一个应用可以有一个UIAbility,也可以有多个UIAbility,如下图所示。例如浏览器应用可以通过一个UIAbility结合多页面的形式让用户进行的搜索和浏览内容;而聊天应用增加一个“外卖功能”的场景,则可以将聊天应用中“外卖功能”的内容独立为一个UIAbility,当用户打开聊天应用的“外卖功能”,查看外卖订单详情,此时有新的聊天消息,即可以通过最近任务列表切换回到聊天窗口继续进行聊天对话。
一个UIAbility可以对应于多个页面,建议将一个独立的功能模块放到一个UIAbility中,以多页面的形式呈现。例如新闻应用在浏览内容的时候,可以进行多页面的跳转使用。
- 单UIAbility应用和多UIAbility应用
dfd33e3aa245f2b5e3d8b225bafd52.png)
4.2 UIAbility的页面创建
- 新建页面
根据业务需要实现Second页面的功能
4.3 UIAbility内页面的跳转和数据传递
UIAbility的数据传递包括有UIAbility内页面的跳转和数据传递、UIAbility间的数据跳转和数据传递
在一个应用包含一个UIAbility的场景下,可以通过新建多个页面来实现和丰富应用的内容。这会涉及到UIAbility内页面的新建以及UIAbility内页面的跳转和数据传递。
打开DevEco Studio,选择一个Empty Ability工程模板,创建一个工程,例如命名为MyApplication。
- 在
src/main/ets/entryability
目录下,初始会生成一个UIAbility文件EntryAbility.ts。可以在EntryAbility.ts文件中根据业务需要实现UIAbility的生命周期回调内容。 - 在
src/main/ets/pages
目录下,会生成一个Index页面。这也是基于UIAbility实现的应用的入口页面。可以在Index页面中根据业务需要实现入口页面的功能。 - 在
src/main/ets/pages
目录下,右键New->Page,新建一个Second页面,用于实现页面间的跳转和数据传递。
为了实现页面的跳转和数据传递,需要新建一个页面。在原有Index页面的基础上,新建一个页面,例如命名为Second.ets。
页面间的导航可以通过页面路由router模块来实现。页面路由模块根据页面url找到目标页面,从而实现跳转。通过页面路由模块,可以使用不同的url访问不同的页面,包括跳转到UIAbility内的指定页面、用UIAbility内的某个页面替换当前页面、返回上一页面或指定的页面等。具体使用方法请参考ohos.router (页面路由)。
4.3.1 页面跳转与参数接收
在使用页面路由之前,需要先导入router模块,如下代码所示。
import router from '@ohos.router';
页面跳转的几种方式,根据需要选择一种方式跳转即可。
-
方式一:API9及以上,router.pushUrl()方法新增了mode参数,可以将mode参数配置为
router.RouterMode.Single单实例
模式和router.RouterMode.Standard多实例
模式。默认开启的模式为Standard
在单实例模式下:如果目标页面的url在页面栈中已经存在
同url页面,离栈顶最近同url页面会被移动到栈顶,移动后的页面为新建页,原来的页面仍然存在栈中,页面栈的元素数量不变;如果目标页面的url在页面栈中不存在
同url页面,按多实例模式跳转,页面栈的元素数量会加1。当页面栈的元素数量较大或者超过32时,可以通过调用router.clear()方法清除页面栈中的所有历史页面,仅保留当前页面作为栈顶页面。
router.pushUrl({ url: 'pages/Second', params: { src: 'Index页面传来的数据', } }, router.RouterMode.Single)
-
方式二:API9及以上,router.replaceUrl()方法新增了mode参数,可以将mode参数配置为
router.RouterMode.Single单实例模式
和router.RouterMode.Standard多实例模式
。
在单实例模式下:如果目标页面的url在页面栈中已经存在
同url页面,离栈顶最近同url页面会被移动到栈顶,替换当前页面,并销毁被替换的当前页面,移动后的页面为新建页,页面栈的元素数量会减1;如果目标页面的url在页面栈中不存在
同url页面,按多实例模式跳转,页面栈的元素数量不变。router.replaceUrl({ url: 'pages/Second', params: { src: 'Index页面传来的数据', } }, router.RouterMode.Single)
已经实现了页面的跳转,接下来,在Second页面中如何进行自定义参数的接收呢?
通过调用router.getParams()方法获取Index页面传递过来的自定义参数。
import router from '@ohos.router';
@Entry
@Component
struct Second {
@State src: string = (router.getParams() as Record<string, string>)['src'];
// 页面刷新展示
// ...
}
在Index页面中,点击“Next”后,即可从Index页面跳转到Second页面,并在Second页面中接收参数和进行页面刷新展示。
- Index页面跳转到Second页面
4.3.2 页面返回和参数接收
经常还会遇到一个场景,在Second页面中,完成了一些功能操作之后,希望能返回到Index页面,那我们要如何实现呢?
在Second页面中,可以通过调用router.back()方法实现返回到上一个页面,或者在调用router.back()方法时增加可选的options参数(增加url参数)返回到指定页面。
- 调用router.back()返回的目标页面需要在页面栈中存在才能正常跳转。
- 例如调用router.pushUrl()方法跳转到Second页面,在Second页面可以通过调用router.back()方法返回到上一个页面。
- 例如调用router.clear()方法清空了页面栈中所有历史页面,仅保留当前页面,此时则无法通过调用router.back()方法返回到上一个页面。
-
返回上一个页面。
router.back();
-
返回到指定页面。
router.back({ url: 'pages/Index' });
效果示意如下图所示。在Second页面中,点击“Back”后,即可从Second页面返回到Index页面。
页面返回可以根据业务需要增加一个询问对话框。
即在调用router.back()方法之前,可以先调用router.enableBackPageAlert()
方法开启页面返回询问对话框功能。
router.enableBackPageAlert()
方法开启页面返回询问对话框功能,只针对当前页面生效。例如在调用router.pushUrl()或者router.replaceUrl()方法,跳转后的页面均为新建页面,因此在页面返回之前均需要先调用router.enableBackPageAlert()方法之后,页面返回询问对话框功能才会生效。- 如需关闭页面返回询问对话框功能,可以通过调用
router.disableAlertBeforeBackPage()
方法关闭该功能即可。
router.enableBackPageAlert({
message: 'Message Info'
});
router.back();
在Second页面中,调用router.back()方法返回上一个页面或者返回指定页面时,根据需要继续增加自定义参数,例如在返回时增加一个自定义参数src。
router.back({
url: 'pages/Index',
params: {
src: 'Second页面传来的数据',
}
})
从Second页面返回到Index页面。在Index页面通过调用router.getParams()方法,获取Second页面传递过来的自定义参数。
调用router.back()方法,不会新建页面,返回的是原来的页面,在原来页面中@State声明的变量不会重复声明,以及也不会触发页面的aboutToAppear()生命周期回调,因此无法直接在变量声明以及页面的aboutToAppear()生命周期回调中接收和解析router.back()传递过来的自定义参数。
可以放在业务需要的位置进行参数解析。示例代码在Index页面中的onPageShow()生命周期回调中进行参数的解析。
import router from '@ohos.router';
class routerParams {
src:string
constructor(str:string) {
this.src = str
}
}
@Entry
@Component
struct Index {
@State src: string = '';
onPageShow() {
this.src = (router.getParams() as routerParams).src
}
// 页面刷新展示
// ...
}
在Second页面中,点击“Back”后,即可从Second页面返回到Index页面,并在Index页面中接收参数和进行页面刷新展示。
4.4 UIAbility的生命周期
当用户浏览、切换和返回到对应应用的时候,应用中的UIAbility实例会在其生命周期的不同状态之间转换。
UIAbility类提供了很多回调,通过这些回调可以知晓当前UIAbility的某个状态已经发生改变:例如UIAbility的创建和销毁,或者UIAbility发生了前后台的状态切换。
例如从桌面点击图库应用图标,到启动图库应用,应用的状态经过了从创建到前台展示的状态变化。
-
从桌面点击图库应用图标启动应用
回到桌面,从最近任务列表,切换回到图库应用,应用的状态经过了从后台到前台展示的状态变化 -
从最近任务列表切换回到图库应用
在UIAbility的使用过程中,会有多种生命周期状态。掌握UIAbility的生命周期,对于应用的开发非常重要。为了实现多设备形态上的裁剪和多窗口的可扩展性,系统对组件管理和窗口管理进行了解耦。UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态,
WindowStageCreate
和WindowStageDestroy
为窗口管理器
(WindowStage)在UIAbility中管理UI界面功能的两个生命周期回调,从而实现UIAbility与窗口之间的弱耦合。 -
UIAbility生命周期状态
- Create状态,在UIAbility实例创建时触发,系统会调用onCreate回调。可以在onCreate回调中进行相关初始化操作。
import UIAbility from '@ohos.app.ability.UIAbility'; import window from '@ohos.window'; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { // 应用初始化 // ... } // ... }
例如用户打开电池管理应用,在应用加载过程中,在UI页面可见之前,可以在onCreate回调中读取当前系统的电量情况,用于后续的UI页面展示
-
UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。每一个UIAbility实例都对应持有一个WindowStage实例
WindowStage为本地窗口管理器,用于管理窗口相关的内容,例如与界面相关的获焦/失焦、可见/不可见。
可以在onWindowStageCreate回调中,设置UI页面加载、设置WindowStage的事件订阅。
在onWindowStageCreate(windowStage)中通过loadContent接口设置应用要加载的页面,Window接口的使用详见窗口开发指导。
import UIAbility from '@ohos.app.ability.UIAbility'; import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onWindowStageCreate(windowStage: window.WindowStage) { // 设置UI页面加载 // 设置WindowStage的事件订阅(获焦/失焦、可见/不可见) // ... windowStage.loadContent('pages/Index', (err, data) => { // ... }); } // ... }
例如用户打开游戏应用,正在打游戏的时候,有一个消息通知,打开消息,消息会以弹窗的形式弹出在游戏应用的上方,此时,游戏应用就从获焦切换到了失焦状态,消息应用切换到了获焦状态。对于消息应用,在onWindowStageCreate回调中,会触发获焦的事件回调,可以进行设置消息应用的背景颜色、高亮等操作。
-
Foreground和Background状态,分别在UIAbility切换至前台或者切换至后台时触发。
分别对应于onForeground回调和onBackground回调。onForeground回调,在UIAbility的UI页面可见之前,即UIAbility切换至前台时触发。可以在onForeground回调中申请系统需要的资源,或者重新申请在onBackground中释放的资源。
onBackground回调,在UIAbility的UI页面完全不可见之后,即UIAbility切换至后台时候触发。可以在onBackground回调中释放UI页面不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等。
import UIAbility from '@ohos.app.ability.UIAbility'; import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onForeground() { // 申请系统需要的资源,或者重新申请在onBackground中释放的资源 // ... } onBackground() { // 释放UI页面不可见时无用的资源,或者在此回调中执行较为耗时的操作 // 例如状态保存等 // ... } }
例如用户打开地图应用查看当前地理位置的时候,假设地图应用已获得用户的定位权限授权。在UI页面显示之前,可以在onForeground回调中打开定位功能,从而获取到当前的位置信息。
当地图应用切换到后台状态,可以在onBackground回调中停止定位功能,以节省系统的资源消耗。
-
前面我们了解了UIAbility实例创建时的onWindowStageCreate回调的相关作用。
对应于onWindowStageCreate回调。在UIAbility实例销毁之前,则会先进入onWindowStageDestroy回调,我们可以在该回调中释放UI页面资源。import UIAbility from '@ohos.app.ability.UIAbility'; import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onWindowStageDestroy() { // 释放UI页面资源 // ... } }
例如在onWindowStageCreate中设置的获焦/失焦等WindowStage订阅事件。
-
Destroy状态,在UIAbility销毁时触发。可以在onDestroy回调中进行系统资源的释放、数据的保存等操作。
import UIAbility from '@ohos.app.ability.UIAbility'; import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onDestroy() { // 系统资源的释放、数据的保存等 // ... } }
例如用户使用应用的程序退出功能,会调用UIAbilityContext的terminalSelf()方法,从而完成UIAbility销毁。或者用户使用最近任务列表关闭该UIAbility实例时,也会完成UIAbility的销毁。
4.5 UIAbility的启动模式
对于浏览器或者新闻等应用,用户在打开该应用,并浏览访问相关内容后,回到桌面,再次打开该应用,显示的仍然是用户当前访问的界面。
对于应用的分屏操作,用户希望使用两个不同应用(例如备忘录应用和图库应用)之间进行分屏,也希望能使用同一个应用(例如备忘录应用自身)进行分屏。
对于文档应用,用户从文档应用中打开一个文档内容,回到文档应用,继续打开同一个文档,希望打开的还是同一个文档内容。
基于以上场景的考虑,UIAbility当前支持singleton(单实例模式)、multiton(多实例模式)和specified(指定实例模式)3种启动模式。
对启动模式的详细说明如下:
-
singleton(单实例模式)
singleton启动模式为单实例模式,也是默认情况下的启动模式。每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例。
应用的UIAbility实例已创建,该UIAbility配置为单实例模式,再次调用startAbility()方法启动该UIAbility实例。由于启动的还是原来的UIAbility实例,并未重新创建一个新的UIAbility实例,此时只会进入该UIAbility的onNewWant()回调,不会进入其onCreate()和onWindowStageCreate()生命周期回调。
singleton启动模式的开发使用,在module.json5文件中的“launchType”字段配置为“
singleton
”即可。{ "module": { // ... "abilities": [ { "launchType": "singleton", // ... } ] } }
-
multiton(多实例模式)
multiton启动模式为多实例模式,每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。这种情况下可以将UIAbility配置为multiton(多实例模式)。
multiton启动模式的开发使用,在module.json5配置文件中的launchType字段配置为multiton
即可。{ "module": { // ... "abilities": [ { "launchType": "multiton", // ... } ] } }
-
specified(指定实例模式)
specified启动模式为指定实例模式,针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)。
例如有两个UIAbility:EntryAbility和SpecifiedAbility,SpecifiedAbility配置为指定实例模式启动,需要从EntryAbility的页面中启动SpecifiedAbility。- 在SpecifiedAbility中,将module.json5配置文件的launchType字段配置为
specified
。
{ "module": { // ... "abilities": [ { "launchType": "specified", // ... } ] } }
- 在创建UIAbility实例之前,开发者可以为该实例指定一个唯一的字符串Key,这样在调用startAbility()方法时,应用就可以根据指定的Key来识别响应请求的UIAbility实例。在EntryAbility中,调用startAbility()方法时,可以在want参数中增加一个自定义参数,例如instanceKey,以此来区分不同的UIAbility实例。
// 在启动指定实例模式的UIAbility时,给每一个 UIAbility实例配置一个独立的Key标识 // 例如在文档使用场景中,可以用文档路径作为Key标识 import common from '@ohos.app.ability.common'; import Want from '@ohos.app.ability.Want'; import { BusinessError } from '@ohos.base'; function getInstance() { return 'key'; } let context:common.UIAbilityContext = ...; // context为调用方UIAbility的UIAbilityContext let want: Want = { deviceId: '', // deviceId为空表示本设备 bundleName: 'com.example.myapplication', abilityName: 'SpecifiedAbility', moduleName: 'specified', // moduleName非必选 parameters: { // 自定义信息 instanceKey: getInstance(), }, } context.startAbility(want).then(() => { console.info('Succeeded in starting ability.'); }).catch((err: BusinessError) => { console.error(`Failed to start ability. Code is ${err.code}, message is ${err.message}`); })
-
由于SpecifiedAbility的启动模式被配置为指定实例启动模式,因此在SpecifiedAbility启动之前,会先进入对应的AbilityStage的onAcceptWant()生命周期回调中,以获取该UIAbility实例的Key值。然后系统会自动匹配,如果存在与该UIAbility实例匹配的Key,则会启动与之绑定的UIAbility实例,并进入该UIAbility实例的onNewWant()回调函数;否则会创建一个新的UIAbility实例,并进入该UIAbility实例的onCreate()回调函数和onWindowStageCreate()回调函数。
示例代码中,通过实现onAcceptWant()生命周期回调函数,解析传入的want参数,获取自定义参数instanceKey。业务逻辑会根据这个参数返回一个字符串Key,用于标识当前UIAbility实例。如果返回的Key已经对应一个已启动的UIAbility实例,系统会将该UIAbility实例拉回前台并获焦,而不会创建新的实例。如果返回的Key没有对应已启动的UIAbility实例,则系统会创建新的UIAbility实例并启动。
import AbilityStage from '@ohos.app.ability.AbilityStage'; import Want from '@ohos.app.ability.Want'; export default class MyAbilityStage extends AbilityStage { onAcceptWant(want: Want): string { // 在被调用方的AbilityStage中,针对启动模式为specified的UIAbility返回一个UIAbility实例对应的一个Key值 // 当前示例指的是module1 Module的SpecifiedAbility if (want.abilityName === 'SpecifiedAbility') { // 返回的字符串Key标识为自定义拼接的字符串内容 if (want.parameters) { return `SpecifiedAbilityInstance_${want.parameters.instanceKey}`; } } return ''; } }
- 在SpecifiedAbility中,将module.json5配置文件的launchType字段配置为
- 当应用的UIAbility实例已经被创建,并且配置为指定实例模式时,如果再次调用startAbility()方法启动该UIAbility实例,且AbilityStage的onAcceptWant()回调匹配到一个已创建的UIAbility实例,则系统会启动原来的UIAbility实例,并且不会重新创建一个新的UIAbility实例。此时,该UIAbility实例的onNewWant()回调会被触发,而不会触发onCreate()和onWindowStageCreate()生命周期回调。
- DevEco Studio默认工程中未自动生成AbilityStage,AbilityStage文件的创建请参见AbilityStage组件容器。
例如在文档应用中,可以为不同的文档实例内容绑定不同的Key值。每次新建文档时,可以传入一个新的Key值(例如可以将文件的路径作为一个Key标识),此时AbilityStage中启动UIAbility时都会创建一个新的UIAbility实例;当新建的文档保存之后,回到桌面,或者新打开一个已保存的文档,回到桌面,此时再次打开该已保存的文档,此时AbilityStage中再次启动该UIAbility时,打开的仍然是之前原来已保存的文档界面。
- 打开文件A,对应启动一个新的UIAbility实例,例如启动UIAbility实例1。
- 在最近任务列表中关闭文件A的任务进程,此时UIAbility实例1被销毁,回到桌面,再次打开文件A,此时对应启动一个新的UIAbility实例,例如启动UIAbility实例2。
- 回到桌面,打开文件B,此时对应启动一个新的UIAbility实例,例如启动UIAbility实例3。
- 回到桌面,再次打开文件A,此时仍然启动之前的UIAbility实例2,因为系统会自动匹配UIAbility实例的Key值,如果存在与之匹配的Key,则会启动与之绑定的UIAbility实例。在此例中,之前启动的UIAbility实例2与文件A绑定的Key是相同的,因此系统会拉回UIAbility实例2并让其获焦,而不会创建新的实例。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!