本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

一、背景

模块化开发的资源管理
  • 资源重复:多个模块定义相同资源,造成冗余

  • 维护困难:资源分散在不同模块,更新维护成本高

  • 一致性差:相同功能在不同模块中使用不同资源

跨模块资源访问方案
  • 集中管理:将共享资源集中存放在特定模块

  • 统一访问:提供标准化方式访问跨模块资源

  • 减少冗余:避免重复定义,提高开发效率

  • 便于维护:资源变更只需修改一处

二、跨模块访问 HAR 资源

方法一:通过 $r 或 $rawfile 语法访问

适用场景
  • 简单的、静态的资源引用

  • 在 UI 组件中直接引用资源

  • 资源使用场景简单,无需复杂处理

语法格式
// 访问字符串资源
Text($r('app.string.module_shared_string'))

// 访问图片资源  
Image($r('app.media.module_shared_image'))

// 访问颜色资源
.backgroundColor($r('app.color.primary_color'))

// 访问原始文件
Text($rawfile('config_file.json'))
应用示例
@Entry
@Component
struct ProductDetailPage {
  build() {
    Column() {
      // 使用HAR中的字符串资源
      Text($r('app.string.product_title'))
        .fontSize($r('app.float.title_font_size'))
        .fontColor($r('app.color.text_primary'))
      
      // 使用HAR中的图片资源
      Image($r('app.media.product_default_image'))
        .width(200)
        .height(200)
    }
  }
}

方法二:通过 resourceManager 访问资源

适用场景
  • 需要程序化处理资源数据

  • 图像效果处理、字符串动态拼接

  • 需要获取资源原始数据的复杂业务逻辑

核心优势
  • 性能更优:使用资源ID作为参数,避免字符串解析

  • 功能丰富:提供更多资源处理接口

  • 灵活性高:支持同步/异步获取资源

实现方式
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct ResourceProcessor {
  // 通过上下文获取ResourceManager
  private resourceMgr: resourceManager.ResourceManager = getContext().resourceManager;
  
  aboutToAppear() {
    // 同步获取字符串资源(推荐方式)
    const welcomeText = this.resourceMgr.getStringSync(
      $r('app.string.welcome_message').id
    );
    
    // 同步获取图片资源
    const logoImage = this.resourceMgr.getMediaContentSync(
      $r('app.media.app_logo').id
    );
    
    // 异步获取颜色资源
    this.resourceMgr.getColor($r('app.color.theme_color').id)
      .then(colorValue => {
        console.info(`Theme color value: ${colorValue}`);
      });
      
    // 获取原始文件内容
    const configData = this.resourceMgr.getRawFileContentSync('app_config.json');
  }
  
  build() {
    Column() {
      // 使用获取到的资源
      Text(this.processedText)
      Image(this.processedImage)
    }
  }
}
优化建议
  • 优先使用ID方式getStringSync(resId) 比 getStringByNameSync(name) 性能更好

  • 避免频繁调用:在 aboutToAppear 中预先获取资源,避免在build中频繁调用

  • 使用同步接口:在非主线程场景下使用同步接口避免阻塞

三、跨模块访问 HSP 资源

方法一:通过 $r 或 $rawfile 语法访问(指定HSP模块)

语法格式
// 访问HSP模块中的字符串资源
Text($r('[hsp_module_name].string.resource_name'))

// 访问HSP模块中的媒体资源  
Image($r('[hsp_module_name].media.image_name'))

// 访问HSP模块中的原始文件
Text($rawfile('[hsp_module_name].file_name'))
应用示例
@Entry
@Component
struct CrossModuleUI {
  build() {
    Column() {
      // 访问基础HSP模块的资源
      Text($r('[base_hsp].string.common_cancel'))
        .fontSize(16)
      
      // 访问UI组件HSP模块的资源
      Image($r('[ui_components].media.ic_arrow_right'))
        .width(24)
        .height(24)
        
      // 访问主题HSP模块的颜色资源
      .backgroundColor($r('[theme_hsp].color.background_primary'))
    }
    .padding($r('[layout_hsp].float.default_padding'))
  }
}

方法二:通过 createModuleContext 访问资源

适用场景
  • 需要对HSP资源进行数据处理

  • 少量资源的程序化访问

  • 不希望添加显式依赖的场景

实现方式
import { application } from '@kit.AbilityKit';

@Entry
@Component
struct HSPResourceAccessor {
  private hspResources: Map<string, any> = new Map();
  
  aboutToAppear() {
    // 创建HSP模块上下文
    application.createModuleContext(getContext(), 'shared_ui_hsp')
      .then(moduleContext => {
        const resManager = moduleContext.resourceManager;
        
        // 通过名称获取字符串资源
        const buttonText = resManager.getStringByNameSync('btn_confirm_text');
        this.hspResources.set('confirmText', buttonText);
        
        // 通过名称获取图片资源
        const iconImage = resManager.getMediaByNameSync('ic_success');
        this.hspResources.set('successIcon', iconImage);
        
        // 获取维度资源
        const spacing = resManager.getFloatByNameSync('spacing_medium');
        this.hspResources.set('mediumSpacing', spacing);
      })
      .catch(error => {
        console.error(`Failed to access HSP resources: ${error.message}`);
      });
  }
  
