鸿蒙初开,关于鸿蒙原生应用开发的资料较少,使用仓颉的资料就更少。我们花费了大约30多页的篇幅,从零开始,详细解释了如何搭建一个拥有两个页面的仓颉鸿蒙应用,并详细解释了代码的工作原理。期望能帮助迷途的羔羊们找到方向。

版权声明

作者:重庆大学 陈波

本文可以在互联网上自由转载,但必须注明出处(作者:海洋饼干叔叔)并包含指向本页面的链接。本文不可以以纸质出版为目的进行改编、摘抄。

本文系《仓颉编程基础及应用》,清华大学出版社,2025年9月第1版, 第14章全文。

14.1 搭建开发环境

  使用仓颉开发鸿蒙应用需要安装DevEco Studio集成开发环境以及与之配套的仓颉语言插件(plugin)。

  详细过程请参见另一篇文章:

DevEco仓颉鸿蒙应用开发环境的下载安装及配置 - Python,C/C++ Club

14.2 世界你好

  本小节带领读者一步一步地创建并运行第1个仓颉鸿蒙手机应用程序。

14.2.1 创建项目

  运行DevEco Studio,点击图14-1中所示的Create Project(创建项目)按钮。

image233

图14-1 在DevEco中创建项目(第1步)

  向下拖动图14-2右侧的滚动条,选择[Cangjie]Empty Ability([仓颉]空的能力)选项,然后点击Next按钮。

image234

图14-2 在DevEco中创建项目(第2步)

  接下来设置项目参数,如图14-3所见,从上到下依次是项目名称(Project name)、捆绑包名称(Bundle name)、项目保存路径(Save location)、兼容SDK版本(Compatible SDK)、模块名称(Module name)以及设备类型(Device type)。

image235

图14-3 在DevEco中创建项目(第3步)

  其中,捆绑包名称通常使用倒序域名格式设置。本例中,作者没有使用默认的保存路径,而是手工选择了一个路径。请注意,每个项目应存储在一个独立的子目录中。按照上述设置,这个鸿蒙应用只能运行在5.0.4及以上版本的鸿蒙手机上。

  完成前述设置后,点击图14-3中的Finish按钮。接下来,DevEco在一阵忙碌之后,向我们呈现了创建并打开的HelloWorld项目,如图14-4所示。

image236

图14-4 在DevEco中创建项目(第4步)

  至此,项目创建完成。

14.2.2 打开虚拟手机

  本节的应用程序预期运行在基于鸿蒙操作系统的手机上,而不是读者的计算机上。在没有实体的鸿蒙手机的情况下,我们需要创建一个虚拟的鸿蒙手机。

  如图14-5所示,在DevEco窗口右上侧的工具栏中,下拉设备列表(图中No Devices处),然后选择Device Manager(设备管理器)。

image237

图14-5 打开设备管理器

  作者稍早已经创建好了一台运行鸿蒙系统的虚拟手机,如图14-6所示。点击Actions(动作)列中的绿色三角形按钮,即可启动该手机。

image238

图14-6 设备管理器中的虚拟设备清单

🚸 注意——

如果读者找不到如图14-6所示的虚拟手机,请扫码阅读14.1节中的操作指南,并按指南所提供的方法创建仿真手机。

  图14-7展示了该虚拟手机启动中(图左)和启动完成后的样子 (图右)。在手机的右侧,有一个工具条,请读者将鼠标移动该工具条各按钮的上方,逐一查看弹出的文字提示,了解各按钮的功能。

  在虚拟手机启动完成后,可以关闭如图14-6所示的设备管理器。

image239 image240
图14-7 虚拟鸿蒙手机(左:启动中,右:启动完成) 图14-7 虚拟鸿蒙手机(左:启动中,右:启动完成)

14.2.3 部署并运行项目

  在启动虚拟手机后,虚拟手机名称(Huawei_Phone)自动显示于设备清单中,如图14-8所示。点击其右侧的绿色三角形按钮,将当前项目/应用程序部署至该设备并运行。

image241

图14-8 部署并运行项目

  接下来可见DevEco进行了一系列复杂的部署操作,然后以失败告终。错误信息见图14-9。

image242

