本文基于 HarmonyOS Next API 24+,深入「车辆保养手册」应用的资源层。我们将从 $r 资源引用机制入手,讲透 ArkUI 资源系统、module.json5 配置、app.json5module.json5 的边界、字符串与颜色资源的工程组织,以及从资源缺失报错到全链路修复的实战经验。

在这里插入图片描述
在这里插入图片描述

一、ArkUI 资源系统总览

1.1 资源是什么

HarmonyOS 的资源(Resource)是把"会因配置而变的内容"从代码里抽出来、放到结构化资源文件里统一管理的机制。典型的资源有:

  • 字符串:应用名、Ability 描述、按钮文案(支持多语言)
  • 颜色:主题色、文字色、背景色(支持深浅模式)
  • 媒体:图标、图片、启动画面
  • profile:布局参数、路由表、备份配置等结构化配置

本应用涉及的资源类型:字符串(string.json)、颜色(color.json)、profile(main_pages)。这些资源分布在两个作用域:

作用域 路径 用途
AppScope AppScope/resources/base/element/ 应用级共享资源
Module(entry) entry/src/main/resources/base/element/ 模块私有资源

1.2 $r 与 $string / $color / $media 的关系

ArkUI 引用资源有两条语法:

  • $r('app.type.name'):通用资源引用,返回 Resource 类型
  • $string:name / $color:name / $media:name:快捷引用,用于特定上下文

本应用代码里大量用 $r

.fontColor($r('app.color.text_primary'))
.backgroundColor($r('app.color.primary'))
.padding({ left: 16, right: 16 })

module.json5 里用的是 $string: / $color: / $media: 快捷引用:

"description": "$string:module_desc",
"icon": "$media:layered_image",
"startWindowBackground": "$color:start_window_background",

两条语法的差异:

语法 使用场景 返回值
$r('app.color.X') ArkUI 代码(ets 文件) Resource 对象
$string:X 配置文件(json5) 字符串资源引用
$color:X 配置文件(json5) 颜色资源引用
$media:X 配置文件(json5) 媒体资源引用

$r 是 ArkTS 代码里的资源引用 API,$string: 等是配置文件里的资源引用 DSL。两者不能混用——配置文件里写 $r('app.string.X') 不会被解析,代码里写 $string:X 不是有效语法。

1.3 $r 的命名空间

$r('app.color.text_primary') 的路径 'app.color.text_primary' 是三层命名:

app.color.text_primary
│   │   │
│   │   └─ 资源名
│   └── 资源类型
─ 作用域
  • app:作用域,表示应用级资源(也可以是 module 名)
  • color:资源类型(color / string / media / profile 等)
  • text_primary:资源名(在 color.json 的 name 字段定义)

本应用代码里所有颜色引用都走 app 作用域——颜色资源在 entry/src/main/resources/base/element/color.json 里,但用 app 引用而不是 entry。这是因为 HarmonyOS 的资源作用域规则:模块私有资源可以用 app 命名空间引用,ArkUI 会在模块和 AppScope 两层查找。

二、字符串资源体系

2.1 module.json5 的字符串引用

本应用的 entry/src/main/module.json5 引用了三个字符串资源:

"module": {
  "description": "$string:module_desc",        // 模块描述
  "abilities": [{
    "description": "$string:EntryAbility_desc",  // Ability 描述
    "label": "$string:EntryAbility_label",       // Ability 显示名
  }]
}

这三个引用都必须在 entry/src/main/resources/base/element/string.json 里有定义,否则编译期会报"The resource reference ‘$string:XXX’ is not defined"。

2.2 string.json 的最终内容

经过几次迭代修复,最终的 string.json:

{
  "string": [
    {
      "name": "app_name",
      "value": "车辆保养手册"
    },
    {
      "name": "module_desc",
      "value": "车辆保养周期与配件使用年限查询手册"
    },
    {
      "name": "EntryAbility_desc",
      "value": "车辆保养手册主入口"
    },
    {
      "name": "EntryAbility_label",
      "value": "车辆保养手册"
    }
  ]
}

