创建第一个 HarmonyOS 项目

任何一门编程语言写出的第一个程序几乎都是 Hello World,这在编程界已经成为经典了。本文就从这个经典开始,了解 HarmonyOS 的应用开发。

选择项目类型

在欢迎界面点击新建项目,选择 JavaEmpty Ability 新建,填入包名等参数。这里的 Ability 是指应用所具备的能力的抽象,一个应用可以包含一个或多个Ability。Ability分为两种类型:FA(Feature Ability)和PA(Particle Ability)。FA/PA是应用的基本组成单元,能够实现特定的业务功能。FA有UI界面,而PA无UI界面。

新建项目

自动签名

目前 DevEco Studio 2.1 版本已经支持自动签名,需要注册华为开发者账号,然后在开发者管理中心创建项目和应用。详情可以查看鸿蒙文档

需要注意的是其中应用的包名必须与 config.json 文件中的 bundleName 一致。

运行程序

之做自动签名的时候已经将手机和电脑连接,现在顶部右侧工具栏中会显示已经连接的手机,点击右边的运行按钮,稍微等待一会,项目就会运行到手机上了。返回手机桌面会看到应用图标,同时长按应用还可以添加一张服务卡片到桌面上。

Hello WorldHello World 桌面小卡片

分析第一个 HarmonyOS 程序

目录结构

左侧文件管理器,显示了用 Java 编写 HarmonyOS 应用的项目结构。

项目结构

  1. .gradle.idea

    这两个目录下放置的都是 DevEco Studio 自动生成的一些文件

  2. entry

    应用程序的代码、资源等内容都放置在这个目录下

  3. EntryCard

    服务卡片的图片按照包名放置在这个目录下

  4. build

    这个目录主要包含了一些编译时自动生成的文件

  5. gradle

    这个目录下包含了 gradle wrapper 的配置文件,使用 gradle wrapper 的方式不需要提前将 gradle 下载好,而是会自动根据本地的缓存情况决定是否需要联网下载 gradle

  6. .gitignore

    这个文件是用来将指定的目录或文件排除在版本控制之外的

  7. build.gradle

    这是项目全局的gradle构建脚本,里面会包含之前自动签名的配置信息和其他编译选项,通常是不需要修改的

  8. gradle.properties

    这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译

  9. gradlewgradlew.bat

    这两个文件是用来在命令行界面中执行 gradle 命令的,其中gradlew是在LinuxMac系统中使用的,gradlew.bat是在Windows系统中使用的(不过当前 DevEco Studio 还没有推出支持 Liunx 运行的版本)

  10. local.properties

    这个文件用于指定本机中的HOS SDK路径,内容是自动生成的

  11. settings.gradle

    这个文件用于指定项目中所有引入的模块。如果是新建的 HelloWorld 项目只有一个 entry,那么该文件中也就只引入了 entry 这一个模块,比如 include ':entry'。通常情况下,模块的引入是自动完成的

顶层目录中除了 entry 外,大多数文件和目录都不需要频繁做大量修改,甚至可以直接使用自动生成的。而 entry 才是之后开发的重点,为了方便之后的开发和学习,本次就先研究下其目录结构。展开之后的目录结构如下图所示:

entry目录下的结构

  1. build

    这个目录和外层的 build 目录类似,也包含一些编译时自动生成的文件

  2. libs

    使用第三方 .jar 包,就需要把这些 jar 包都放在 libs 目录下,放在这个目录下的 jar 包会被自动添加到项目的构建路径里

  3. ohosTest

    编写 HarmonyOS Test 测试用例,用于项目的自动化测试

  4. java

    放置 Java 代码的地方,展开目录会看到系统自动生成的 MyApplication 文件和 MainAbility 文件

  5. res

    项目的 UI 布局、图片、国际化字符串等资源都存在这个目录下。由于内容很多,所以下面还分了很多子目录,比如 resources\base\layout 用于存放布局文件

  6. config.json

    这个是整个 HarmonyOS 项目的配置文件,由appdeviceConfigmodule 三个部分组成。包含应用全局配置,以及应用包含的各类 Ability 的配置。还有类似于 AndroidManifest.xml 的权限声明。

  7. test

    也是一种项目自动化测试

  8. build.gradle

    这是 entry 模块的 gradle 构建脚本,这个文件中会指定很多项目构建相关的配置

  9. proguard-rules.pro

    这个文件用于指定项目代码的混淆规则,当代码开发完成后打包成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。