图14-9 部署项目失败

  出错的原因与仓颉工程默认的处理器架构有关。仓颉鸿蒙工程默认的编译架构为arm64-v8a(即编译器生成的机器语言指令源于arm64-v8a指令集),而作者的仿真手机事实上工作在x86_64指令集的CPU上。

  在DevEco中,展开左侧的树形目录结构,找到并打开HelloWorld/entry/build-profile.json5,然后添加图14-10中高亮显示的第6行信息。请读者仔细核对,确保修改的内容和位置都准确无误。

image243

图14-10 修改build-profile.json5

image244

图14-11 Hello Cangjie显示成功

  使用快捷键Ctrl + S保存修改,然后再次点击图14-8中所示的绿色三角形按钮。接下来,在Windows任务栏中找到隐藏的虚拟手机,将其切换至前台。

  不出意外的话,读者的第一个仓颉App部署并成功运行。如图14-11所示,一个标题为“Hello Cangjie”的蓝色按钮显示在手机屏幕的正中央。

  此时,点击图14-12所示的红色矩形按钮即可终止App在设备上的运行。点击图14-11手机右侧工具条右上角的×,则可关闭虚拟设备。

image245

图14-12 停止运行

14.2.4 修改主页面

  文件HelloWorld/entry/src/main/cangjie/index.cj对应着本示例的主页面。为便于解释示例程序的工作原理(稍后),我们按照图14-13对该文件进行了修改。

image246

图14-13 对index.cj的修改

  完成上述修改并保存后,再次运行程序,在虚拟手机上得如图14-14所示的运行效果。如图所见,应用运行时,手机屏幕上显示了意为世界你好的一段文字,其下有一个标题为Hello的蓝色按钮。使用鼠标模拟手指对该按钮进行点击,其上方文字会在中、英、西、俄等多国语言的“世界你好”间随机切换。图中当前显示的Hola Mundo即为西班牙语版本的世界你好。

  至此,读者的第一个仓颉鸿蒙应用创建完成。

image247

图14-14 HelloWorld运行效果

📇 说明——

这个示例的代码虽然简单,但清晰地描述其结构和工作原理却并非易事。

可能已经有读者对图14-13中展示的index.cj中的代码感到疑惑了:其语法令人十分陌生。这还是仓颉吗?

本章的后续部分,我们将使用很大的篇幅来讨论仓颉鸿蒙应用的结构和工作原理。

14.3 消息循环

  本书前13章中的大部分程序都是所谓控制台应用(console application)。这类应用的生命周期非常简单,如图14-15所示,程序的运行从main函数开始,并因main函数的返回而告终。

image248

图14-15 控制台应用的生命周期

  显然,14.2节中的鸿蒙应用世界你好的生命周期有着显著的不同。如图14-16所示,应用在完成初始化以及初始界面绘制后,便进入所谓消息循环:

(1) 主线程在获得时间片后,便会尝试从由操作系统及应用框架▲所管理的消息队列中获取消息。这些消息,通常是指由操作者对程序发出的指令,比如按钮点击。这些消息,也被称为事件(event)。

(2) 如果收到消息,应用根据消息的类型及程序的当前状态进行恰当的处理并做出响应。如果必要的话,还会刷新/重绘界面。

(3) 如果没有收到消息,应用通常会主动让出时间片并等待下一次被唤醒。

image249

图14-16 图形界面应用程序的生命周期

  不同于Windows桌面应用程序,在手机应用的窗口的右上角,没有×按钮。如图14-17所示,在手机上关闭一个应用,需要从手机屏幕底部向上滑动,待出现应用卡片时,再点击其下的垃圾桶按钮。对于应用而言,用户的这个操作也是一个消息(事件),作为对这个事件的响应,应用在完成必要的诸如数据保存之类的善后工作后结束执行。

image250

图14-17 关闭鸿蒙手机应用

  图14-16所示的消息循环对于程序员而言是透明(不可见)的。在操作系统的支持下,应用框架隐含地实现了前述消息循环。而应用的开发者,仅需为相关消息/事件提供回调函数(callback function)即可。当相关事件发生时,应用框架会自动调用执行相关的回调函数,以完成对事件的处理和响应。

  在示例index.cj的第31 ~ 33行,可以看到如图14-18所示的代码。粗略地解释,{}及其内的代码即为一个匿名函数对象,它作为回调函数与Hello按钮的Click事件相关联。

  当操作者点击Hello按钮时,最先获得这一消息的是操作系统。操作系统根据当前界面上各应用的显示状态判断出该点击动作归属于世界你好,然后将该点击动作的原始信息封装为消息,置于世界你好的消息队列里。而世界你好应用的主线程,稍后从消息队列中取得该消息,并在应用框架的支持下调用相关的回调函数完成对事件的处理:随机挑一种语言的“Hello World”,替换message状态▲,并刷新屏幕上方的文字显示。

