写给Android开发者的HarmonyOS入门指南

前言

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下载和升级 | 华为开发者联盟

创建项目

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

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

项目结构

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

PROJECT_FILE_TREE

  • Demo/build-profile.json5 项目配置文件,等同于 Android 项目中的 settings.gradle.kts
{
  "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 应用配置,定义包名、版本、应用图标、名称等配置:

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

使用的图标:

名称资源:

{
  "string": [
    {
      "name": "app_name",
      "value": "Demo"
    }
  ]
}

在应用管理中显示:

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

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

{
  "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"
            ]
          }
        ]
      }
    ]
  }
}

使用的图标:

名称资源:

{
  "string": [
    {
      "name": "EntryAbility_label",
      "value": "Ability1"
    }
  ]
}

在桌面显示:

在任务栈显示:

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

"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 应用一样配置一个全局的应用程序入口,拥有类似的生命周期方法回调:

import AbilityStage from '@ohos.app.ability.AbilityStage';

export default class DemoAbilityStage extends AbilityStage {
    
    onCreate() {
        // 应用启动回调
    }
}

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

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 实现:

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

@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%') // 高度铺满
  }
}

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

界面跳转

Pages 跳转

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

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

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 文件中添加这个界面的声明:

{
  "src": [
    "pages/Index",
    "pages/Second"
  ]
}

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

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 进行创建,会自动生成对应的布局文件和配置,更便捷且不容易出错。

UIAbility 跳转

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

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

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

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

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,都会在任务栈中单独显示出来;

总结

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容