启动流程

这样整个项目的目录结构就基本了解了。接下来先分析下这个项目究竟是怎么运行起来的。

首先打开 config.json 文件,找到 module 对象,从中可以看到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"module": {
"package": "xyz.zhzh.HarmonyOSGetStart",
"name": ".MyApplication",
"mainAbility": "xyz.zhzh.HarmonyOSGetStart.MainAbility",

...

"abilities": [
{
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
],
"orientation": "unspecified",
"name": "xyz.zhzh.HarmonyOSGetStart.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"formsEnabled": true,
"label": "$string:entry_MainAbility",
"type": "page",
"forms": [
{
"landscapeLayouts": [
"$layout:form_image_with_information_widget_2_2"
],
"isDefault": true,
"scheduledUpdateTime": "10:30",
"defaultDimension": "2*2",
"name": "widget",
"description": "This is a service widget",
"colorMode": "auto",
"type": "Java",
"supportDimensions": [
"2*2"
],
"portraitLayouts": [
"$layout:form_image_with_information_widget_2_2"
],
"updateEnabled": true,
"updateDuration": 1
}
],
"launchType": "standard"
}
]
}

每个HAP的根目录下都存在一个config.json配置文件,文件内容主要涵盖以下三个方面:
① 应用的全局配置信息,包含应用的包名、生产厂商、版本号等基本信息。
② 应用在具体设备上的配置信息,包含应用的备份恢复、网络安全等能力。
HAP包的配置信息,包含每个Ability必须定义的基本属性(如包名、类名、类型以及Ability提供的能力),以及应用访问系统或其他应用受保护部分所需的权限等

另外上面这段代码还表示对 MainAbility 的注册,其中包含了配置图标(”icon”)、名称(”label”)、描述(”description”)和桌面卡片的配置。通常一个 HAP 里可能会包含多个 Ability ,但是只会有一个主 Ability,在手机上点击应用图标,首先启动的就是这个 MainAbility

打开 MainAbility 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
public class MainAbility extends Ability {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
}

...
}

首先可以看到,MainAbility 是继承自 Ability 的。而 AbilityHarmonyOS 系统提供的一个基类,所有自定义的 Ability 都必须继承它或者它的子类才能拥有其特性。然后可以看到其中有一个 onStart() 方法,这个方法是被创建时必定要执行的方法。这里不编写 UI,可以看到在 onStart() 方法的第二行调用了 super.setMainRoute(MainAbilitySlice.class.getName()) 方法,引入了 MainAbilitySlice,打开文件查看代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
}

@Override
public void onActive() {
super.onActive();
}

@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}

这里 MainAbilitySlice 继承自 AbilitySlice,是指应用的单个页面及其控制逻辑的总和。其中用 super.setUIContent(ResourceTable.Layout_ability_main) 引入布局文件。打开路径在项目 entry > src > main > resources > base > layoutability_main.xml 的这个文件,可以看到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="vertical">

<Text
ohos:id="$+id:text_helloworld"
ohos:height="match_content"
ohos:width="match_content"
ohos:background_element="$graphic:background_ability_main"
ohos:layout_alignment="horizontal_center"
ohos:text="$string:mainability_HelloWorld"
ohos:text_size="40vp"
/>
</DirectionalLayout>

这里用到了 DirectionalLatout 这种布局,这是 HarmonyOS 中比较常用的一种布局方式,用于将组件按照水平或者垂直这一个方向排布。里面第一个组件是 Text,也就是用来显示屏幕上的 你好,世界。当然除了 Text 外,还有 ButtonImageTab 等等各类基础组件可供使用,之后再慢慢学习研究吧。

