1 Stage模型应用程序包结构
在开发态,一个应用包含一个或者多个Module,可以在DevEco Studio工程中创建一个或者多个Module。Module是HarmonyOS应用/服务的基本功能单元,包含了源代码、资源文件、第三方库及应用/服务配置文件,每一个Module都可以独立进行编译和运行。Module分为“Ability”和“Library”两种类型,“Ability”类型的Module对应于编译后的HAP(Harmony Ability Package);“Library”类型的Module对应于HAR(Harmony Archive),或者HSP(Harmony Shared Package),HAR相当于Android中的lib下的jar或者aar。HSP相当于通过gradle引入第三方包。一个Module可以包含一个或多个UIAbility组件。
Module与UIAbility组件关系示意图
开发者通过DevEco Studio把应用程序编译为一个或者多个.hap后缀的文件,即HAP。HAP是HarmonyOS应用安装的基本单位,包含了编译后的代码、资源、三方库及配置文件。HAP可分为Entry和Feature两种类型。
- Entry类型的HAP:是应用的主模块,在module.json5配置文件中的type标签配置为“entry”类型。在同一个应用中,同一设备类型只支持一个Entry类型的HAP,通常用于实现应用的入口界面、入口图标、主特性功能等。
- Feature类型的HAP:是应用的动态特性模块,在module.json5配置文件中的type标签配置为“feature”类型。一个应用程序包可以包含一个或多个Feature类型的HAP,也可以不包含;Feature类型的HAP通常用于实现应用的特性功能,可以配置成按需下载安装,也可以配置成随Entry类型的HAP一起下载安装(请参见module对象内部结构中的“deliveryWithInstall”)。
每个HarmonyOS应用可以包含多个.hap文件,一个应用中的.hap文件合在一起称为一个Bundle,而bundleName就是应用的唯一标识,在应用上架到应用市场时,需要把应用包含的所有.hap文件(即Bundle)打包为一个.app后缀的文件用于上架,这个.app文件称为App Pack(Application Package),其中同时包含了描述App Pack属性的pack.info文件;在云端(服务器)分发和终端设备安装时,都是以HAP为单位进行分发和安装的。打包后的HAP包结构包括ets、libs、resources等文件夹和resources.index、module.json、pack.info等文件。
- ets目录用于存放应用代码编译后的字节码文件。
- libs目录用于存放库文件。库文件是HarmonyOS应用依赖的第三方代码(.so二进制文件)。
- resources目录用于存放应用的资源文件(字符串、图片等),便于开发者使用和维护,详见资源分类与访问。
- resources.index是资源索引表,由IDE编译工程时生成。
- module.json是HAP的配置文件,内容由工程配置中的module.json5和app.json5组成,该文件是HAP中必不可少的文件。IDE会自动生成一部分默认配置,开发者按需修改其中的配置。详细字段请参见应用配置文件。
- pack.info是Bundle中用于描述每个HAP属性的文件,例如app中的bundleName和versionCode信息、module中的name、type和abilities等信息,由IDE工具生成Bundle包时自动生成。
应用程序包结构(Stage模型)
资源访问
应用开发过程中,经常需要用到颜色、字体、间距、图片等资源,在不同的设备或配置中,这些资源的值可能不同。
1、应用资源:借助资源文件能力,开发者在应用中自定义资源,自行管理这些资源在不同的设备或配置中的表现。
2、系统资源:开发者直接使用系统预置的资源定义(即分层参数,同一资源ID在设备类型、深浅色等不同配置下有不同的取值)
base目录
base目录是默认存在的目录,二级子目录element用于存放字符串、颜色、布尔值等基础元素,media、profile存放媒体、动画、布局等资源文件。
目录中的资源文件会被编译成二进制文件,并赋予资源文件ID。通过指定资源类型(type)和资源名称(name)引用。
限定词目录
en_US和zh_CN是默认存在的两个限定词目录,其余限定词目录需要开发者根据开发需要自行创建。二级子目录element、media、profile用于存放字符串、颜色、布尔值等基础元素,以及媒体、动画、布局等资源文件。
同样,目录中的资源文件会被编译成二进制文件,并赋予资源文件ID。通过指定资源类型(type)和资源名称(name)来引用。
限定词目录的命名要求
限定词目录可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度,限定词之间通过下划线(_)或者中划线(-)连接。开发者在创建限定词目录时,需要遵守限定词目录的命名规则。
限定词的组合顺序:
_移动国家码_移动网络码-语言_文字_国家或地区-横竖屏-设备类型-颜色模式-屏幕密度_。开发者可以根据应用的使用场景和设备特征,选择其中的一类或几类限定词组成目录名称。
限定词的连接方式:
语言、文字、国家或地区之间采用下划线(_)连接,移动国家码和移动网络码之间也采用下划线(_)连接,除此之外的其他限定词之间均采用中划线(-)连接。例如:zh_Hant_CN、zh_CN-car-ldpi。
页面和自定义组件生命周期
自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
- aboutToDisappear:在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
生命周期流程如下图所示,下图展示的是被@Entry装饰的组件(页面)生命周期。
应用程序包更新流程
HarmonyOS包管理服务提供了应用程序包更新能力,更新方式如下。
- 应用市场内更新:新版本应用通过应用市场上架后,应用市场通知终端用户该应用有新版本,终端用户可以根据通知到应用市场(客户端)进行应用升级。
- 应用内检测升级:终端用户启动应用时,应用市场检测到该应用有新版本会通知终端用户,可以到应用市场进行应用的下载更新。
您正在浏览HarmonyOS开发者3.1/4.0版本配套的开发者文档,对应API能力级别为API 9 Release,详细版本配套关系请参考版本说明。
创建自定义组件
在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。只有@entry声明的组件才有页面生命周期
@Entry
@Component
struct ParentComponent {
build() {
Column() {
Text('ArkUI message')
HelloComponent({ message: 'Hello, World!' });
Divider()
HelloComponent({ message: '你好!' });
}
}
}
2 H5和鸿蒙通信
为了兼容当前H5使用的dsbridge框架,鸿蒙端使用DSBridge-HarmonyOS库和H5进行通信
3 装饰器
全局的装饰器需要使用function关键字,组件内的装饰器不需要使用function
3.1 @Builder装饰器
又叫自定义构建函数,将重复使用的UI元素抽象成一个方法,在build方法里调用。自定义构建函数的参数传递有按值传递和按引用传递两种(默认按值传递)。如果一个变量用@state修饰,变量值改变需要刷新UI,那么需要使用按引用传递。如果不需要刷新UI可以使用按值传递
按值传递
@Builder function overBuilder(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
overBuilder(this.label)
}
}
}
按引用传递
@Builder function overBuilder($$: { paramA1: string }) {
Row() {
Text(`UseStateVarByReference: ${$$.paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
// 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder
overBuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
// 点击“Click me”后,UI从“Hello”刷新为“ArkUI”
this.label = 'ArkUI';
})
}
}
}
3.2 @BuilderParam装饰器
用来装饰指向@Builder方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。@BuilderParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化
@Builder function overBuilder($$ : {label: string }) {
Text($$.label)
.width(400)
.height(50)
.backgroundColor(Color.Green)
}
@Component
struct Child {
label: string = 'Child'
// 无参数类型,指向的componentBuilder也是无参数类型
@BuilderParam customBuilderParam: () => void;
// 有参数类型,指向的GlobalBuilder1也是有参数类型的方法
@BuilderParam customOverBuilderParam: ($$ : { label : string}) => void;
build() {
Column() {
this.customBuilderParam()
this.customOverBuilderParam({label: 'global Builder label' } )
}
}
}
@Entry
@Component
struct Parent {
label: string = 'Parent'
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ customBuilderParam: this.componentBuilder, customOverBuilderParam: overBuilder })
}
}
}
3.3 @Styles装饰器
定义组件重用样式。@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。通过@Styles装饰器可以快速定义并复用自定义样式。
- 组件内@Styles的优先级高于全局@Styles。
- 框架优先找当前组件内的@Styles,如果找不到,则会全局查找
// 全局
@Styles function functionName() { ... }
// 在组件内
@Component
struct FancyUse {
@Styles fancy() {
.height(100)
}
}
使用场景
// 定义在全局的@Styles封装的样式
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@State heightValue: number = 100
// 定义在组件内的@Styles封装的样式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
build() {
Column({ space: 10 }) {
// 使用全局的@Styles封装的样式
Text('FancyA')
.globalFancy()
.fontSize(30)
// 使用组件内的@Styles封装的样式
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
3.4 @Extend装饰器
用于扩展原生组件样式,仅支持定义在全局
// @Extend(Text)可以支持Text的私有属性fontColor
@Extend(Text) function fancy () {
.fontColor(Color.Red)
}
// superFancyText可以调用预定义的fancy。组件内的fancy在“3.3 @Styles装饰器”里声明过
@Extend(Text) function superFancyText(size:number) {
.fontSize(size)
.fancy()
}
使用场景
以下示例声明了3个Text组件,每个Text组件均设置了fontStyle、fontWeight和backgroundColor样式
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(100)
.backgroundColor(Color.Blue)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(200)
.backgroundColor(Color.Pink)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(300)
.backgroundColor(Color.Orange)
}.margin('20%')
}
}
@Extend将样式组合复用
@Extend(Text) function fancyText(weightValue: number, color: Color) {
.fontStyle(FontStyle.Italic)
.fontWeight(weightValue)
.backgroundColor(color)
}
通过@Extend组合样式
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fancyText(100, Color.Blue)
Text(`${this.label}`)
.fancyText(200, Color.Pink)
Text(`${this.label}`)
.fancyText(300, Color.Orange)
}.margin('20%')
}
}
4 stateStyles
多态样式。@Styles和@Extend仅仅应用于静态页面的样式复用,stateStyles可以依据组件的内部状态的不同,快速设置不同样式。组件状态分为
- focused:获焦态。
- normal:正常态。
- pressed:按压态。
- disabled:不可用态
使用场景
@Entry
@Component
struct StateStylesSample {
build() {
Column() {
Button('Button1')
.stateStyles({
focused: {
.backgroundColor(Color.Pink)
},
pressed: {
.backgroundColor(Color.Black)
},
normal: {
.backgroundColor(Color.Red)
}
})
.margin(20)
Button('Button2')
.stateStyles({
focused: {
.backgroundColor(Color.Pink)
},
pressed: {
.backgroundColor(Color.Black)
},
normal: {
.backgroundColor(Color.Red)
}
})
}.margin('30%')
}
}
5 状态管理
5.1 @State装饰器
- 组件内状态同步,当状态改变时UI会发生对应的渲染改变。@State装饰的变量叫做状态变量,必须初始化。
- 不管是基本数据类型还是引用类型,只能观察到一层数据的变化
- 父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰的变量,可以初始化子组件中@State装饰的变量
- 父组件中@State装饰的变量,可以初始化子组件中常规变量、@State、@Link、@Prop和@Provide装饰的变量
使用场景
- 自定义组件MyComponent定义了被@State装饰的状态变量count和title,其中title的类型为自定义类Model。如果count或title的值发生变化,则查询MyComponent中使用该状态变量的UI组件,并进行重新渲染。
- EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent。
class Model {
public value: string;
constructor(value: string) {
this.value = value;
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
// 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化
MyComponent({ count: 1, increaseBy: 2 })
.width(300)
MyComponent({ title: new Model('Hello World 2'), count: 7 })
}
}
}
@Component
struct MyComponent {
@State title: Model = new Model('Hello World');
@State count: number = 0;
private increaseBy: number = 1;
build() {
Column() {
Text(`${this.title.value}`)
.margin(10)
Button(`Click to change title`)
.onClick(() => {
// @State变量的更新将触发上面的Text组件内容更新
this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';
})
.width(300)
.margin(10)
Button(`Click to increase count = ${this.count}`)
.onClick(() => {
// @State变量的更新将触发该Button组件的内容更新
this.count += this.increaseBy;
})
.width(300)
.margin(10)
}
}
}
5.2 @Prop装饰器
- 父向子单向同步,当状态改变时UI会发生对应的渲染改变。@Prop装饰器不能在@Entry装饰的自定义组件中使用。不可以在子组件中初始化(文档说可以,编译器说不可以)
- 不管是基本数据类型还是引用类型,只能观察到一层数据的变化
- 父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰的变量,可以初始化子组件中@Prop装饰的变量
- 父组件中@Prop装饰的变量,可以初始化子组件中常规变量、@State、@Link、@Prop、@Provide装饰的变量
使用场景
如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其它读者用户。从代码角度讲,对@Prop图书对象的本地更改不会同步给图书馆组件中的@State图书对象。
class Book {
public title: string;
public pages: number;
public readIt: boolean = false;
constructor(title: string, pages: number) {
this.title = title;
this.pages = pages;
}
}
@Component
struct ReaderComp {
@Prop title: string;
@Prop readIt: boolean;
build() {
Row() {
Text(this.title)
Text(`... ${this.readIt ? 'I have read' : 'I have not read it'}`)
.onClick(() => this.readIt = true)
}
}
}
@Entry
@Component
struct Library {
@State book: Book = new Book('100 secrets of C++', 765);
build() {
Column() {
ReaderComp({ title: this.book.title, readIt: this.book.readIt })
ReaderComp({ title: this.book.title, readIt: this.book.readIt })
}
}
}
5.3 @Link装饰器
- 父向子双向同步,当状态改变时UI会发生对应的渲染改变。@Link装饰器不能在@Entry装饰的自定义组件中使用。不可以在子组件中初始化(文档说可以,编译器说不可以)
- 不管是基本数据类型还是引用类型,只能观察到一层数据的变化
- 父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰的变量,可以初始化子组件中@Link装饰的变量
- 父组件中@Link装饰的变量,可以初始化子组件中常规变量、@State、@Link、@Prop、@Provide装饰的变量
使用场景
1.点击子组件GreenButton和YellowButton中的Button,子组件会发生相应变化,将变化同步给父组件。因为@Link是双向同步,会将变化同步给@State。
2.当点击父组件ShufflingContainer中的Button时,@State变化,也会同步给@Link,子组件也会发生对应的刷新。
class GreenButtonState {
width: number = 0;
constructor(width: number) {
this.width = width;
}
}
@Component
struct GreenButton {
@Link greenButtonState: GreenButtonState;
build() {
Button('Green Button')
.width(this.greenButtonState.width)
.height(40)
.backgroundColor('#64bb5c')
.fontColor('#FFFFFF,90%')
.onClick(() => {
if (this.greenButtonState.width < 700) {
// 更新class的属性,变化可以被观察到同步回父组件
this.greenButtonState.width += 60;
} else {
// 更新class,变化可以被观察到同步回父组件
this.greenButtonState = new GreenButtonState(180);
}
})
}
}
@Component
struct YellowButton {
@Link yellowButtonState: number;
build() {
Button('Yellow Button')
.width(this.yellowButtonState)
.height(40)
.backgroundColor('#f7ce00')
.fontColor('#FFFFFF,90%')
.onClick(() => {
// 子组件的简单类型可以同步回父组件
this.yellowButtonState += 40.0;
})
}
}
@Entry
@Component
struct ShufflingContainer {
@State greenButtonState: GreenButtonState = new GreenButtonState(180);
@State yellowButtonProp: number = 180;
build() {
Column() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
// 简单类型从父组件@State向子组件@Link数据同步
Button('Parent View: Set yellowButton')
.width(312)
.height(40)
.margin(12)
.fontColor('#FFFFFF,90%')
.onClick(() => {
this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100;
})
// class类型从父组件@State向子组件@Link数据同步
Button('Parent View: Set GreenButton')
.width(312)
.height(40)
.margin(12)
.fontColor('#FFFFFF,90%')
.onClick(() => {
this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
})
// class类型初始化@Link
GreenButton({ greenButtonState: $greenButtonState }).margin(12)
// 简单类型初始化@Link
YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12)
}
}
}
}
5.4 @Provide装饰器和@Consume装饰器
- 与后代组件双向同步,可以跨层级传递
- @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
- 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
- @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
@Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide修饰的变量和@Consume修饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量。
使用场景
在下面的示例是与后代组件双向同步状态@Provide和@Consume场景。当分别点击CompA和CompD组件内Button时,reviewVotes 的更改会双向同步在CompA和CompD中。
@Component
struct CompD {
// @Consume装饰的变量通过相同的属性名绑定其祖先组件CompA内的@Provide装饰的变量
@Consume reviewVotes: number;
build() {
Column() {
Text(`reviewVotes(${this.reviewVotes})`)
Button(`reviewVotes(${this.reviewVotes}), give +1`)
.onClick(() => this.reviewVotes += 1)
}
.width('50%')
}
}
@Component
struct CompC {
build() {
Row({ space: 5 }) {
CompD()
CompD()
}
}
}
@Component
struct CompB {
build() {
CompC()
}
}
@Entry
@Component
struct CompA {
// @Provide装饰的变量reviewVotes由入口组件CompA提供其后代组件
@Provide reviewVotes: number = 0;
build() {
Column() {
Button(`reviewVotes(${this.reviewVotes}), give +1`)
.onClick(() => this.reviewVotes += 1)
CompB()
}
}
}
5.5 @Observed装饰器和@ObjectLink装饰器
- @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
- 被@Observed装饰的类,可以被观察到属性的变化;
- 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
- 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
- @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了
总结 @state 组件内同步,单层
@prop 父子单向同步,单层
@Link 父子双向同步,单层
@provide/@consume 父子子子双向同步,单层
@observed/@objectLink父子双向同步,多层
5.6 LocalStorage
- 页面级UI状态存储(存储在内存中)
- 页面内共享,通过@Entry接收一个LocalStorage实例,这个实例可以在页面内共享
- 页面间共享,在UIAbility实例内创建LocalStorage实例,实现页面间共享
使用步骤
①使用构造函数创建LocalStorage实例storage;
②使用@Entry装饰器将storage添加到LocalStoragePage顶层组件中;
③建立数据同步
@LocalStorageLink绑定LocalStorage对给定的属性,建立双向数据同步。
@LocalStorageProp绑定LocalStorage对给定的属性,建立单向数据同步
// 创建新实例并使用给定对象初始化
let storage = new LocalStorage({ name:'Tom' });
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct LocalStoragePage {
// @LocalStorageProp变量装饰器与LocalStorage中的'name'属性建立单向绑定
@LocalStorageProp('name') myName:string = ''
build() {
Row() {
Column() {
Text(this.myName)
.fontSize(50)
.fontWeight(FontWeight.Bold)
//点击按钮后,只改变当前组件显示的myName,不会同步到LocalStorage中
Button('更改名字').onClick(()=>{
this.myName = 'jack'
})
//子组件Child
Child()
}
.width('100%')
}
.height('100%')
}
}
@Component
struct Child {
// @LocalStorageProp变量装饰器与LocalStorage中的'name'属性建立单向绑定
@LocalStorageProp('name') myName:string = ''
build() {
Row(){
//当父组件改变时,子组件不会改变,myName显示 'Tom'
Text(`Child-${this.myName}`).fontSize(30)
}
}
}
5.7 AppStorage
- 特殊的单例LocalStorage对象。由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储(存储在内存中)
- @StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步
- @StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步
AppStorage.SetOrCreate('money', 50);
@Entry
@Component
struct AppStoragePage {
@StorageLink('money') storLink: number = 1;
build() {
Row() {
Column() {
Text(`AppStorage ${this.storLink}`)
.fontSize(30)
.onClick(() => this.storLink += 1)
}
.width('100%')
}
.height('100%')
}
}
5.8 PersistentStorage
- 持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。类似于安卓的SP,建议存储数据不超过2KB,同步操作(存储在磁盘中)
- 如果需要存储大量的数据,建议使用数据库api。
- 支持number, string, boolean, enum ,可以被JSON.stringify()和JSON.parse()重构的对象
- 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
- 不支持undefined 和 null
5.8.1 PersistProp
static PersistProp<T>(key: string, defaultValue: T): void
将AppStorage中key对应的属性持久化到文件中。该接口的调用通常在访问AppStorage之前。
PersistentStorage.PersistProp('money',50)
1、PersistentStorage中没有money属性,使用AppStorag中的money属性持久化,如果AppStorage
中没有money属性则使用默认值(50)持久化
2、PersistentStorage中有money属性,则将money属性的值写入AppStorag的money属性里
PersistentStorage.PersistProp('money',50)
@Entry
@Component
struct PersistentStoragePage {
@State message: string = 'Hello PersistentStoragePage'
@StorageLink('money') money:number = 10
build() {
Row() {
Column({space:10}) {
Text(this.message).fontSize(30)
// 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
Text(`money:${this.money}`).fontSize(30).fontWeight(FontWeight.Bold)
.onClick(()=>{
this.money += 1
})
}
.width('100%')
}
.height('100%')
}
}
5.9 Environment
- 设备环境查询。开发者如果需要应用程序运行的设备的环境参数,以此来作出不同的场景判断,比如多语言,暗黑模式等,需要用到Environment设备环境查询。
- Environment是ArkUI框架在应用程序启动时创建的单例对象。它为AppStorage提供了一系列描述应用程序运行状态的属性。Environment的所有属性都是不可变的(即应用不可写入),所有的属性都是简单类型。
// 将设备languageCode存入AppStorage中
Environment.EnvProp('languageCode', 'en');
@Entry
@Component
struct Index {
@StorageProp('languageCode') languageCode: string = 'en';
build() {
Row() {
Column() {
// 输出当前设备的languageCode
Text(this.languageCode)
}
}
}
}
5.10 @Watch装饰器
- 状态变量更改通知。@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用
@Component
struct TotalView {
@Prop @Watch('onCountUpdated') count: number;
@State total: number = 0;
// @Watch 回调
onCountUpdated(propName: string): void {
this.total += this.count;
}
build() {
Text(`Total: ${this.total}`)
}
}
@Entry
@Component
struct CountModifier {
@State count: number = 0;
build() {
Column() {
Button('add to basket')
.onClick(() => {
this.count++
})
TotalView({ count: this.count })
}
}
}
初步总结
- 页面内,页面间共享变量可以使用 AppStroage/LocalStorage/globalThis
- UIAbility与页面之间共享变量可以使用 EventHub(监听数据改变)/AppStroage/LocalStorage/globalThis
- 类似安卓中的sp存储数据使用首选项。
- 类似安卓中的room存储数据使用关系型数据库
6 渲染控制
6.1 if/else:条件渲染
- 每当if或else if条件语句中使用的状态变量发生变化时,条件语句都会更新并重新评估新的条件值。如果条件值评估发生了变化,这意味着需要构建另一个条件分支。此时ArkUI框架将:
- 删除所有以前渲染的(早期分支的)组件。
- 执行新分支的构造函数,将生成的子组件添加到其父组件中。
- 在以下示例中,如果count从0增加到1,那么if语句更新,条件count > 0将重新评估,评估结果将从false更改为true。因此,将执行条件为真分支的构造函数,创建一个Text组件,并将它添加到父组件Column中。如果后续count更改为0,则Text组件将从Column组件中删除。由于没有else分支,因此不会执行新的构造函数
@Entry
@Component
struct ViewA {
@State count: number = 0;
build() {
Column() {
Text(`count=${this.count}`)
if (this.count > 0) {
Text(`count is positive`)
.fontColor(Color.Green)
}
Button('increase count')
.onClick(() => {
this.count++;
})
Button('decrease count')
.onClick(() => {
this.count--;
})
}
}
}
6.2 ForEach:循环渲染
@Entry
@Component
struct Parent {
@State simpleList: Array<string> = ['one', 'two', 'three'];
build() {
Row() {
Column() {
ForEach(this.simpleList, (item: string) => {
ChildItem({ 'item': item } as Record<string, string>)
}, (item: string) => item)
}
.width('100%')
.height('100%')
}
.height('100%')
.backgroundColor(0xF1F3F5)
}
}
@Component
struct ChildItem {
@Prop item: string;
build() {
Text(this.item)
.fontSize(50)
}
}
6.3 LazyForEach:数据懒加载
- LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用
- 在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private originDataArray: string[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): string {
return this.originDataArray[index];
}
// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
// 通知LazyForEach组件需要重载所有子组件
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
// 通知LazyForEach组件需要在index对应索引处添加子组件
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
// 通知LazyForEach组件需要在index对应索引处删除该子组件
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 20; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Text(item).fontSize(50)
.onAppear(() => {
console.info("appear:" + item)
})
}.margin({ left: 10, right: 10 })
}
}, (item: string) => item)
}.cachedCount(5)
}
}
7 应用模型
7.1 UIAbility组件
- UIAbility组件是一种包含UI界面的应用组件,主要用于和用户交互。
- UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口;一个UIAbility组件中可以通过多个页面来实现一个功能模块。每一个UIAbility组件实例,都对应于一个最近任务列表中的任务。
使用代码
为使应用能够正常使用UIAbility,需要在module.json5配置文件的abilities标签中声明UIAbility的名称、入口、标签等相关信息。
{
"module": {
// ...
"abilities": [
{
"name": "EntryAbility", // UIAbility组件的名称
"srcEntry": "./ets/entryability/EntryAbility.ts", // UIAbility组件的代码路径
"description": "$string:EntryAbility_desc", // UIAbility组件的描述信息
"icon": "$media:icon", // UIAbility组件的图标
"label": "$string:EntryAbility_label", // UIAbility组件的标签
"startWindowIcon": "$media:icon", // UIAbility组件启动页面图标资源文件的索引
"startWindowBackground": "$color:start_window_background", // UIAbility组件启动页面背景颜色资源文件的索引
// ...
}
]
}
}
生命周期
UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI界面加载、设置WindowStage的事件订阅
启动模式
UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了三种启动模式:
- singleton(单实例模式)
- multiton(多实例模式)
- specified(指定实例模式)
8 开发工具
下载地址
DevEco Studio-HarmonyOS SDK下载和升级-华为开发者联盟
注意:开发工具默认从美国站点下载SDK,需要更改配置文件改为从中国站点下载SDK
window:
C:\Users\username\AppData\Roaming\Huawei\DevEcoStudio3.0\options,open country.region.xml ,modify countryregion name为“CN”
mac
/Users/username/Library/Application Support/Huawei/DevEcoStudio3.0/options,open country.region.xml ,modify countryregion name为“CN”
9 环境配置
hdc,nodejs,ohpm在初次安装DevEco Studio 3.1的时候会提示下载,记住安装路径
9.1 HDC
hdc对应安卓中的adb,调试的命令行工具
- 配置环境变量
export PATH=$PATH:/Users/zjiaxind/Library/Huawei/Sdk/hmscore/3.1.0/toolchains
export CLASSPATH
HDC_SERVER_PORT=7035
launchctl setenv HDC_SERVER_PORT $HDC_SERVER_PORT
export HDC_SERVER_PORT
9.2 Node.js
ohpm依赖nodejs执行,JavaScript语言的服务器运行环境,JavaScript可以脱离浏览器直接运行
- 配置环境变量
export NODE_HOME=/Users/zjiaxind/Library/NodejsHuawei16.19.1
export PATH=$NODE_HOME/bin:$PATH
9.3 Ohpm
鸿蒙生态三方库的包管理工具,支持OpenHarmony共享包的发布、安装和依赖管理
- 进入“ohpm/bin”目录,打开命令行工具,执行如下指令初始化ohpm。
Windows环境下执行:init.bat
Linux/macOS环境下执行:./init
- 配置环境变量
export OHPM_HOME=/Users/zjiaxind/Library/Huawei/ohpm
export PATH=$OHPM_HOME/bin:$PATH