在这里插入图片描述

每日一句正能量

不必向三观不同的人解释,沉默是对轻看最好的回击。
解释消耗能量,沉默保存能量并释放边界信号。对轻看者解释,等于邀请对方继续评判你;而沉默表明:你的看法不影响我,我无需你的批准。不辩解、不愤怒、不讨好,只是轻轻移开注意力。

摘要

资源文件管理是 HarmonyOS 应用开发中极易被忽视却直接影响包体积、启动性能和国际化体验的关键环节。ArkTS 的资源体系围绕 resources 目录构建,通过限定词(Qualifier)机制实现一套资源、多端适配。本文从资源目录的物理结构出发,深入解析限定词匹配优先级、$r()$rawfile() 的引用差异、多语言国际化实现路径,以及 ResourceManager 运行时 API 的高级用法。结合一个完整的「多模块资源治理」实战案例,演示如何在大型项目中通过分层架构、资源去重和 Token 代码生成工具链,实现资源文件的可维护、可扩展与高性能。文章最后给出包体积优化策略与常见陷阱规避方案,帮助开发者建立系统级的资源管理思维。


一、ArkTS 资源体系概述

在 HarmonyOS 中,资源(Resource)不仅指图片和字符串,还包括颜色、尺寸、布尔值、整数、媒体文件、配置文件等。ArkTS 的资源体系采用「集中管理 + 限定词分发」的设计理念:所有资源统一存放在 resources 目录下,系统根据设备特征、语言、主题等上下文自动选择最合适的资源版本。

1.1 资源类型全景

资源类型 存放目录 引用方式 典型用途
字符串 element/string.json $r('app.string.xxx') UI 文本、提示信息
颜色 element/color.json $r('app.color.xxx') 主题色、状态色
尺寸 element/float.json $r('app.float.xxx') 间距、圆角、阴影偏移
布尔值 element/bool.json $r('app.bool.xxx') 功能开关
整数 element/integer.json $r('app.integer.xxx') 计数、索引
图片/媒体 media/ $r('app.media.xxx') 图标、背景图、视频
配置文件 profile/ $r('app.profile.xxx') 应用配置、路由表
原始文件 rawfile/ $rawfile('xxx') JSON、CSV、HTML 模板
图标 mipmap/ $r('app.mipmap.xxx') 应用图标、通知图标

1.2 与 Android / iOS 资源体系的差异

维度 Android iOS HarmonyOS ArkTS
目录结构 res/values-zh/ zh.lproj/ resources/zh_CN/element/
引用方式 R.string.xxx NSLocalizedString $r('app.string.xxx')
运行时加载 Resources.getString() Bundle.main.path() resourceManager.getStringSync()
限定词维度 语言、密度、方向 语言 语言、主题、密度、方向、设备类型
原始文件 assets/ Bundle rawfile/

ArkTS 的资源体系在设计上吸收了 Android 的限定词思想和 iOS 的 Bundle 简洁性,同时通过 $r() 的编译期解析实现了类型安全。


二、资源目录结构与限定词系统

resources 目录是 ArkTS 资源管理的核心容器,其内部结构决定了资源匹配的精确度和应用包体积。

在这里插入图片描述

2.1 基础目录:base

base 目录是资源的「兜底」目录,包含应用运行所必需的全部资源。当系统无法在任何限定词目录中找到匹配资源时,会回退到 base 目录。

resources/
└── base/
    ├── element/
    │   ├── string.json       # 字符串资源
    │   ├── color.json        # 颜色资源
    │   ├── float.json        # 尺寸资源
    │   ├── bool.json         # 布尔资源
    │   └── integer.json      # 整数资源
    ├── media/
    │   ├── icon_home.png
    │   └── banner_default.png
    ├── profile/
    │   └── app_config.json   # 应用级配置
    └── mipmap/
        └── icon.png          # 应用图标

2.2 限定词目录命名规则

限定词目录通过「目录名_限定词值」的格式命名,多个限定词用「-」连接。系统按优先级从高到低匹配:

resources/
├── base/                          # 兜底目录
├── zh_CN/                         # 简体中文(中国大陆)
├── zh_TW/                         # 繁体中文(中国台湾)
├── en_US/                         # 美式英语
├── en/                            # 通用英语(兜底)
├── dark/                          # 深色模式
├── zh_CN-dark/                    # 简体中文 + 深色模式(组合限定词)
└── tablet/                        # 平板设备