分析 build.gradle 文件

Gradle 是一个非常先进的项目构建工具,它使用了一种基于 Groovy 的领域特定语言(DSL)来进行项目设置,摒弃了传统基于 XML(如 AntMaven)的各种烦琐配置。HarmonyOS 项目中有两个 build.gradle 文件,一个是在最外层目录下的,一个是在 entry 目录下的。这两个文件对构建项目都起到了至关重要的作用,下面就来对这两个文件中的内容进行详细的分析。先看最外层目录下的 build.gradle 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apply plugin: 'com.huawei.ohos.app'

ohos {
signingConfigs {
debug {
...
}
}
compileSdkVersion 5
defaultConfig {
compatibleSdkVersion 5
}
}

buildscript {
repositories {
maven {
url 'https://repo.huaweicloud.com/repository/maven/'
}
maven {
url 'https://developer.huawei.com/repo/'
}
jcenter()
}
dependencies {
classpath 'com.huawei.ohos:hap:2.4.4.2'
classpath 'com.huawei.ohos:decctest:1.2.4.0'
}
}

allprojects {
repositories {
maven {
url 'https://repo.huaweicloud.com/repository/maven/'
}
maven {
url 'https://developer.huawei.com/repo/'
}
jcenter()
}
}

这些代码都是自动生成的,虽然语法结构看上去可能有点难以理解,但是如果忽略语法结构,只看最关键的部分,其实还是很好懂的。首先,两处 repositories 的闭包中都声明了:mavenrepojcenter() 这三个配置。它们分别对应了一个代码仓库,mavenrepo 仓库中包含的主要是华为自家的扩展依赖库,而 jcenter 仓库中包含的大多是一些第三方的开源库。声明了这三行配置之后,我们就可以在项目中轻松引用任何仓库中的依赖库了。接下来,dependencies闭包中使用 classpath 声明了两个插件:一个 hap 插件和一个 decctest 插件。这两个插件都是为了构建项目必需的。

这样就将最外层目录下的 build.gradle 文件粗略分析完了,通常情况下,并不需要修改这个文件中的内容,除非想添加一些全局的项目构建配置。下面再来看 entry 目录下的 build.gradle 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apply plugin: 'com.huawei.ohos.hap'
apply plugin: 'com.huawei.ohos.decctest'

ohos {
compileSdkVersion 5
defaultConfig {
compatibleSdkVersion 5
}
showInServiceCenter true
buildTypes {
release {
proguardOpt {
proguardEnabled false
rulesFiles 'proguard-rules.pro'
}
}
}

}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
testImplementation 'junit:junit:4.13'
ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.100'
}
decc {
supportType = ['html','xml']
}

这个文件中的内容就要相对复杂一些了,首先应用了两个 HarmonyOS 的插件,接着是一个 ohos 的闭包。在这个闭包中我们可以配置项目构建的各种属性。其中,compileSdkVersion 用于指定项目的编译版本。buildTypes闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包:一个是 debug,一个是 releasedebug 闭包用于指定生成测试版安装文件的配置,release 闭包用于指定生成正式版安装文件的配置。另外,debug 闭包是可以忽略不写的,因此上面的代码中就只有一个 release 闭包。下面来看一下 release 闭包中的具体内容,proguardEnabled 用于指定是否对项目的代码进行混淆,true表示混淆,false 表示不混淆。rulesFiles 用于指定混淆时使用的规则文件,这里指定了 proguard-rules.pro 是在当前项目的 entry 目录下的,里面可以编写当前项目特有的混淆规则。

接下来还剩一个 dependencies 闭包。这个闭包的功能非常强大,它可以指定当前项目所有的依赖关系。通常项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的 jar 包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对 jcenter 仓库上的开源项目添加依赖关系。观察一下 dependencies 闭包中的配置,第一行的 implementation fileTree 就是一个本地依赖声明,它表示将 libs 目录下所有 .jar 后缀的文件都添加到项目的构建路径中。