前言

2023华为开发者大会 之后,HarmonyOS 后续版本将不再支持 Android应用 的说法愈演愈烈,虽然网络上有很多相关的新闻,但大多都是基于 HarmonyOS NEXT 开发者预览版 不支持 Android应用 安装做的推测,目前未见华为官方正式发布说明。

也有人说目前的鸿蒙开发工具 DevEco Studio 里面都没有集成 Android SDK,但这也只能说明原生的鸿蒙应用无法安装在 Android 系统上,这个说法尚未定论。

但是 HarmonyOS 的发展是必然的,作为移动端的从业人员,自然不能停滞不前,今天我们就来走进 HarmonyOS 的世界。

准备及注意

这篇文章是面向 Android开发者 的,因此一些基本的东西我就不再赘述,相关文档可以在华为开发者联盟查看。

以下内容都是基于已经搭建好 HarmonyOS 开发环境(DevEco Studio、HarmonyOS SDK);

DevEco Studio应用签名是和华为账号绑定的,所以开始前需要注册一个华为账号(首次运行失败会有提示);

运行 HarmonyOS 项目需要一部鸿蒙系统的手机;(目前仅Mate 50、60系列手机能正常使用,其它机型需要使用投屏工具进行操作,直接在手机上操作会卡死);

让我们从Hello World开始

DevEco Studio

Android 开发要使用 Android Studio 开发,HarmonyOS 也提供了一个 DevEco Studio 开发工具,同样是基于 IDEA 开发的,因此功能界面和 Android Studio 大致相同,很容易上手,并且可以从 Import Sample 菜单里选择官方提供的模板库,下载到本地运行。

工具下载地址:HUAWEI DevEco Studio和SDK下载和升级 | 华为开发者联盟

WELCOME_TO_DEVECO_STUDIO

创建项目

点击欢迎界面的 Create Project,创建新项目的流程也和 Android Studio 类似,这里的 Ability UI组件类似 Android 中的 Activity,用于提供UI显示及生命周期回调,这里我们简单的用默认模板创建一个 Demo 项目;

CHOOSE_ABILITY_TEMPLATE

Bundle name 等同于 Android 中的 package name,项目名、保存位置可以自选,其他配置保持默认就可以了,完成项目创建。

CONFIGURE_YOUR_PROJECT

项目结构

待项目创建完成,你就能看到如下的项目目录结构,文件看上去很多,但是我们只需要关注重点部分;

PROJECT_FILE_TREE

  • Demo/build-profile.json5 项目配置文件,等同于 Android 项目中的 settings.gradle.kts

    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
    {
    "app": {
    "signingConfigs": [], // 签名配置
    "compileSdkVersion": 9, // SDK 版本配置
    "compatibleSdkVersion": 9, // SDK 版本配置
    "products": [
    {
    "name": "default",
    "signingConfig": "default",
    }
    ]
    },
    "modules": [ // 模块配置
    {
    "name": "entry", // 模块名
    "srcPath": "./entry", // 模块路径
    "targets": [
    {
    "name": "default",
    "applyToProducts": [
    "default"
    ]
    }
    ]
    }
    ]
    }
  • Demo/AppScope 存放全局资源及配置的路径;

  • Demo/AppScope/resources 应用全局资源路径;

  • Demo/AppScope/app.json5 应用配置,定义包名、版本、应用图标、名称等配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "app": {
    "bundleName": "com.sample.demo", // 包名
    "vendor": "example", // 供应商
    "versionCode": 1000000, // 版本号
    "versionName": "1.0.0", // 版本名
    "icon": "$media:app_icon", // 应用图标,此处配置影响应用管理中显示
    "label": "$string:app_name" // 应用名,此处配置影响应用管理中显示
    }
    }

    使用的图标:

    app_icon

    名称资源:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "string": [
    {
    "name": "app_name",
    "value": "Demo"
    }
    ]
    }

    在应用管理中显示:

    DEMO_IN_APP_SETTING

  • Demo/entry 应用主模块,应用入口,存放代码、资源的路径;

  • Demo/entry/src/main/module.json5 模块配置文件,类似 Android 项目中的 AndroidManifest.xml

    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
    {
    "module": {
    "name": "entry", // 当前module的名字,module打包成hap后,表示hap的名称,标签值采用字符串表示(最大长度31个字节),该名称在整个应用要唯一
    "type": "entry", // 表示模块的类型,类型有三种,分别是entry、feature和har
    "srcEntry": "./ets/DemoAbilityStage.ts", // 模块的入口文件路径,默认没有,需要手动创建,类似 Android 中的 Application
    "description": "$string:module_desc", // 当前模块的描述信息
    "mainElement": "EntryAbility", // 该标签标识hap的入口ability名称或者extension名称。只有配置为mainElement的ability或者extension才允许在服务中心露出
    "deviceTypes": [ // 该标签标识hap可以运行在哪类设备上
    "phone",
    "tablet"
    ],
    "deliveryWithInstall": true, // 该模块是否随应用一起安装
    "installationFree": false, // 释放支持免安装
    "pages": "$profile:main_pages", // ability 中使用的 page 信息配置
    "abilities": [ // ability 配置列表,类似 Android 中的 Activity 列表
    {
    "name": "EntryAbility", // 逻辑名,整个应用要唯一
    "srcEntry": "./ets/entryability/EntryAbility.ts", // 入口代码路径
    "description": "$string:EntryAbility_desc", // 描述信息
    "icon": "$media:icon", // 图标,如果为 MainElement,必填,此处配置影响应用列表显示及任务栈显示
    "label": "$string:EntryAbility_label", // 标签名,此处配置影响应用列表显示及任务栈显示
    "startWindowIcon": "$media:icon", // 启动页图标
    "startWindowBackground": "$color:start_window_background", // 启动页背景颜色
    "exported": true,
    "skills": [
    {
    "entities": [
    "entity.system.home"
    ],
    "actions": [
    "action.system.home"
    ]
    }
    ]
    }
    ]
    }
    }

    使用的图标:

    icon

    名称资源:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "string": [
    {
    "name": "EntryAbility_label",
    "value": "Ability1"
    }
    ]
    }

    在桌面显示:

    ABILITY_IN_DESKTOP

    在任务栈显示:

    ABILITY_IN_STACK

    如果有多个 UIAbility,则配置了 skillsentity.system.home 的都会在桌面创建图标:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    "skills": [
    {
    "entities": [
    "entity.system.home"
    ],
    "actions": [
    "action.system.home"
    ]
    }
    ]
  • Demo/entry/src/main/ets 源码文件路径;

  • Demo/entry/src/main/ets/entryability/EntryAbility.ts UI 组件,类似 Android 中的 Activity

  • Demo/entry/src/main/resources 资源文件路径;