image251

图14-18 为Hello按钮的Click事件提供回调函数

🎯 要点——

在底层操作系统的支持下,鸿蒙应用框架的存在隐藏了一个复杂图形应用程序的实现细节,极大地简化了应用的开发工作。从复杂的技术细节中解放出来的程序员,可以腾出手来专注于业务逻辑。

14.4 宏及领域专用语言

  图14-13中展示的index.cj代码让人十分疑惑。文件的扩展名告诉我们这是一个仓颉程序文件,但里面的代码却并不符合通常的仓颉语法规则!

  事实上,index.cj中的代码使用的是借助于仓颉的宏(macro)特性而定制出来的领域专用语言(Domain Specific Language,简称DSL)。这种语言声明式的语法结构特别适合描述手机应用的页面(page)结构。

🎯 要点——

宏可以视为一种特殊的函数。普通的函数接受一些数据作为输入,经过计算及处理后产生另一些数据作为输出。而宏,它接受一个程序片段作为输出,经处理,产生一个新的程序片段作为输出。

  我们通过一个简单的示例来讨论宏的工作机制。在计算机上创建名为Decoration的目录,然后打开CodeArts(不是DevEco),使用菜单文件à打开项目定位并打开Decoration空目录。接下来在其中创建名为logging.cj和main.cj的两个仓颉程序文件,其内容如图14-18及图14-19所示。

image252

图14-18 Decoration目录下的logging.cj文件

  logging.cj的第4 ~ 12行定义了一个名为Logging的宏。如代码第4行所见,Logging宏在形式上很像函数,但它的输入形参类型和返回值类型均为Tokens。Tokens是由Token(词法单元)组成的序列,代表一个程序片段。

📇 说明——

我们无意向本书的读者详细介绍宏定义的语法细节,因为掌握它们需要编译原理的基础知识。同时,在绝大多数的使用场景,程序员都是在使用宏而不是创造宏。

  下述关于Logging宏代码的讨论相当简略,读者如果实在看不明白,略过即可。

▶第5行:将类型为Tokens(程序片段)的形参input解析为一个函数声明(FuncDecl, Function Declaration)。这说明,Logging宏所期望的输入是包含且仅包含一个函数声明的程序片段。

▶第6 ~ 11行:以输入的旧函数声明为基础,生成并返回一个新的函数声明。quote表达式用于从代码模板构造Tokens。

▶第7行:funcDecl.identifier为输入函数声明的函数名;id:Int64系宏为函数新增的形参。

▶第8行:在新函数中生成一行println代码。

▶第9行:在新函数中引用旧函数的函数体代码(funcDecl.block.nodes)。

▶第10行:在新函数中生成另一行println代码。

image253

图14-19 Decoration目录下的main.cj文件

  main.cj则包含了对Logging宏的调用。

▶第2行:导入包含Logging宏定义的macrodefine包。

▶第4 ~ 7行:@Logging即为对Logging宏的调用。在这次调用中,第5 ~ 7行的myFunc函数声明成为宏调用的输入参数。而Logging宏,经过对输入的处理,就地生成并返回了经过修饰,或者说改造的同名新函数。

▶第10行:调用myFunc函数。请注意,这里为myFunc提供了整数2025作为实参,而第5行的原始myFunc函数为零参数。myFunc的名为id的形参,系由Logging宏改造修饰而得的。

  接下来,点击CodeArts下方工具栏中的终端按钮,然后依次输入如图14-20所示的三行终端命令并执行。

image254

图14-20 编译并执行包含宏的示例程序

▶第1行:使用仓颉编译器(cjc)编译包含宏的程序文件logging.cj,参数“–compile-macro”表明本次编译为宏编译。

▶第2行:编译main.cj,生成可行文件main.exe。参数“–debug-macro”要求编译器生成并保留main.cj经宏调用/展开后的结果代码文件,以供分析。

注意,在Windows操作系统下,可执行文件的扩展名为exe。

