鸿蒙开发从入门到跨平台系列第一篇:深入了解鸿蒙项目的核心结构

2,066 阅读11分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

大家好,我是拭心。

上篇文章《# 深圳打响第一枪!鸿蒙技术是否有前途的最强信号来了》 提到,深圳已经发了 2024 年关于鸿蒙软件生态的规划,如果目标达到,过几年很有可能出现 iOS Android 鸿蒙三足鼎立的情况,因此我们客户端程序员有必要储备一下鸿蒙知识。

接下来我将分几篇文章介绍鸿蒙开发的入门、实战和跨平台相关知识,今天这篇文章作为开篇,主要介绍一下鸿蒙开发使用的 IDE 和一个简单工程的项目结构,解决读者在刚接触鸿蒙开发时一脸茫然、无从下手的问题。

IDE

image.png

鸿蒙开发的 IDE 叫做 DevEco Studio,它是基于 IntelliJ IDEA Community 的开源版本打造,和 Android Studio 血脉相通,因此做过 Android 开发或者使用过 IDEA 的工程师们切换到鸿蒙开发时,会发现查看项目目录、文件结构、调试/运行、设备选择、日志、命令行等都是相同的位置,像回家一样自然,非常顺滑。

新建项目页面也类似:

image.png

image.png

上两图是开发鸿蒙和 Android 应用时新建项目的页面,可以看到两者很相似。在鸿蒙中,Ability 类似 Android 的 Activity,IDE 提供了一些典型业务的 Ability 实例工程;另外鸿也提供了类似 JNI 的 NApi 工程,用于开发需要调用 C++ 的项目。

项目结构

当我们使用 DevEco Studio 新建一个鸿蒙项目后,创建的项目长这样:

image.png

AppScope

按照上图中从上往下的顺序(忽略前两个文件夹 .hvigor .idea,不重要),第一个比较重要的文件夹是 AppScope

image.png

AppScope 的作用是配置 app 的核心信息,包括唯一标识(bundleName)、版本号、icon 和名称等等。新建项目后我们一般都会修改这些内容,这时候就需要修改上图中的 app.json5 文件。

.json5 是鸿蒙项目中配置文件的格式,这个格式结尾的文件就是配置文件(好像是句废话哈哈).

第二个重要的文件夹是 entry,它是鸿蒙项目的默认入口代码目录,内容比较多我们稍后介绍。

hvigor

第三个文件夹是 hvigor

image.png

hvigor 是鸿蒙项目的构建工具(可以类比 Gradle),支持使用 JavaScript/TypeScript 开发配置脚本。

根目录下的 hvigor 文件夹用来配置构建工具的版本号、用到的插件、执行配置、日志配置、调试配置等信息:

image.png

当我们需要升级 hvigor 本身或者官方构建插件(@ohos/hvigor-ohos-plugin)的版本号时,需要去 hvigor/hvigor-config.json5 文件中修改(文件内容见上图右侧)。

  • 如果编译速度慢,需要修改这个文件的 execution 部分,比如开启守护进程、打开增量编译和并行编译。
  • 如果调试的时候经常断开(可能是内存不够),可以试一下把这个文件的 nodeOptions 属性的 maxOldSpaceSize 参数调大一点(单位是 MB)。

hvigor 依赖 node,因此在开发鸿蒙项目时我们需要安装并配置 node 环境。

了解 hvigor 是什么后,接下来看下项目根目录下的 hvigorfile.ts 文件。

hvigorfile.ts 是用来配置项目的构建校本,我们可以在其中创建自定义的 task 和 plugin(两者差不多,不同在于 plugin 方便项目之间复用),然后添加到项目的构建流程中。

image.png

由于配置文件是 ts 文件格式,因此自定义 hvigor 构建 task 很简单,只需要定义一个 pluginId、实现 run 方法即可。

在上图中,我们的 myTask 函数就是一个自定义的 task,它的 id 是 myTask,然后在任务的执行方法里打印了一条日志。

自定义 plugin 也是类似的,区别在于需要继承 HvigorPlugin:

image.png

自定义 task/plugin 函数以后,我们把它添加到导出的 plugins 数组即可。

了解 hvigor 及 hvigorfile.ts 文件后,接下来我们看看根目录下另外比较重要的两个文件。

build-profile.json5

image.png

build-profile.json5 用来配置 app 的签名信息、使用的 SDK 版本号、构建模式和多 module 信息。

签名信息配置在 signingConfigs 中:

{
  "app": { 
    //工程的签名信息,可包含多个签名信息
    "signingConfigs": [  
      {
        "name": "default",  //标识签名方案的名称,用户可自定义
        "type": "HarmonyOS",  //标识HarmonyOS应用
        //该方案的签名材料
        "material": {  
          "certpath": "D:\\SigningConfig\\debug_hos.cer",  //调试或发布证书文件,格式为.cer
          "storePassword": "******",  //密钥库密码,以密文形式呈现
          "keyAlias": "debugKey",  //密钥别名信息
          "keyPassword": "******",  //密钥密码,以密文形式呈现
          "profile": "D:\\SigningConfig\\debug_hos.p7b",  //调试或发布证书Profile文件,格式为.p7b
          "signAlg": "SHA256withECDSA",  //密钥库signAlg参数
          "storeFile": "D:\\SigningConfig\\debug_hos.p12"  //密钥库文件,格式为.p12
        }
      }
    ],
  }
}

如上图所示,签名信息可以有多个,每个配置信息主要包括它的名称、使用的证书和密钥信息、密码等文件。

构建模式配置在 buildModeSet 中:

"buildModeSet": [
      {
        "name": "debug",   //定义构建模式的类型名称,系统默认给出test、debug和release,用户也可以自定义
        "buildOption": {   //配置项目在构建过程中使用的相关配置
          "packOptions": {  包配置选项,用于限制包大小和包数量
            "mainPackageLimitSize": 2,
            "normalPackageLimitSize": 2
          },
          "debuggable": true,
          "resOptions": {},
          //cpp相关编译配置
          "externalNativeOptions": {
            "path": "./src/main/cpp/CMakeLists.txt",  //CMake配置文件,提供CMake构建脚本
            "arguments": "",  //传递给CMake的可选编译参数
            "abiFilters": [  //用于设置本机的ABI编译环境
              "armeabi-v7a",
              "arm64-v8a",
              "x86_64"
            ],
            "cppFlags": ""  //设置C++编译器的可选参数
          },
          "sourceOption": {   //使用不同的标签对源代码进行分类,以便在构建过程中对不同的源代码进行不同的处理
            "worker": []
          },
          //配置筛选har依赖.so资源文件的过滤规则
          "napiLibFilterOption": {
            //按照.so文件的优先级顺序,打包最高优先级的.so文件
            "pickFirsts": [
              "**/1.so"
            ],
            //按照.so文件的优先级顺序,打包最低优先级的.so 文件
            "pickLasts": [
              "**/2.so"
            ],
            //排除的.so文件
            "excludes": [
              "**/3.so"
            ],
            //允许当.so重名冲突时,使用高优先级的.so文件覆盖低优先级的.so文件
            "enableOverride": true
          },
        }
      }   
    ]

和开发 Android 应用类似,开发鸿蒙应用时也支持对不同包(比如 debug 包和 release 包)做不同的配置,其中比较关键的有这几个:

  • externalNativeOptions:项目有 C/C++ 代码时需要配置,比如 CMakeList 文件路径、参数、ABI 类型等等
  • napLibFilterOption:项目里有 .so 文件冲突时,在这里配置使用规则,比如 pickFirsts、excludes

build-profile.json5 中最后一个关键的配置信息是 modules,即模块配置信息。

在大点的项目里一般会有多个模块,当我们新建一个模块后,就需要在 build-profile.json5 文件的 modules 属性中加一个配置:

  "modules": [
    {
      "name": "entry",  //模块名称,须与模块中module.json5文件中的module.name保持一致
      "srcPath": "./entry",  //标明模块根目录相对工程根目录的相对路径
      "targets": [  //定义构建的APP产物,由product和各模块定义的targets共同定义
        {
          "name": "default",  //target名称,由各个模块的build-profile.json5中的targets字段定义
          "applyToProducts": [  
            "default"   //表示将该模块下的“default” Target打包到“default” Product中
          ]
        }
      ]
    }
  ]

配置信息包括模块名称、路径、产物信息等。

比较可惜的是,目前 build-profile.json5 文件还不支持条件语句,这在大型项目编译时可是刚需啊。毕竟很多时候业务只想编译自己用到的那几个模块,如果能像 Gradle 一样根据 properties 文件里的值修改配置信息就好了,希望华为可以早点支持这个功能。

oh-package.json5

oh-package.json5 和前端项目里的 package.json 文件类似,主要用来声明项目名称、版本号、描述、入口和依赖信息。

image.png

一个项目里会有多个 oh-package.json5 文件,除了根目录下,每个模块都有自己的 oh-package.json5 文件。

当模块有导出文件时,就需要在 oh-package.json5 的 main 属性填写要导出的文件名称,比如 index.ets。

oh-package.json5 中声明的依赖信息,在项目打开或者手动点击 Sync And Refresh Project 后 ohpm 会下载对应的库到当前目录的 oh_modules 文件夹下:

image.png

ohpm 是鸿蒙的包管理工具,类似 npm。

当我们在不同项目里用到同一个三方库时,为了避免版本冲突,可以在项目根目录下的 oh-package.json5 里强制指定版本号:

image.png

上图中在 overrides 中配置的三方库版本号将作为最终使用的版本。

entry

介绍完上面的文件和文件夹作用后,终于到了最重要的 entry 目录:

image.png

entry 的名字不一样,重点是它的内容,每个新建的模块一样。

从上图可以看到,entry 目录下也有 build-profile.json5、hvigorfile.ts 和 oh-package.json5 文件,这是因为在鸿蒙项目里,每个模块可以单独配置自己的构建流程和依赖信息。

模块中最重要的配置文件却不在这三者之间,而是 src/main/module.json5

module.json5

image.png

module.json5 文件用来配置一个模块的名称、类型、进程信息和入口等,其中比较关键的配置有这些:

  • type: 表示当前模块的类型,类型有:entry(项目入口)、feature(动态特性包)、har(静态包)、shared(动态共享包)。一般来说项目的入口模块类型是 entry,其他业务模块的类型是 feature,纯粹的库类型为 shared
  • mainElement:表示当前模块的入口 UIAbility 名称,名词要和后面的 abilities 中声明的一致
  • deviceType:表示当前模块可以运行在哪类设备上,支持的值为 phone、tablet、tv、wearable、car(可以看到,鸿蒙能开发的应用类型还是很多的)
  • pages:当前模块的页面路由配置文件路径,默认为 src/main/resources/base/profile/main_pages.json
  • abilities:当前模块里的所有 UIAbility 信息

UIAbility 的配置信息有这些:

  • name:组件名称,前面的 mainElement 中写的要和这里的一致
  • srcEntry:UIAbility 文件的相对路径
  • launchType:当前 Ability 的启动模式
  • startWindowIcon:启动页面的 icon
  • skills:当前 UIAbility 能够接收的 Want 特征

UIAbility 的启动模式有三种:

  • multition:多实例,每次启动都新创建一个
  • singleton:单例模式,只有第一次启动创建
  • specified:指定模式,运行时由开发者决定是否创建新实例

记得很多年前 Android 面试中 Activity 的启动模式经常被问到,鸿蒙的 UIAbility 启动模式或许以后也是个考察点,可以重点关注下。

skills 标签表示 UIAbility 被启动时能够接收的 Want 特征,那什么是 Want 呢?

image.png

Want 是在组件启动时传递的对象,比如在 UIAbilityA 中要启动 UIAbilityB 时,可以通过定义一个 Want 对象传递数据给 UIAbilityB 。

根据是否指定要打开的 Ability 信息,Want 分为两种:

  1. 显式 Want:通过 bundleName 和 abilityName 确定了要打开的 Ability 是什么
  2. 隐式 Want:没有明确指定要打开的 Ability 名称,而是通过 action、entities 定义要使用的内容,由系统匹配支持的应用来处理

可以看到,鸿蒙的 Want 类似 Android 的 Intent。

回到 skills,skills 就是用来声明这个 UIAbility 支持的能力,包括 entities、actions 和 uris 属性:

"skills": [
  {
    "entities": [
      "entity.system.home"
    ],
    "actions": [
      "action.system.home"
    ]
  }
]

actions 表示能够接收的行为,包括系统预定义的和自定义的。entities 和它类似,暂时没搞清楚两者区别。

一个 app 可以配置多个具有入口能力的 skills标签(即配置了ohos.want.action.home 和 entity.system.home)。

OK,这就是一个模块的 module.json5 文件核心属性。

其他

image.png

介绍完上面的内容,entry 文件夹中剩下的就是 cpp、ets 和 resources 文件夹。

  • cpp 用于保存 cpp/h 和 CMakeLists.txt 文件(可以没有)
  • ets 用于存放 .ts 和 .ets 文件(鸿蒙的代码格式)
  • resources 用于保存字符串、颜色值、图片和路由配置等信息

到这里我们就了解了 entry 目录的主要内容及作用。

总结

好了,这篇文章到这里就结束了,主要讲了:

  1. 鸿蒙开发和 Android 开发 IDE 的相似性,降低读者对鸿蒙开发上手的担忧
  2. 鸿蒙项目的关键文件、文件夹的作用,让读者对鸿蒙项目的各个组成部分有更深入的认识

在这篇文章中,我们刻意略过了一些鸿蒙技术名词的含义(比如 Ability),这是因为它展开的内容很多,我们刚接触鸿蒙开发时先不了解这么多,先对鸿蒙项目有个整体的认识,当要修改相关信息时,知道该改哪里,就够了。

后面的文章我们会逐步介绍鸿蒙开发的语言、框架和跨平台技术细节,感兴趣可以持续关注哦~

感谢你的阅读,欢迎留下你对这篇文章以及鸿蒙开发的想法,以及你想了解的鸿蒙开发知识,我们下期再见!