限定词优先级规则(从高到低):

  1. 语言 + 区域(如 zh_CN)> 仅语言(如 zh)> base
  2. 主题(如 dark)作为独立维度与语言组合
  3. 设备类型(如 tabletwearable
  4. 屏幕密度(如 sdpimdpixhdpixxhdpi

2.3 限定词匹配实战

假设系统语言为「简体中文(中国大陆)」,主题为「深色模式」,系统会按以下顺序查找 string.json

1. resources/zh_CN-dark/element/string.json   ← 最精确匹配
2. resources/zh_CN/element/string.json         ← 语言精确匹配
3. resources/zh/element/string.json             ← 语言模糊匹配
4. resources/dark/element/string.json           ← 主题匹配
5. resources/base/element/string.json           ← 兜底

关键原则:限定词目录只需存放与 base 不同的资源,相同资源无需重复放置。这既是包体积优化的核心手段,也是资源维护的黄金法则。


三、资源引用机制: r ( ) 、 r()、 r()rawfile() 与 ResourceManager

ArkTS 提供了三种资源引用方式,分别适用于不同的场景和性能要求。

在这里插入图片描述

3.1 $r():编译期结构化引用

$r() 是 ArkTS 最常用的资源引用方式,在编译阶段将资源标识符解析为内部 ID,运行时直接通过 ID 查找资源值。

// string.json
{
  "string": [
    { "name": "app_name", "value": "智慧办公" },
    { "name": "welcome_msg", "value": "欢迎使用 HarmonyOS 应用" }
  ]
}

// color.json
{
  "color": [
    { "name": "brand_primary", "value": "#0d6efd" },
    { "name": "surface", "value": "#ffffff" }
  ]
}

// 在组件中使用
Text($r('app.string.welcome_msg'))
  .fontColor($r('app.color.brand_primary'))
  .backgroundColor($r('app.color.surface'))

$r() 的优势

  • 编译期校验:拼写错误的资源名会在编译时报错,而非运行时崩溃;
  • 类型安全$r('app.color.xxx') 返回 ResourceColor,可直接用于 .fontColor()
  • 零运行时解析开销:资源 ID 在编译期确定,运行时仅做 O(1) 的数组索引查找;
  • 自动限定词匹配:系统自动选择最合适的资源版本,开发者无需手动判断。

3.2 $rawfile():运行时原始文件引用

rawfile 目录下的文件不参与编译期解析,以原始字节流形式打包进 HAP。适用于配置文件、数据文件、HTML 模板等非结构化资源。

// 读取 rawfile 目录下的 JSON 配置文件
async loadConfig() {
  const context = getContext(this);
  const rawFile = await context.resourceManager.getRawFileContent('config/app_settings.json');
  const jsonStr = buffer.from(rawFile).toString('utf8');
  const config = JSON.parse(jsonStr);
  console.info(`API 地址: ${config.apiBaseUrl}`);
}

// 在 Image 组件中直接使用 rawfile 图片
Image($rawfile('images/splash_bg.png'))
  .width('100%')
  .height('100%')

$rawfile() 的局限

  • 不支持限定词自动匹配(rawfile 本身可建子目录手动管理);
  • 运行时 I/O 读取,有性能开销;
  • 无编译期校验,路径错误仅在运行时暴露。

3.3 ResourceManager:运行时动态 API

ResourceManager 提供了一套完整的运行时资源操作 API,适用于需要根据动态条件加载资源的场景。

import resourceManager from '@ohos.resourceManager';

class ResourceLoader {
  private resMgr: resourceManager.ResourceManager;

  constructor(context: Context) {
    this.resMgr = context.resourceManager;
  }

  // 同步获取字符串
  getStringSync(resId: number): string {
    return this.resMgr.getStringSync(resId);
  }

  // 异步获取字符串(支持参数插值)
  async getString(resId: number, ...args: string[]): Promise<string> {
    return this.resMgr.getString(resId, args);
  }

  // 获取颜色
  getColor(resId: number): number {
    return this.resMgr.getColorSync(resId);
  }

  // 获取媒体文件路径
  async getMediaPath(resId: number): Promise<string> {
    return this.resMgr.getMediaContentBase64(resId);
  }

  // 获取设备支持的资源目录列表
  async getLocales(): Promise<Array<string>> {
    return this.resMgr.getLocales();
  }
}

典型应用场景

  • 插件化架构中动态加载插件资源;
  • A/B 测试中根据实验组加载不同文案;
  • 运行时语言切换(无需重启应用)。

四、多语言国际化:从 string.json 到 plural.json

国际化(i18n)是应用走向全球市场的必经之路。ArkTS 的国际化体系以 string.json 为基础,通过限定词目录实现多语言隔离。

在这里插入图片描述

4.1 基础字符串国际化

// resources/base/element/string.json
{
  "string": [
    { "name": "app_name", "value": "Smart Office" },
    { "name": "login_title", "value": "Welcome Back" },
    { "name": "login_button", "value": "Sign In" }
  ]
}

// resources/zh_CN/element/string.json
{
  "string": [
    { "name": "app_name", "value": "智慧办公" },
    { "name": "login_title", "value": "欢迎回来" },
    { "name": "login_button", "value": "登录" }
  ]
}

4.2 复数形式(Plural)

不同语言对复数的处理规则不同(如英语分 one/other,俄语分 one/few/many/other)。ArkTS 通过 plural.json 支持 ICU MessageFormat 风格的复数表达。

// resources/base/element/plural.json
{
  "plural": [
    {
      "name": "unread_messages",
      "value": {
        "zero": "You have no unread messages",
        "one": "You have 1 unread message",
        "other": "You have %d unread messages"
      }
    }
  ]
}

// resources/zh_CN/element/plural.json
{
  "plural": [
    {
      "name": "unread_messages",
      "value": {
        "other": "你有 %d 条未读消息"
      }
    }
  ]
}
// 在代码中使用复数资源
Text(this.resMgr.getPluralStringSync(
  $r('app.plural.unread_messages').id, 
  5,  // 数量
  5   // 插值参数
))

4.3 字符串参数插值

ArkTS 支持在 string.json 中定义带占位符的字符串,运行时动态填充。

{
  "string": [
    { 
      "name": "greeting", 
      "value": "Hello, %s! You have %d new notifications." 
    }
  ]
}
// 异步填充参数
async showGreeting(userName: string, count: number) {
  const message = await this.resMgr.getString(
    $r('app.string.greeting').id, 
    userName, 
    count.toString()
  );
  this.toastMessage = message;
}

4.4 语言切换与实时刷新

HarmonyOS 支持应用内语言切换,无需重启。通过监听系统 Locale 变化并刷新 UI 状态即可实现:

import i18n from '@ohos.i18n';

@Entry
@Component
struct I18nDemoPage {
  @State currentLocale: string = i18n.getSystemLocale();

  aboutToAppear() {
    // 监听系统语言变化
    i18n.getSystemLocale();
    // 实际开发中应注册系统配置变化监听
  }

  async switchLanguage(locale: string) {
    // 切换应用语言(需配合应用配置)
    await i18n.setSystemLocale(locale);
    this.currentLocale = locale;
    // 触发页面重建,自动重新匹配资源
  }
}

五、主题资源与深色模式适配

上一篇文章介绍了 ArkTS 的组件样式与主题系统,本节从资源管理角度补充主题资源的分发机制。

5.1 深色模式资源目录

通过 dark 限定词目录,可以为深色模式提供独立的资源覆盖:

resources/
├── base/element/color.json          # 浅色模式颜色
├── base/media/bg_home.png         # 浅色模式背景
├── dark/element/color.json        # 深色模式颜色覆盖
└── dark/media/bg_home.png         # 深色模式背景覆盖
// resources/base/element/color.json
{
  "color": [
    { "name": "surface", "value": "#ffffff" },
    { "name": "on_surface", "value": "#1a1a2e" },
    { "name": "divider", "value": "#e9ecef" }
  ]
}

// resources/dark/element/color.json
{
  "color": [
    { "name": "surface", "value": "#121212" },
    { "name": "on_surface", "value": "#e0e0e0" },
    { "name": "divider", "value": "#333333" }
  ]
}

5.2 运行时监听主题变化

import { themeManager } from '@kit.ArkUI';

@Entry
@Component
struct ThemeAwarePage {
  @State isDarkMode: boolean = false;

  aboutToAppear() {
    // 获取当前主题模式
    this.isDarkMode = themeManager.getThemeMode() === ThemeMode.DARK;
    
    // 监听主题变化
    themeManager.on('themeModeChange', (mode) => {
      this.isDarkMode = mode === ThemeMode.DARK;
      // 所有使用 $r('app.color.xxx') 的组件会自动重新匹配资源
    });
  }

  aboutToDisappear() {
    themeManager.off('themeModeChange');
  }
}

5.3 组合限定词:语言 + 主题

对于需要同时适配语言和主题的场景,可以使用组合限定词目录:

resources/
├── zh_CN-dark/element/string.json   # 简体中文 + 深色模式
├── zh_CN/element/string.json         # 简体中文 + 浅色模式
├── en_US-dark/element/string.json    # 美式英语 + 深色模式
└── en_US/element/string.json         # 美式英语 + 浅色模式

注意:组合限定词目录会增加包体积和维护成本,建议仅在必要时使用,优先通过 ColorToken 在代码层处理主题差异。


六、动态资源加载与运行时管理

在某些高级场景中,资源需要在运行时动态决策加载,而非编译期绑定。

6.1 动态选择资源 ID

@Entry
@Component
struct DynamicResourcePage {
  @State currentMood: 'happy' | 'sad' | 'neutral' = 'happy';

  getMoodIcon(): Resource {
    const moodMap: Record<string, Resource> = {
      'happy': $r('app.media.icon_happy'),
      'sad': $r('app.media.icon_sad'),
      'neutral': $r('app.media.icon_neutral'),
    };
    return moodMap[this.currentMood];
  }

  build() {
    Column() {
      Image(this.getMoodIcon())
        .width(64)
        .height(64)
    }
  }
}

6.2 从网络加载资源并缓存

import { request } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';

class NetworkResourceCache {
  private cacheDir: string;

  constructor(context: Context) {
    this.cacheDir = context.cacheDir + '/remote_resources';
    fileIo.mkdir(this.cacheDir).catch(() => {}); // 目录已存在则忽略
  }

  async loadImage(url: string): Promise<string> {
    const fileName = this.hashUrl(url);
    const localPath = `${this.cacheDir}/${fileName}`;
    
    // 检查本地缓存
    try {
      await fileIo.access(localPath);
      return localPath;
    } catch {
      // 下载并缓存
      const response = await request.download(url, localPath);
      return localPath;
    }
  }

  private hashUrl(url: string): string {
    // 简化示例,实际应使用 SHA-256
    return url.replace(/[^a-zA-Z0-9]/g, '_');
  }
}

6.3 rawfile 子目录管理

rawfile 支持子目录,适合按模块或版本组织原始资源:

resources/rawfile/
├── v1/
│   ├── terms_of_service.html
│   └── privacy_policy.html
├── v2/
│   ├── terms_of_service.html
│   └── privacy_policy.html
└── templates/
    ├── email_welcome.html
    └── email_reset_password.html
// 读取版本化的用户协议
async loadTerms(version: string): Promise<string> {
  const context = getContext(this);
  const content = await context.resourceManager.getRawFileContent(`${version}/terms_of_service.html`);
  return buffer.from(content).toString('utf8');
}

七、大型项目资源工程化实践

当项目规模扩大,资源文件数量从几十个增长到数百个时,缺乏治理的资源目录将成为技术债务的重灾区。

在这里插入图片描述

7.1 分层架构设计

业务层(Business):各业务模块(如 feature_homefeature_mine)独立维护自身资源,通过 HAR(HarmonyOS Archive)包隔离。

共享层(Shared):公共组件(如按钮、对话框、导航栏)的资源统一放在 shared_components 模块,避免各业务重复定义。

基础层(Foundation):Design Token 定义(颜色、字体、间距)放在 base_tokens 模块,作为所有上层模块的依赖。

平台层(Platform):系统兜底资源,仅包含 base 目录和必要的 rawfile

7.2 资源命名规范

建立统一的命名规范是资源治理的第一步:

# 颜色资源:{语义}_{层级}_{状态}
color_brand_primary_default
color_brand_primary_pressed
color_surface_elevated
color_text_primary_disabled

# 字符串资源:{模块}_{页面}_{元素}
string_login_title_welcome
string_login_hint_username
string_login_error_invalid_password

# 媒体资源:{模块}_{用途}_{规格}
media_home_banner_720x360
media_mine_avatar_placeholder
media_common_icon_arrow_right

7.3 资源去重与复用

在多模块项目中,相同资源(如通用图标)可能在多个 HAR 中重复打包,导致包体积膨胀。解决方案:

  1. 公共模块抽离:将通用资源集中到 shared_resources HAR 中,各业务模块通过依赖引用;
  2. 构建期去重:在 Hvigor 构建脚本中配置 resourceMerge 规则,自动合并重复资源;
  3. 矢量图优先:使用 SVG 矢量图替代多密度位图,一套资源适配所有屏幕。
// 在 build-profile.json5 中配置资源合并
{
  "modules": [
    {
      "name": "feature_home",
      "resourceMerge": {
        "enabled": true,
        "duplicates": "merge"  // 重复资源自动合并
      }
    }
  ]
}

7.4 Token 代码生成工具链

手动维护 color.json 与代码中的 ColorToken 类容易出错。建议建立自动化工具链:

// 工具链流程:
// 1. 设计师在 Figma 中维护 Design Token
// 2. 导出为 tokens.json(W3C Design Tokens Format)
// 3. 构建脚本读取 tokens.json,自动生成:
//    - resources/base/element/color.json
//    - resources/base/element/float.json
//    - src/main/ets/theme/ColorToken.ets
//    - src/main/ets/theme/Spacing.ets

// 生成的 ColorToken.ets 示例
export class ColorToken {
  static readonly brandPrimary = $r('app.color.brand_primary');
  static readonly surface = $r('app.color.surface');
  static readonly onSurface = $r('app.color.on_surface');
}

八、性能优化与常见问题

8.1 包体积优化策略

策略 说明 预期收益
矢量图替代位图 SVG 一套适配所有密度 减少 60%+ 图片体积
WebP 格式 比 PNG 减少 25-35% 体积 显著降低媒体资源体积
限定词最小化 仅在差异目录放置差异资源 避免冗余文件
资源压缩 启用 HAP 构建时的资源压缩 整体减少 10-20%
按需加载 非首屏资源延迟加载或分包 减少首包体积

8.2 启动性能优化

资源加载是应用启动的关键路径。优化策略包括:

  1. 预加载关键资源:在 SplashAbility 中提前加载首页所需的颜色和字符串资源;
  2. 延迟加载非关键资源:使用 LazyForEach 和动态导入延迟加载列表图片;
  3. 资源缓存ResourceManager 内部已实现资源缓存,避免重复解析 JSON。

8.3 常见陷阱与规避

陷阱 现象 解决方案
资源名拼写错误 编译报错或运行时白图 使用 $r() 的编译期校验,避免字符串硬编码
限定词目录遗漏 切换语言后显示英文 确保 base 目录包含完整的兜底资源
rawfile 路径错误 运行时文件找不到 使用 resourceManager.getRawFileList() 枚举确认
多模块资源冲突 同名资源被覆盖 建立命名空间规范,如 module_name_resource_name
深色模式未覆盖 深色模式下颜色突兀 建立 dark 目录并做全路径 UI 走查
复数规则缺失 俄语/阿拉伯语复数显示错误 使用 plural.json 并覆盖所有 ICU 复数类别

九、总结

ArkTS 的资源管理体系通过「集中目录 + 限定词分发 + 编译期解析」三位一体的设计,实现了资源的高效管理与多端适配。从 $r() 的零开销编译期引用,到 $rawfile() 的灵活运行时加载,再到 ResourceManager 的精细化 API,开发者可以根据场景选择最合适的资源操作方式。

在大型项目中,资源治理的核心是分层架构自动化工具链:通过业务/共享/基础/平台四层隔离,避免资源耦合;通过 Token 代码生成和构建期去重,降低维护成本。最终目标是让资源管理从「人肉运维」进化为「工程化流水线」。


转载自:https://blog.csdn.net/u014727709/article/details/163239339
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

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

更多推荐