▶第3行:执行当前目前(.)下的main.exe文件。图10-20下方终端框页内的第4 ~ 6行即为程序执行结果。

  上述编译过程在Decoration目录下生成了很多中间文件。其中,main.cj.macrocall为main.cj经宏调用/展开后的结果文件,其内容请见图14-21。

image255

图14-21 main.cj的宏展开结果

  图14-22展示了本例中Logging宏对函数myFunc的改造过程。原始的零参数的myFunc函数,作为输入提供给Logging宏,经过宏调用/展开,得到一个新的myFunc函数。新函数不但被增加了提供调试输出的println代码,还增添了名为id的形参。

image256

图14-22 Logging宏展开效果

  至此,我们可以大致解释index.cj(图14-13)中那些看不懂的语法表达了。代码中的@Entry、@Component、@State等都是宏调用。为便于进行移动终端应用的开发,仓颉鸿蒙团队使用宏创造了一种领域专用语言,以声明式的语法描述移动应用的页面组件构成及组件间的协作。

  如图14-23所示,宏调用可以视为编译的一个前序过程。由领域专用语言混合仓颉书写的包含宏调用的程序文件(如本示例中的index.cj),首先由编译工具链(tool chain)进行宏调用/展开,生成不含宏调用的100%符合原生仓颉语法的程序文件,再经编译得到可执行文件。

image257

图14-23 宏调用与编译过程

  我们可以在DevEco中查找并查看index.cj经过宏展开之后的结果程序文件。如图14-24所示,在DevEco中执行菜单项EditàFileàFile in files,然后以index.cj中的类型名称EntryView为关键字进行搜索,可查得index.cj的宏展开文件index.cj.macrocall。

image258

图14-24 在DevEco中查找宏展开结果文件

  index.cj.marcrocall与index.cj处于同一子目录下。图14-25展示了该文件的部分内容。仔细查看该文件,可知:① 宏展开后的文件100%使用了仓颉的原生语法;② 仅有37行代码的index.cj,经宏展开后变为77行,其内容面目全非而又纷繁复杂。

image259

图14-25 index.cj的宏展开结果文件(局部)

  显然,由宏所构建的声明式领域专用语言大大简化了移动应用页面的设计工作。

14.5 主页面代码解析

  示例项目中的代码文件index.cj用于生成图14-14中的应用主页面。图14-26再次展示了index.cj的核心代码部分。如图中所见,第3 ~ 18行的部分被折叠了,点击第3行import左侧的+号按钮,展开可见下述代码:

image-20250508000009353

  这些代码负责导入与声明式DSL相关的宏(Entry、Component、State)以及仓颉鸿蒙的界面组件,包括Row(行容器)、Column(列容器)、Text(标签)、Button(按钮)等。

  上述包名多以ohos开始。ohos是Open Harmoney Operating System(开源鸿蒙操作系统)的首字母缩写。

image260

图14-26 主页面核心代码

▶第21行:Component宏用于装饰一个自定义组件。自定义组件是可被复用的UI(用户界面)单元。对于本行的Component宏而言,其输入为第22 ~ 36行的EntryView类型声明。

▶第20行:Entry宏进一步将自定义组件装饰为入口组件。入口组件,即常规意义上的页面组件。对于Entry宏而言,其输入为Component宏对EntryView类型声明进行展开后的输出。

  此处的Entry宏和Component宏构成了嵌套的宏调用:EntryView类型声明先经Component宏展开,展开后的结果再交由Entry宏进一步展开。

  查看index.cj的宏展开文件index.cj.macrocall可见,经过两个宏的装饰,EntryView类型改为从CustomView继承,并添加了init、aboutToBeDeleted、updateWithValueParams、rerender、forceRerender等成员函数。

📇 说明——

诸如Component、Entry这类用于改造提升函数声明或者类型声明的宏,常被称为装饰器(decorator)。

▶第23 ~ 24行:字符串数组messages存储6种语言的“世界你好”。

▶第27 ~ 36行:build成员函数用于创建页面内容。这个函数可以认为是回调函数,程序员不必主动书写代码调用这个函数。该函数预期由鸿蒙应用框架▲适时调用。

▶第28 ~ 35行:Row(行)是一个容器组件,它约束其内的子组件沿从左至右的水平方向布局。

  容器组件不可见,用户不能在实际界面上看到它。通过将可见的组件(比如标签、按钮)置于不同类型的布局(layout)容器内,我们可以合理地安排这些可见组件在不同分辨率及不同尺寸上的屏幕上的位置和尺寸。主页面内各组件间的布局和从属关系请见图14-27。