四个字符串资源的用途:

资源名 引用方
app_name 车辆保养手册 app.json5app.label(通过 $string:app_name 引用)
module_desc 车辆保养周期与配件使用年限查询手册 module.json5module.description
EntryAbility_desc 车辆保养手册主入口 module.json5 的 Ability description
EntryAbility_label 车辆保养手册 module.json5 的 Ability label(应用在桌面的显示名)

2.3 资源缺失报错与修复历程

应用开发过程中遇到了一系列资源引用错误,修复历程是个典型的工程经验:

第一次:app_name 缺失

最初 string.json 只有少量字符串,应用编译时报:

Error: The resource reference '$string:module_desc' is not defined.

修复:把 module_desc 加到 string.json。

第二次:EntryAbility_desc 缺失

报:

Error: The resource reference '$string:EntryAbility_desc' is not defined.

修复:把 EntryAbility_descEntryAbility_label 都加到 string.json。这两个是成对出现的——Ability 的 description 和 label 都要在资源里有定义。

资源管理的教训

这段修复历程给了三个工程教训:

  1. module.json5 的所有 $string: 引用都要在 string.json 里有对应定义:编译期会全量校验,缺一个就报错
  2. 改 module.json5 时要同步想 string.json:增加新 Ability、新扩展点都要带上对应的字符串资源
  3. 不要把 string.json 当成"只放被引用的字符串":如果 string.json 里有未被引用的键,编译器不会报错(只是冗余),但反过来缺引用就报错

2.4 AppScope 与 module 的字符串分工