项目架构及与Android对比

从总体的项目架构来看,HarmonyOS 项目是比较贴近 Android 项目中 Compose + 单Activity 架构的,并且我们在 HarmonyOS 中也能找到一些与 Android 项目对应的关系:

Android HarmonyOS
settings.gradle.kts 项目配置文件 Demo/build-profile.json5 项目配置文件,不同的是将应用的签名、SDK版本、多渠道配置移动到了这里
build.gradle.kts 模块配置文件 Demo/AppScope/app.json5 应用配置文件,配置包名、版本、图标、名称
AndroidManifest.xml 清单文件 Demo/entry/src/main/module.json5 模块配置文件,配置应用入口、路由等信息
Application 应用程序入口 AbilityStage 应用程序入口
Activity UI组件 UIAbility UI组件
Navgation 页面路由 pages 页面路由

生命周期

module.json5 中的 module 节点声明 srcEntry,我们也能和 Android 应用一样配置一个全局的应用程序入口,拥有类似的生命周期方法回调:

1
2
3
4
5
6
7
8
import AbilityStage from '@ohos.app.ability.AbilityStage';

export default class DemoAbilityStage extends AbilityStage {

onCreate() {
// 应用启动回调
}
}

HarmonyOS 中的UI组件 UIAbility 也和 Android 中的 Activity 有着类似的生命周期:

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
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
// 组件创建
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}

onDestroy() {
// 组件销毁
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}

onWindowStageCreate(windowStage: window.WindowStage) {
// window 创建
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

// 设置布局,显示 ets/pages/Index.ets 的布局
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}

onWindowStageDestroy() {
// window 销毁
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}

onForeground() {
// 进入前台
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}

onBackground() {
// 进入后台
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}

HarmonyOSUIAbility 没有 AndroidActivityonResume onPause 生命周期回调方法,当然如果需要的话还是有方案可以实现的,需要在 onWindowStageCreate 方法里,通过 windowStage 实现:

1
2
3
4
5
6
7
8
9
10
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.on('windowStageEvent', (event) => {
// event 取值为枚举类型 window.WindowStageEventType
if(event === window.WindowStageEventType.ACTIVE) {
// 获取焦点
} else {
// 失去焦点
}
})
}

布局