image261

图14-27 主页面布局

  在第35行,我们调用了Row对象的height成员函数,设置其高度为100%(100.percent),即纵向占满整个屏幕。

▶第29 ~ 34行:Column(列)也是一个容器组件,它约束其内的子组件沿从上至下的垂直方向布局。如图14-27所示,这个Column组件系Row组件的子(成员)组件。

  在第34行,我们调用了Column对象的width成员函数,设置其宽度为100%,即横向铺满整个屏幕。

▶第30行:Text(标签)用于显示文本,是可见组件。本例中,它与Button(按钮)一起被置于Column组件的内部,它是Column组件的成员。如图14-27所示。

  如图14-28所示,本行代码的执行分三步完成:①构造函数构造Text对象。②以构造函数返回的Text对象为基础,执行其成员函数fontSize,设置字体大小。该函数返回this,即Text对象自身作为返回值。③以fontSize成员函数返回的Text对象为基础,执行其成员函数height,设置组件高度(像素单位)。同样地,height成员函数也返回了this,如果必要,可以在其后方继续执行Text对象的成员函数。

image262

图14-28 串联的函数调用序列

  “成员变量”message作为参数参与了Text对象的构造。如代码第25行所见,message被State宏装饰为状态。这意味着,只要message的值发生改变,这个Text对象的显示内容就会自动刷新,以反应状态的变化。

▶第25 ~ 26行:State宏装饰成员变量message为状态。第26行的message成员变量声明构成了State宏的输入。

image-20250508000303916

  在index.cj的宏展开文件中,我们摘出了与message相关的部分。不难看出,经过State宏装饰,message由普通成员变量变成了属性,在属性被置值时,第29行的函数将被调用。这个函数调用将最终导致页面内与message绑定的Text对象的刷新。

▶第31 ~ 33行:Button(按钮)对象与Text对象都是Column对象的成员。由于Text在前,Button在后,作为Column容器从上至下纵向布局的结果,在主页面中,Text在上,Button在下。

  通过Button对象的onClick成员函数,我们向Button对象提供一个匿名函数。如14.3节所述,当这个按钮被点击后,鸿蒙应用框架会自动调用这个匿名函数对象,并提供类型为ClickEvent的evt作为参数。

  本例中,按钮点击事件的响应回调函数十分简单,如代码第32行所见,它从messages数组中随机挑选一个字符串,然后赋值给message。如前所述,由于message被装饰为状态,它改变后,与之关联的Text对象会自动刷新以反应这一变化。

  接下来,以onClick函数返回的this对象为基础,fontSize、height和margin成员函数被依次执行。其中,margin(top:100)设置了按钮的上部间距,这个间距反应到手机屏幕上,构成了Text和Button之间的纵向间隙。

  读者可以详细查看index.cj.macrocall文件中经宏展开后的build函数的详细内容,以便更深入地理解隐藏在index.cj背后的复杂工作原理。

14.6 添加新页面

  一个移动应用程序通常包含多个页面。如图14-29所示,在DevEco的Project树形目录中,右键单击HelloWorld/entry/src/main/cangjie子目录,在弹出菜单依次选择New(新建)–> Cangjie File(仓颉文件)。接着在随后出现的对话框中给新文件取名为about(关于),然后点击OK。

image263

图14-29 新建仓颉程序文件

  新文件about.cj随之被创建并自动打开。该文件与index.cj在同一目录里。接下来,仿照index.cj中的声明式语法为about页面创建内容,请见图14-30。

image264

图14-30 about.cj文件内容

  如图14-30所示,About(关于)页面是类About描述,该类由Entry和Component宏装饰。About页面最终的显示效果及内部布局(layout)结构分别见图14-31及图14-32。

▶第3行:ohos.router.Router为界面路由包。其提供一系列函数支持终端屏幕上的页面切换和跳转。

▶第8 ~ 19行:About类型的build成员函数用于创建页面内容。该函数预期由鸿蒙应用框架▲适时调用。

image265 image266
图14-31 About页面 图14-32 About页面的布局

▶第11 ~ 16行:在Column(列)容器内从上至下依次布局了三个可视的组件,包含两个Text(标签)以用一个Button(按钮)。