本应用有两个 string.json:

  • AppScope/resources/base/element/string.json:应用级字符串(app_name
  • entry/src/main/resources/base/element/string.json:模块级字符串(module_descEntryAbility_descEntryAbility_label

这种分工的逻辑:

  • app_name 是整个应用的标识,所有模块共享,放 AppScope
  • module_descEntryAbility_descEntryAbility_label 是 entry 模块特有的描述,放模块私有

ArkUI 的资源查找规则是"模块私有优先,找不到再查 AppScope"。因此 app_name 放 AppScope 后,所有模块都能用 $string:app_name 引用;模块私有字符串放模块私有目录,不会污染其他模块。

三、颜色资源体系

3.1 color.json 的完整色板

{
  "color": [
    { "name": "start_window_background", "value": "#F5F6F8" },
    { "name": "page_bg",                 "value": "#F5F6F8" },
    { "name": "primary",                 "value": "#1F6FEB" },
    { "name": "primary_dark",            "value": "#1554C4" },
    { "name": "accent",                  "value": "#FF8C1A" },
    { "name": "card_bg",                 "value": "#FFFFFF" },
    { "name": "card_border",             "value": "#E6E8EB" },
    { "name": "text_primary",            "value": "#1F2329" },
    { "name": "text_secondary",          "value": "#646A73" },
    { "name": "text_hint",               "value": "#8F959E" },
    { "name": "tag_warn_bg",             "value": "#FFF1E0" },
    { "name": "tag_warn_text",           "value": "#E8740F" },
    { "name": "tag_normal_bg",           "value": "#E8F3FF" },
    { "name": "tag_normal_text",         "value": "#1F6FEB" },
    { "name": "tag_replace_bg",          "value": "#FDE2E4" },
    { "name": "tag_replace_text",        "value": "#D7263D" },
    { "name": "divider",                 "value": "#EDEEEF" },
    { "name": "tab_indicator",           "value": "#1F6FEB" },
    { "name": "header_bg",               "value": "#FFFFFF" }
  ]
}

19 种颜色组成了完整的色板。这些颜色按职能分五组:

颜色 用途
背景 page_bg, card_bg, header_bg, start_window_background 不同区域的底色
主色 primary, primary_dark, accent 主色与强调色
文字 text_primary, text_secondary, text_hint 三级文字层次
边框 card_border, divider 卡片边框与分隔线
标签 tag_warn_*, tag_normal_*, tag_replace_* 三种标签的底色与字色

3.2 颜色命名的工程约束

色板命名有几个工程化原则:

  1. 职能命名而不是色相命名:用 text_primary 而不是 dark_gray,用 primary 而不是 blue_500。职能命名让代码语义清晰,改色时不影响代码
  2. 同职能成对tag_warn_bg + tag_warn_text 是一对,底色与字色配对出现,方便查找
  3. 三级层次:文字分 primary / secondary / hint 三级,对应主文字、副文字、提示文字的视觉重量递减

3.3 颜色值的取色逻辑

19 种颜色的取值都经过对比度与视觉重量的考量:

颜色名 hex RGB 视觉重量
text_primary #1F2329 (31, 35, 41) 重,主文字
text_secondary #646A73 (100, 106, 115) 中,副文字
text_hint #8F959E (143, 149, 158) 轻,提示文字
divider #EDEEEF (237, 238, 239) 极轻,分隔线
page_bg #F5F6F8 (245, 246, 248) 极轻,页面底
card_bg #FFFFFF (255, 255, 255) 无色,卡片底
primary #1F6FEB (31, 111, 235) 重,主色
accent #FF8C1A (255, 140, 26) 中,强调色

文字色(text_*)与背景色(page_bg / card_bg)的对比度都满足 WCAG AA 标准(≥ 4.5:1):

  • text_primary (#1F2329) vs card_bg (#FFFFFF) ≈ 14:1,远超 AA
  • text_secondary (#646A73) vs card_bg ≈ 5.8:1,超过 AA
  • text_hint (#8F959E) vs card_bg ≈ 3.2:1,超过 AA Large(≥ 3:1)

这是色板设计的可访问性底线——文字与背景的对比度必须满足无障碍标准,否则色弱用户和强光环境下难以辨认。

3.4 深色模式支持的预留

色板里有 primaryprimary_dark 两个主色:

  • primary #1F6FEB:浅色模式主色(亮蓝)
  • primary_dark #1554C4:深色模式主色(深蓝)

本应用目前只设计了浅色模式,但 primary_dark 已经预留。HarmonyOS 的资源系统支持深色模式 override——在 entry/src/main/resources/dark/element/color.json 里定义同名颜色的深色版本,系统会自动按当前模式选择。

本应用代码里所有 $r('app.color.primary') 都走浅色版,深色模式适配时要在 dark/element/color.json 里补全所有颜色的深色版本,并把代码里的 #FFFFFF 等硬编码 hex 也搬到资源里。

四、module.json5 配置详解

4.1 module.json5 的完整结构

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone"],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["ohos.want.action.home"]
          }
        ]
      }
    ],
    "extensionAbilities": [
      {
        "name": "EntryBackupAbility",
        "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
        "type": "backup",
        "exported": false,
        "metadata": [
          {
            "name": "ohos.extension.backup",
            "resource": "$profile:backup_config"
          }
        ]
      }
    ]
  }
}

4.2 module 级配置

字段 含义
name entry 模块名,与目录名对应
type entry 模块类型,entry 表示主入口模块
description $string:module_desc 模块描述,引用字符串资源
mainElement EntryAbility 主入口 Ability 名,应用启动时打开
deviceTypes ["phone"] 支持的设备类型,本应用只支持手机
deliveryWithInstall true 安装时一并交付模块
installationFree false 不支持免安装
pages $profile:main_pages 页面路由表,引用 profile 资源

4.3 Ability 配置

abilities 数组里定义了一个 EntryAbility