  build() {
    Column() {
      Text(this.hspResources.get('confirmText') || 'Default Text')
      Image(this.hspResources.get('successIcon'))
    }
    .padding(this.hspResources.get('mediumSpacing') || 0)
  }
}
方法优缺点

优点

  • 无需在模块间添加依赖关系

  • 使用相对简单直接

缺点

  • 硬编码问题:需要手动编写资源名称字符串

  • 维护困难:HSP资源名称变更时,使用方需要同步修改

  • 类型安全:缺乏编译时检查,容易出错

  • 不适合大量资源:资源较多时代码冗长

方法三:HSP导出资源管理类(推荐方案)

适用场景
  • 大量资源的跨模块访问

  • 需要对资源进行封装和抽象

  • 希望提供类型安全的资源访问接口

实现步骤

步骤1:在HSP中创建资源管理类

// shared_resources_hsp/src/main/ets/utils/ResourceExporter.ets

export class SharedResourceManager {
  // 字符串资源
  static getCommonStrings() {
    return {
      confirm: $r('app.string.btn_confirm'),
      cancel: $r('app.string.btn_cancel'),
      loading: $r('app.string.status_loading'),
      error: $r('app.string.status_error')
    };
  }
  
  // 图片资源
  static getIcons() {
    return {
      arrowRight: $r('app.media.ic_arrow_right'),
      arrowLeft: $r('app.media.ic_arrow_left'),
      close: $r('app.media.ic_close'),
      menu: $r('app.media.ic_menu')
    };
  }
  
  // 颜色资源
  static getThemeColors() {
    return {
      primary: $r('app.color.theme_primary'),
      secondary: $r('app.color.theme_secondary'),
      background: $r('app.color.background_primary'),
      text: $r('app.color.text_primary')
    };
  }
  
  // 维度资源
  static getLayoutDimensions() {
    return {
      spacingSmall: $r('app.float.spacing_small'),
      spacingMedium: $r('app.float.spacing_medium'),
      spacingLarge: $r('app.float.spacing_large'),
      cornerRadius: $r('app.float.corner_radius_default')
    };
  }
  
  // 获取特定主题资源
  static getDarkThemeResources() {
    return {
      background: $r('app.color.dark_background'),
      text: $r('app.color.dark_text_primary'),
      card: $r('app.color.dark_surface')
    };
  }
}

步骤2:在HSP入口文件中导出

// shared_resources_hsp/index.ets

export { SharedResourceManager } from './src/main/ets/utils/ResourceExporter';
export { ThemeManager } from './src/main/ets/utils/ThemeManager';
export { ImageProcessor } from './src/main/ets/utils/ImageProcessor';

步骤3:使用方导入并使用

// 使用方模块
import { SharedResourceManager } from 'shared_resources_hsp';

@Entry
@Component
struct ProductScreen {
  private resources = SharedResourceManager.getCommonStrings();
  private icons = SharedResourceManager.getIcons();
  private colors = SharedResourceManager.getThemeColors();
  private dimensions = SharedResourceManager.getLayoutDimensions();
  
  build() {
    Column() {
      // 使用HSP导出的字符串资源
      Text(this.resources.loading)
        .fontSize(18)
        .fontColor(this.colors.text)
      
      // 使用HSP导出的图标资源
      Image(this.icons.close)
        .width(24)
        .height(24)
        .onClick(() => {
          // 关闭操作
        })
      
      Button(this.resources.confirm)
        .backgroundColor(this.colors.primary)
        .borderRadius(this.dimensions.cornerRadius)
    }
    .padding(this.dimensions.spacingMedium)
    .backgroundColor(this.colors.background)
  }
}
方法优势
  • 封装性好:HSP内部资源变化不影响使用方

  • 类型安全:提供完整的TypeScript类型支持

  • 易于维护:资源变更只需修改HSP内部实现

  • 使用简便:使用方无需了解资源具体名称

  • 功能丰富:支持资源组合和业务逻辑封装

四、资源冲突解决

资源覆盖优先级

当不同模块存在同名资源时,按照以下优先级覆盖(从高到低):

  1. AppScope 中的资源 - 最高优先级

  2. HAP 包自身模块资源

  3. 依赖的 HAR/HSP 模块资源

    • 按照 oh-package.json5 中 dependencies 的顺序

    • 依赖顺序在前的模块优先级较高

示例

// oh-package.json5
{
  "dependencies": {
    "shared_ui_components": "1.0.0",    // 优先级较高
    "third_party_library": "2.1.0",     // 优先级较低
    "theme_package": "1.2.0"            // 优先级最低
  }
}

建议

  • 命名空间化:在资源名称前添加模块前缀,如 module_prefix_resource_name

  • 明确依赖:在文档中明确说明资源依赖关系

  • 版本管理:使用语义化版本管理共享资源模块

五、方案选择

场景 推荐方案 理由
简单UI资源引用 方法一:$r 语法 简单直接,代码清晰
HAR资源访问 方法一或方法二 访问方式与本地资源一致
少量HSP资源 方法二:createModuleContext 灵活,无需添加依赖
大量HSP资源 方法三:资源管理类 类型安全,易于维护
团队协作开发 方法三:资源管理类 接口稳定,降低耦合
资源需要处理 方法二或方法三 支持程序化资源处理

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