▶第12行:“Our Home Planet”的字体大小为20,较“Earth”为小;成员函数foregroundColor设置前景色,对于标签而言,前景色即文字颜色。0x707070表示由0x70的红、0x70的绿、0x70的蓝三个颜色分量混合而成的灰色。每个颜色分量的取值范围为0 ~ 255(0xff)。

▶第13行:margin成员函数设置Button(按钮)的上方间距为100像素。该间距形成了图14-31中“Our Home Planet”与Back按钮之间的空隙。

▶第14 ~ 16行:以第13行margin成员函数返回的this对象为基础,执行其onClick成员函数,设置该按钮被点击后的回调函数。

▶第15行:函数Router.back执行页面回退,即切换至当前显示页面的前一页。本例中,About页由主页面(即Index页)跳转而得,Router.back的执行将屏幕切换回主页面。

  为了能够在主页面中跳转至About页,我们在主页面的Column容器中新增了一个Row容器,将原有的Hello按钮和新增的About按钮置于其中。改造后的主页面显示效果及其布局如图14-33及图14-34所示。

image267 image268
图14-33 改造后的主页面 图14-34 改造后的主页面布局

  图14-35展示了主页面(index.cj)文件的变化部分。

image269

图14-35 修改index.cj

▶第32 ~ 40行:在Column容器中Text组件之下添加一个Row容器。用于容纳Hello及About按钮。在Row容器内,两个按钮从左至右水平布局。

▶第40行:执行Row容器的margin成员函数设置其上方间距。该间距设置形成了图14-33中两个按钮与“Hello World”之间的纵向空隙。

▶第37 ~ 39行:在About按钮的onClick回调函数内,执行Router.push(url: “About”)由主页面跳转至About页面。命名参数url表示目标页面的路径。

  至此,我们成功为示例添加了About页面。点击图14-33的About按钮,由主页面跳转至About页面。点击图14-31的Back按钮,由About页面回退至主页面。

14.7 Ability

  在14.2.1节创建鸿蒙应用时,我们选择的Template(模板)是[Cangjie]Empty Ability(参见图14-2)。

  在鸿蒙的术语体系里,Ability(能力)才是系统调度的基本单元。Ability组件包含UI(用户界面),并负责与用户的交互。一个移动应用程序可以包含一个或多个Ability。假设一个应用既能导航、又可支付,则可将导航和支付分别设计为独立的Ability并包裹在同一个应用中。当移动终端上的其他应用请求导航服务时,该应用的导航Ability可以被独立调度以提供相应服务。

  每一个运行中的Ability实例均会表现为系统任务列表(参见图14-17)中的一个任务。除非需要多屏协同,即在多个屏幕上分别处理属于同一个应用的多项任务,“一个Ability+多个页面”的模式可以满足绝大多数应用场景。

  于本示例而言,与index.cj及about.cj在同一目录下的main_ability.cj即为应用的唯一Ability。逻辑上,主页面和关于页面归属于这个Ability。如图14-36所示,main_ability.cj定义了类型MainAbility,其继承自ohos.ability.Ability。

image270

图14-36 main_ability.cj文件内容

  如图14-37所示,当用户打开、切换、返回应用时,应用中的Ability实例会在生命周期的不同状态之间切换。与这些状态对应,Ability提供onCreate(创建)、onForeground(拉至前台)、onBackground(退至后台)、onDestroy(销毁)等回调函数。在对应的状态迁移发生时,应用框架会调用执行对应的回调函数。

image271

图14-37 Ability的生命周期

  图14-36中的MainAbility重写了Ability父类的onCreate回调函数。可以在该回调函数中进行页面初始化操作,例如从文件系统装载初始数据等。其类型为Want的形参want包含了正在被创建的Ability的名称、捆绑包名称(Bundle name)等信息。launchParam则提供了Ability的启动原因等信息。

  在Ability实例创建完成之后进入前台之前,应用框架会创建一个窗口舞台(WindowStage)。每一个Ability实例都会与一个窗口舞台实例相绑定,通过该舞台,Ability持有一个主窗口,该窗口为Ability提供绘制区域。在相关舞台对象创建完成之后,应用框架会调用执行Ability的onWindowStageCreate回调函数。如图14-36第24行所示,正是在该回调函数里,Ability执行windowStage的loadContent成员函数,载入并显示了主页面EntryView。EntryView即index.cj中的页面类型。