字段 含义
name EntryAbility Ability 名,与 srcEntry 文件名对应
srcEntry ./ets/entryability/EntryAbility.ets Ability 源码入口
description $string:EntryAbility_desc Ability 描述
icon $media:layered_image 桌面图标(分层图标)
label $string:EntryAbility_label 桌面显示名
startWindowIcon $media:startIcon 启动画面图标
startWindowBackground $color:start_window_background 启动画面背景色
exported true 可被其他应用调用
skills [...] Ability 支持的意图过滤器

skills 数组里有一个意图过滤器:

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

这个过滤器声明 EntryAbility 支持 entity.system.home 实体的 ohos.want.action.home 动作——这是 HarmonyOS 的桌面入口约定。配了这个过滤器,应用才会出现在桌面图标列表里,点击图标启动 EntryAbility

4.4 extensionAbilities 配置

extensionAbilities 数组里定义了一个 EntryBackupAbility

{
  "name": "EntryBackupAbility",
  "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
  "type": "backup",
  "exported": false,
  "metadata": [{
    "name": "ohos.extension.backup",
    "resource": "$profile:backup_config"
  }]
}

这是个备份扩展能力,用于应用数据备份与恢复。本应用是静态数据工具,没有用户数据要备份,但 HarmonyOS 工程模板默认带这个扩展能力。metadata 里的 resource 引用了 $profile:backup_config,指向 entry/src/main/resources/base/profile/backup_config.json 备份配置文件。

五、app.json5 与 module.json5 的边界

5.1 app.json5 的职责

AppScope/app.json5 是应用级配置,定义整个应用共享的属性:

{
  "app": {
    "bundleName": "com.example.doubleinfo",
    "vendor": "example",
    "versionCode": 1,
    "versionName": "1.0.0",
    "icon": "$media:startIcon",
    "label": "$string:app_name"
  }
}
字段 含义
bundleName com.example.doubleinfo 应用包名,全球唯一
vendor example 供应商
versionCode 1 版本号(数字,递增)
versionName 1.0.0 版本名(展示用)
icon $media:startIcon 应用图标
label $string:app_name 应用名(引用字符串资源)

5.2 两份配置的边界

维度 app.json5 module.json5
作用域 应用级(AppScope) 模块级(entry 等)
共享性 所有模块共享 模块私有
内容 包名、版本、应用名、应用图标 模块名、Ability、路由、扩展能力
资源引用 $string:app_name(引用 AppScope 的字符串) $string:module_desc(引用模块私有字符串)

关键边界:

  • 应用名 app_name:放 AppScope 的 string.json,被 app.json5 的 label 引用,所有模块共享
  • 模块描述 module_desc:放 entry 的 string.json,被 module.json5 的 description 引用,只在 entry 模块可见
  • Ability 名 EntryAbility_label:放 entry 的 string.json,被 module.json5 的 Ability label 引用

5.3 资源查找的优先级

ArkUI 资源查找规则:模块私有优先,找不到再查 AppScope

例如代码里 $r('app.color.primary')

  1. 先在 entry/src/main/resources/base/element/color.jsonprimary
  2. 找不到则去 AppScope/resources/base/element/color.json �查 primary
  3. 两个地方都没有则报错

本应用的所有颜色都放在 entry 模块的 color.json 里,AppScope 没有 color.json。字符串资源则分两处——app_name 在 AppScope,其余在 entry。这是按"是否多模块共享"划分的:

  • 应用名 app_name 假设未来可能有其他模块(如 feature 模块)引用,放 AppScope 共享
  • 模块描述等只有 entry 用,放 entry 私有

六、profile 资源:main_pages 路由表

6.1 main_pages 的内容

entry/src/main/resources/base/profile/main_pages.json 是页面路由表:

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

这声明了应用有一个页面 pages/Index,对应 entry/src/main/ets/pages/Index.ets 文件。

6.2 module.json5 与 main_pages 的关系

module.json5pages 字段引用 main_pages

"pages": "$profile:main_pages"

这是 HarmonyOS 的页面注册机制——所有页面都要在 main_pages.json 里声明,否则无法被路由系统识别。本应用只有一个页面,所以 src 数组只有一项 "pages/Index"