HarmonyOS 使用 ArkTS 作为开发语言,并且提供 ArkTS UI 组件用于UI布局,就像上面 UIAbility 中显示的布局 ets/pages/Index.ets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entry // 声明这个组件可作为页面入口,即在 UIAbility 中加载或进行页面跳转
@Component // 声明这是一个UI组件
struct Index {
@State message: string = 'Hello World'

build() {
// 声明布局
Row() { // 横向布局
Column() { // 竖向布局
Text(this.message) // 文本控件
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%') // 宽度铺满
}
.height('100%') // 高度铺满
}
}

INDEX_PREVIEW_1

上面是新建项目默认生成的布局,效果是在屏幕中间显示 Hello World 文本,更多组件可参考组件参考(基于ArkTS的声明式开发范式

界面跳转

Pages 跳转

HarmonyOS 生成的项目里,默认只有一个 UIAbility,并通过 windowStage.loadContent 来加载显示布局,因此一种多界面的方式就是编写不同的 Page,在不同的 Page 直接跳转;

首先,我们在 ets/pages 路径下新建一个 Second.ets 文件,并参照 Index.ets 声明布局,并添加按钮用于返回上一级:

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
import router from '@ohos.router'

@Entry
@Component
struct Second {
@State message: string = 'Second Page'

build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
// 添加按钮
Button("点击返回")
.onClick(() => {
// 按钮点击通过 router 返回上一级
router.back()
})
}
.width('100%')
}
.height('100%')
}
}

然后,我们需要在 resources/base/profile/main_pages.json 文件中添加这个界面的声明:

1
2
3
4
5
6
{
"src": [
"pages/Index",
"pages/Second"
]
}

在之前的 Index.ets 布局中添加按钮并配置点击跳转逻辑:

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
import router from '@ohos.router'

@Entry
@Component
struct Index {
@State message: string = 'Hello World'

build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
// 添加按钮
Button("点击跳转")
.onClick(() => {
// 按钮点击通过 router 跳转到 pages/Second
router.pushUrl({
url: "pages/Second"
})
})
}
.width('100%')
}
.height('100%')
}
}

这样通过点击按钮就能进行界面的跳转了。上面创建 Page 的方式推荐在 ets/pages 路径使用 New -> Page 进行创建,会自动生成对应的布局文件和配置,更便捷且不容易出错。

ROUTE_PAGE

UIAbility 跳转

虽然 HarmonyOS 官方提供的模板里面都只有一个 UIAbility,但是它还是支持多 UIAbility 的;

同样的在 ets 路径下参照 EntryAbility.ts 创建一个新的 SecondEntryAbility.ts,在 onWindowStageCreate 中加载布局 pages/SecondAblity,在 module.json5 中添加对应的配置即可,这里还是推荐使用 New -> Ability 进行创建;

ets/pages/SecondAbility.ets 中修改文本及添加返回按钮:

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
import common from '@ohos.app.ability.common'

@Preview
@Entry
@Component
struct SecondAbility {
@State message: string = 'Second Ability'

build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("点击返回")
.margin({ top: 30 })
.onClick(() => {
// 按钮点击关闭当前 UIAbility
let context = getContext(this) as unknown as common.UIAbilityContext
context.terminateSelf()
})
}
.width('100%')
}
.height('100%')
}
}

然后我们修改 ets/pages/Index.ets 中的代码,在之前的按钮下方添加一个新的按钮点击跳转 SecondEntryAbility

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
import common from '@ohos.app.ability.common'
import Want from '@ohos.app.ability.Want'

@Entry
@Component
struct Index {
@State message: string = 'Hello World'

build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("点击跳转")
.onClick(() => {
// 按钮点击跳转到 SecondEntryAbility
let context = getContext(this) as unknown as common.UIAbilityContext
let want: Want = {
deviceId: "",
bundleName: "com.sample.demo",
abilityName: "SecondEntryAbility",
}
context.startAbility(want)
})
// 添加按钮
Button("点击跳转Ability")
.onClick(() => {
// 按钮点击跳转到 SecondEntryAbility
let context = getContext(this) as unknown as common.UIAbilityContext
let want: Want = {
deviceId: "",
bundleName: "com.sample.demo",
abilityName: "SecondEntryAbility",
}
context.startAbility(want)
})
}
.width('100%')
}
.height('100%')
}
}

这里我们就要提到 UIAbilityActivity 的区别了,虽然他们同样都是UI组件,但是在 HarmonyOS 中,每打开一个 UIAbility,都会在任务栈中单独显示出来;

START_ABILITY

总结

一路看下来,相信你对 HarmonyOS 项目如何上手已经有了思路,作为 Android 开发者,我在写这篇文章的时候更多的是在寻找 HarmonyOSAndroid 开发的相似之处,通过这样的对比,我们不需要从头了解 HarmonyOS 开发,就能更快的入手了。