HarmonyOS Next API 24 资源体系:从 $r 到 module.json5
本文基于 HarmonyOS Next API 24+,深入「车辆保养手册」应用的资源层。我们将从
$r资源引用机制入手,讲透 ArkUI 资源系统、module.json5配置、app.json5与module.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.json5 的 app.label(通过 $string:app_name 引用) |
module_desc |
车辆保养周期与配件使用年限查询手册 | module.json5 的 module.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_desc 和 EntryAbility_label 都加到 string.json。这两个是成对出现的——Ability 的 description 和 label 都要在资源里有定义。
资源管理的教训
这段修复历程给了三个工程教训:
- module.json5 的所有
$string:引用都要在 string.json 里有对应定义:编译期会全量校验,缺一个就报错 - 改 module.json5 时要同步想 string.json:增加新 Ability、新扩展点都要带上对应的字符串资源
- 不要把 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_desc、EntryAbility_desc、EntryAbility_label)
这种分工的逻辑:
app_name是整个应用的标识,所有模块共享,放 AppScopemodule_desc、EntryAbility_desc、EntryAbility_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 颜色命名的工程约束
色板命名有几个工程化原则:
- 职能命名而不是色相命名:用
text_primary而不是dark_gray,用primary而不是blue_500。职能命名让代码语义清晰,改色时不影响代码 - 同职能成对:
tag_warn_bg+tag_warn_text是一对,底色与字色配对出现,方便查找 - 三级层次:文字分
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) vscard_bg(#FFFFFF) ≈ 14:1,远超 AAtext_secondary(#646A73) vscard_bg≈ 5.8:1,超过 AAtext_hint(#8F959E) vscard_bg≈ 3.2:1,超过 AA Large(≥ 3:1)
这是色板设计的可访问性底线——文字与背景的对比度必须满足无障碍标准,否则色弱用户和强光环境下难以辨认。
3.4 深色模式支持的预留
色板里有 primary 和 primary_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 的 Abilitylabel引用
5.3 资源查找的优先级
ArkUI 资源查找规则:模块私有优先,找不到再查 AppScope。
例如代码里 $r('app.color.primary'):
- 先在
entry/src/main/resources/base/element/color.json查primary - 找不到则去
AppScope/resources/base/element/color.json�查primary - 两个地方都没有则报错
本应用的所有颜色都放在 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.json5 的 pages 字段引用 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"
}
}
本应用用的是默认模板提供的分层图标,没有自定义图标设计。如果要换应用图标,要同步替换:
AppScope/resources/base/media/startIcon.png(应用图标)entry/src/main/resources/base/media/layered_image.json与对应图层(Ability 图标)
7.3 启动画面 startWindow
startWindowIcon 和 startWindowBackground 定义启动画面:
- 启动画面在应用冷启动时显示,直到 EntryAbility 加载完成
startWindowIcon#media:startIcon:启动画面中央显示的图标startWindowBackground$color:start_window_background:启动画面背景色(#F5F6F8)
这两个资源的配合让应用启动时有一个平滑的过渡——从系统桌面图标 → 启动画面 → 应用首屏,视觉上连贯。本应用的 start_window_background 与 page_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)也用浅色值
这导致深色模式下应用看起来很亮,与系统主题不协调。要完整支持深色模式,需要:
- 在
entry/src/main/resources/dark/element/color.json里定义所有颜色的深色版本 - 把代码里的硬编码 hex(如分类色、
#FFFFFF)搬到资源里,引用$r('app.color.X') - 在
dark/element/string.json里如有需要做深色模式文案调整
8.3 dark element 的工程决策
本应用选择"暂不支持深色模式"是个工程取舍:
- 理由:工具型应用使用时长短,深色模式的边际收益有限;完整适配要补 19 种深色 + 调所有硬编码,工作量不小
- 代价:深色模式下应用显得"刺眼",用户体验有损
这是个"先跑起来再优化"的典型决策。如果应用将来要上架,深色模式适配是必做的工程项——HarmonyOS 应用市场对深色模式支持有审核要求。
九、资源体系的工程化建议
9.1 资源命名规范
从本应用的资源命名归纳出几个规范:
- 职能命名:
text_primary而不是dark_gray_900,page_bg而不是light_gray_100 - 前缀分组:
tag_warn_*、tag_normal_*、tag_replace_*同前缀的归一组 - 配对出现:
tag_warn_bg与tag_warn_text是一对,永远成对新增/删除 - 避免色相命名:
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 的资源体系。关键点归纳:
- **$r 与 string:是两条语法∗∗:代码里用‘string: 是两条语法**:代码里用 `string:是两条语法∗∗:代码里用‘r(‘app.type.name’)
,配置文件里用$type:name` - AppScope 与模块私有分工:应用级共享资源放 AppScope,模块私有资源放模块目录
- module.json5 的所有资源引用都要在对应资源文件里有定义:编译期全量校验,缺一个就报错
- 深色模式靠 dark/ 目录 override:本应用暂未适配,但资源结构已预留扩展空间
- 资源命名按职能:
text_primary而不是dark_gray,改色不影响代码
十一、附录:本篇涉及的资源文件清单
| 文件 | 作用 | 资源数 |
|---|---|---|
AppScope/resources/base/element/string.json |
应用级字符串 | 1(app_name) |
AppScope/app.json5 |
应用级配置 | 引用 app_name、startIcon |
entry/src/main/resources/base/element/string.json |
模块级字符串 | 4(app_name、module_desc、EntryAbility_desc、EntryAbility_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 层,讲透双进度条与分类筛选的工程实现。
更多推荐


所有评论(0)