如果应用扩展成多页面(例如加一个详情页 pages/Detail.ets),就要同步在 main_pages.json 里加 "pages/Detail"。这是 HarmonyOS 路由注册的硬约束。

6.3 ArkUI 路由 API 与 main_pages 的配合

HarmonyOS 的路由 API(router.push / router.replaceUrl)要求目标页面在 main_pages.json 里注册:

import router from '@ohos.router'

router.pushUrl({
  url: 'pages/Detail'   // 必须在 main_pages.json 里声明
})

本应用没有用路由 API——所有交互在单页内完成。但 main_pages.json 仍然必须有 pages/Index,因为这是 EntryAbility 启动时的入口页面。

七、media 资源:图标与启动画面

7.1 module.json5 引用的 media 资源

"icon": "$media:layered_image",      // Ability 桌面图标
"startWindowIcon": "$media:startIcon",  // 启动画面图标

两个 media 资源:

资源名 用途 文件位置
layered_image Ability 分层图标 entry/src/main/resources/base/media/layered_image.json + 图层图
startIcon 启动画面图标 entry/src/main/resources/base/media/startIcon.png

7.2 分层图标 layered_image

HarmonyOS Next 支持分层图标(layered image)——一个图标由前景图和背景图两层叠加组成,支持在不同主题下显示不同效果。

layered_image.json 大致结构:

{
  "layered-image": {
    "background": "$media:layered_image_background",
    "foreground": "$media:layered_image_foreground"
  }
}

本应用用的是默认模板提供的分层图标,没有自定义图标设计。如果要换应用图标,要同步替换:

  1. AppScope/resources/base/media/startIcon.png(应用图标)
  2. entry/src/main/resources/base/media/layered_image.json 与对应图层(Ability 图标)

7.3 启动画面 startWindow