14.8 Stage框架

  鸿蒙应用框架以Stage(舞台)模型来描述一个移动应用的结构。该模型由舞台和舞者来组成,其中,舞台包括AbilityStage以及WindowStage,而舞台上的舞者,则是Ability和Window(窗口)。

  本示例中的另一个程序文件ability_stage.cj则描述了本应用的AbilityStage。图14-38展示了该文件的内容。图中可见,MyAbilityStage继承自AbilityStage。

image272

图14-38 ability_stage.cj文件内容

  向当前阶段的读者解释Stage模型是一个艰难的任务,这里只能浅尝则止。简单地说,一个移动应用可以由多个Module(模块)构成,而一个Module,又可以包含一个或多个Ability。这些Ability作为舞者,在由AbilityStage提供的舞台上运作。每一个Ability实例,又与一个WindowStage实例相绑定,并通过该WindowStage持有一个主窗口,从而获得页面的绘制区域。

  在项目的entry/src/main目录下,可以找到一个名为module.json5的配置文件。该文件提供了名为entry的Module(模块)的配置信息。图14-39展示了该配置文件的局部内容。图中可见,名为entry的模块由程序中的MyAbilityStage(见图14-38)类型管辖,该舞台包含名为EntryAbility的Ability。EntryAbility对应程序中里的MainAbility(见图14-36)类型。

image273

图14-39 模块配置文件内容(局部)

14.9 修改应用名称及图标

  接下来,我们给本章的小小示例划上句号。如图14-40所示,“世界你好”应用在手机桌面上的图标和名称都是默认的。接下来我们修改应用的名称及图标。

  首先,如图14-41所示,在虚拟手机的桌面,用鼠标左键长按应用图标,然后选择卸载删除应用。

image274 image275
图14-40 应用名称及图标 图14-41 卸载应用

  接下来,我们从IconPark[1]下载了一个png格式的图片文件,将其命名为hello.png,然后复制到项目的entry/src/main/resources/base/media目录下。这种复制可以直接在文件夹中完成,如图14-42所示。

image276

图14-42 复制图标文件至资源文件夹

  上述复制完成后,稍等一会儿,便可在DevEco的项目文件夹的对应子目录下看到这个文件。接下来,我们再次打开entry/src/main/module.json5,找到如图14-43第14行所述的icon项,将其值修改为”$media:hello”。

image277

图14-43 修改module.json5

  在图14-43的第15行,label项用于设置应用的名称。其值仍然是以$符号开始的资源项。用左手按住Ctrl键不放,然后鼠标点击”$string:EntryAbility_label”,DevEco便打开了一个名为string.json文件,将其中EntryAbility_label的value项改变Hello World。请见图14-44。

image278

图14-44 修改string.json

  最后,在DevEco中,使用菜单项BuildàRebuild project重新构建项目,然后再次运行,便可在虚拟手机上看到应用的图标和名称发生了期待的变化。请见图14-45。

image279

图14-45 修改后的应用名称及图标

14.10 小结

  使用仓颉开发鸿蒙应用需要安装DevEco Studio以及与之配套的仓颉语言插件。在DevEco中可以创建虚拟手机,以运行鸿蒙应用。

  图形界面应用程序的生命周期包含一个由操作系统和应用框架支持的消息循环。在完成初始化以及初始界面绘制后,应用会保持运行并进行消息循环,接受操作者的操作指令,并做出恰当的反馈。

  借助于宏特性,华为开发了一种专门领域专业语言,它以声明式的语法描述页面的结构。如果把宏视作函数,那么它的输入和输出均为程序片段。

  一个移动应用可以由多个Module(模块)构成,而一个Module,又可以包含一个或多个Ability。这些Ability作为舞者,在由AbilityStage提供的舞台上运作。每一个Ability实例,又与一个WindowStage实例相绑定,并通过该WindowStage持有一个主窗口,从而获得页面的绘制区域。


[1] IconPark是一个开源的共享图标网站,其内提供的图标和插图可以免费商用。


欢迎支持海洋饼干叔叔系列程序设计教材,案例、配套资源丰富,实践性强,高等教育出版社出版。

高校教学同行如果需要样书,或者索取教学支持资源, 请联系公众号或者海洋饼干叔叔本人。

《Python编程基础及应用》 《Python编程基础及应用实验教程》 《C++编程基础及应用》
book1 实验书图片 Cpp小尺寸