startWindowIconstartWindowBackground 定义启动画面:

  • 启动画面在应用冷启动时显示,直到 EntryAbility 加载完成
  • startWindowIcon #media:startIcon:启动画面中央显示的图标
  • startWindowBackground $color:start_window_background:启动画面背景色(#F5F6F8

这两个资源的配合让应用启动时有一个平滑的过渡——从系统桌面图标 → 启动画面 → 应用首屏,视觉上连贯。本应用的 start_window_backgroundpage_bg 都是 #F5F6F8,让启动画面到应用首屏的过渡几乎无感。

八、dark element 与深色模式适配

8.1 dark element 目录结构

HarmonyOS 的资源系统支持深色模式 override:

entry/src/main/resources/
├─ base/
│   └: ...
│   ├─element/
│   │   ├─ color.json    ← 浅色模式(默认)
│   │   └: ...
│   │   └: ...
│   ├─element/
│   │   ├─ color.json    ← 浅色模式(默认)
│   │   └: ...
│   └: ...
│   └: ...
│   └: ...
│   ├─element/
│   │   ├─ color.json    ← 深色模式 override
│   │   └: ...
│   └: ...
│   └: ...
└─<s/ ...

base/ 是默认资源(浅色模式),dark/ 是深色模式 override。系统按当前模式选择加载哪个目录的资源。

8.2 本应用的深色模式现状

本应用目前没有 dark/ 目录,意味着所有资源走浅色版。如果系统切到深色模式:

  • 颜色资源都用 base/element/color.json 的浅色版(如 page_bg #F5F6F8
  • 代码里的硬编码 hex(如分类色 #1F6FEB)也用浅色值

这导致深色模式下应用看起来很亮,与系统主题不协调。要完整支持深色模式,需要:

  1. entry/src/main/resources/dark/element/color.json 里定义所有颜色的深色版本
  2. 把代码里的硬编码 hex(如分类色、#FFFFFF)搬到资源里,引用 $r('app.color.X')
  3. dark/element/string.json 里如有需要做深色模式文案调整

8.3 dark element 的工程决策

本应用选择"暂不支持深色模式"是个工程取舍:

  • 理由:工具型应用使用时长短,深色模式的边际收益有限;完整适配要补 19 种深色 + 调所有硬编码,工作量不小
  • 代价:深色模式下应用显得"刺眼",用户体验有损

这是个"先跑起来再优化"的典型决策。如果应用将来要上架,深色模式适配是必做的工程项——HarmonyOS 应用市场对深色模式支持有审核要求。

九、资源体系的工程化建议

9.1 资源命名规范

从本应用的资源命名归纳出几个规范:

  1. 职能命名text_primary 而不是 dark_gray_900page_bg 而不是 light_gray_100
  2. 前缀分组tag_warn_*tag_normal_*tag_replace_* 同前缀的归一组
  3. 配对出现tag_warn_bgtag_warn_text 是一对,永远成对新增/删除
  4. 避免色相命名primary 而不是 blue_500,改色时不影响代码

9.2 资源作用域划分

资源该放 AppScope 还是模块私有?判据:

场景 放哪里
多模块共享的资源(应用名、应用图标) AppScope
单模块私有的资源(模块描述、Ability 名) 模块私有
不确定的资源 先放模块私有,将来有共享需求再迁 AppScope

9.3 资源引用的完整性检查

本应用开发过程中遇到的资源缺失报错,归纳出完整性检查清单:

检查项 方法
module.json5 的 $string: 引用 每个引用都要在 string.json 里有定义
module.json5 的 $color: 引用 每个引用都要在 color.json 里有定义
module.json5 的 $media: 引用 每个引用都要在 media 目录里有对应文件
module.json5 的 $profile: 引用 每个引用都要在 profile 目录里有对应 json
app.json5 的 $string: / $media: 引用 同上,但查 AppScope 的资源目录
代码里的 $r('app.color.X') X 要在 color.json 里有定义
代码里的 $r('app.string.X') X 要在 string.json 里有定义

每次改 module.json5 / app.json5 时都要同步想资源文件——增加新 Ability、新扩展能力、新路由都要带上对应的字符串、颜色、profile 资源。

十、本篇小结

本章从 $r 资源引用机制入手,完整覆盖了 HarmonyOS Next API 24 的资源体系。关键点归纳:

  1. **$r 与 string:是两条语法∗∗:代码里用‘string: 是两条语法**:代码里用 `string:是两条语法:代码里用r(‘app.type.name’),配置文件里用 $type:name`
  2. AppScope 与模块私有分工:应用级共享资源放 AppScope,模块私有资源放模块目录
  3. module.json5 的所有资源引用都要在对应资源文件里有定义:编译期全量校验,缺一个就报错
  4. 深色模式靠 dark/ 目录 override:本应用暂未适配,但资源结构已预留扩展空间
  5. 资源命名按职能text_primary 而不是 dark_gray,改色不影响代码

十一、附录:本篇涉及的资源文件清单

文件 作用 资源数
AppScope/resources/base/element/string.json 应用级字符串 1(app_name
AppScope/app.json5 应用级配置 引用 app_namestartIcon
entry/src/main/resources/base/element/string.json 模块级字符串 4(app_namemodule_descEntryAbility_descEntryAbility_label
entry/src/main/resources/base/element/color.json 模块级颜色 19
entry/src/main/resources/base/profile/main_pages.json 页面路由表 1(pages/Index
entry/src/main/resources/base/profile/backup_config.json 备份配置 默认模板
entry/src/main/resources/base/media/layered_image.* Ability 分层图标 默认模板
entry/src/main/resources/base/media/startIcon.png 启动画面图标 默认模板
entry/src/main/module.json5 模块配置 引用多个资源

至此完整覆盖了"HarmonyOS Next API 24 资源体系"的全貌。下一篇将进入信息可视化 UX 层,讲透双进度条与分类筛选的工程实现。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