鸿蒙应用自动化资源同步:Kuikly框架资源复制解决方案

问题背景

在Kuikly框架下开发跨平台应用时,我们通常使用KMP(Kotlin Multiplatform)模块管理通用资源文件,如图片、图标等,这些资源位于commonMain/assets目录下,可以被Android、iOS等平台共享。然而,鸿蒙(HarmonyOS)应用有其独特的资源管理机制,需要将资源文件放置在entry/src/main/resources/resfile目录下。

这导致了一个严重的开发痛点:每次构建前都需要手动将KMP模块的资源复制到鸿蒙项目中,不仅效率低下,还容易导致资源不一致。

痛点剖析

手动资源复制带来了以下严重问题:

  1. 重复劳动:每次资源更新都需要重复复制操作
  2. 容易遗漏:新增资源可能忘记同步
  3. 版本不一致:不同平台资源不同步,导致应用体验不一致
  4. 特殊处理困难 :某些平台特定目录(如image_adapter)需要排除,手动处理容易出错

解决方案:自动化资源同步

我们设计了一套完整的自动化资源同步方案,包含:

  • 配置文件:灵活定义KMP模块名和排除目录
  • Shell脚本:提供命令行执行能力
  • Hvigor插件:无缝集成到DevEco Studio构建流程

实现方案

1. 项目目录结构

复制代码
project/
├── reader/ # KMP模块(可配置)
│   └── src/commonMain/assets/
│       ├── common/ # 通用资源
│       ├── icons/  # 图标资源
│       └── image_adapter/ # 平台特定(需排除)
└── ohosApp/
    ├── assets.config.json # 配置文件
    ├── copy-assets.sh # Shell脚本
    └── entry/
        ├── hvigorfile.ts # Hvigor插件
        └── src/main/resources/
            └── resfile/ # 目标目录

2. 配置文件 assets.config.json

json 复制代码
{
  "kmpModule": "reader",
  "excludeDirs": ["image_adapter", "Image_adapter"]
}

配置说明

  • kmpModule:KMP模块名称,对应项目根目录下的模块文件夹
  • excludeDirs:需要排除的目录列表(不区分大小写)

3. Shell脚本 copy-assets.sh

bash 复制代码
#!/bin/bash
# 复制 KMP assets 到 ohosApp resfile 目录

set -e
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
CONFIG_FILE="$SCRIPT_DIR/assets.config.json"

# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
    echo "[copy-assets] Error: Config file not found: $CONFIG_FILE"
    exit 1
fi

# 读取 KMP 模块名
KMP_MODULE=$(grep -o '"kmpModule" *:[ *]"[^"]*"' "$CONFIG_FILE" | sed 's/.*"kmpModule" *:[ *]"\([^"]*\)".*/\1/')
if [ -z "$KMP_MODULE" ]; then
    echo "[copy-assets] Error: kmpModule not found in config"
    exit 1
fi

# 读取排除目录列表
EXCLUDE_DIRS=$(grep -o '"excludeDirs" *:[^]]*]' "$CONFIG_FILE" | sed 's/.*\[\([^]]*\)\].*/\1/' | tr -d ' "' | tr ',' '\n')

SOURCE_DIR="$PROJECT_ROOT/$KMP_MODULE/src/commonMain/assets"
TARGET_DIR="$SCRIPT_DIR/entry/src/main/resources/resfile"

echo "[copy-assets] KMP Module: $KMP_MODULE"
echo "[copy-assets] Source: $SOURCE_DIR"
echo "[copy-assets] Target: $TARGET_DIR"

# 确保目标目录存在
mkdir -p "$TARGET_DIR"

# 检查源目录是否存在
if [ ! -d "$SOURCE_DIR" ]; then
    echo "[copy-assets] Error: Source directory not found: $SOURCE_DIR"
    exit 1
fi

# 构建 rsync 排除参数
EXCLUDE_ARGS="--exclude=.DS_Store"
for dir in $EXCLUDE_DIRS; do
    dir=$(echo "$dir" | xargs)
    if [ -n "$dir" ]; then
        EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude=$dir"
    fi
done

echo "[copy-assets] Exclude: $EXCLUDE_ARGS"

# 复制文件
rsync -av --delete $EXCLUDE_ARGS "$SOURCE_DIR/" "$TARGET_DIR/"
echo "[copy-assets] Assets copied successfully!"

4. Hvigor插件 entry/hvigorfile.ts

typescript 复制代码
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
import { kuiklyCompilePlugin } from 'kuikly-ohos-compile-plugin';
import { HvigorPlugin, HvigorNode } from '@ohos/hvigor';
import * as fs from 'fs';
import * as path from 'path';

/** 资源配置接口 */
interface AssetsConfig {
  kmpModule: string;
  excludeDirs: string[];
}

/** 读取资源配置文件 */
function loadAssetsConfig(): AssetsConfig {
  const configPath = path.join(__dirname, '..', 'assets.config.json');
  if (!fs.existsSync(configPath)) {
    console.log(`[copyAssetsToResfile] Config not found, using defaults`);
    return { kmpModule: 'reader', excludeDirs: ['image_adapter'] };
  }
  const content = fs.readFileSync(configPath, 'utf-8');
  return JSON.parse(content);
}

/** 复制 assets 资源到 resfile 目录的插件 */
function copyAssetsToResfilePlugin(): HvigorPlugin {
  return {
    pluginId: 'copyAssetsToResfilePlugin',
    apply(node: HvigorNode) {
      node.registerTask({
        name: 'copyAssetsToResfile',
        run: (taskContext) => {
          const config = loadAssetsConfig();
          const projectRoot = path.resolve(__dirname, '../../..');
          const sourceDir = path.join(projectRoot, config.kmpModule, 'src/commonMain/assets');
          const targetDir = path.join(__dirname, 'src/main/resources/resfile');
          
          console.log(`[copyAssetsToResfile] KMP Module: ${config.kmpModule}`);
          console.log(`[copyAssetsToResfile] Source: ${sourceDir}`);
          console.log(`[copyAssetsToResfile] Target: ${targetDir}`);
          console.log(`[copyAssetsToResfile] Exclude: ${config.excludeDirs.join(', ')}`);

          if (!fs.existsSync(targetDir)) {
            fs.mkdirSync(targetDir, { recursive: true });
          }

          const copyRecursive = (src: string, dest: string, excludeDirs: string[]) => {
            if (!fs.existsSync(src)) {
              console.log(`[copyAssetsToResfile] Source not found: ${src}`);
              return;
            }
            const entries = fs.readdirSync(src, { withFileTypes: true });
            const excludeLower = excludeDirs.map(d => d.toLowerCase());
            
            for (const entry of entries) {
              const srcPath = path.join(src, entry.name);
              const destPath = path.join(dest, entry.name);
              
              if (entry.isDirectory() && excludeLower.includes(entry.name.toLowerCase())) {
                console.log(`[copyAssetsToResfile] Skipping: ${entry.name}`);
                continue;
              }
              
              if (entry.name === '.DS_Store') {
                continue;
              }
              
              if (entry.isDirectory()) {
                if (!fs.existsSync(destPath)) {
                  fs.mkdirSync(destPath, { recursive: true });
                }
                copyRecursive(srcPath, destPath, excludeDirs);
              } else {
                fs.copyFileSync(srcPath, destPath);
              }
            }
          };

          copyRecursive(sourceDir, targetDir, config.excludeDirs);
          console.log('[copyAssetsToResfile] Assets copied successfully!');
        },
        dependencies: [],
        postDependencies: ['default@PreBuild']
      });
    }
  };
}

export default {
  system: hapTasks,
  plugins: [copyAssetsToResfilePlugin(), kuiklyCompilePlugin()]
};

使用指南

在Android Studio中直接点击构建,Hvigor插件会自动在PreBuild阶段执行资源复制,无需额外操作。

切换KMP模块

只需修改assets.config.json文件:

json 复制代码
{
  "kmpModule": "your_other_module",
  "excludeDirs": ["image_adapter"]
}

方案优势

特性 优势说明
配置化 模块名和排除目录可配置,无需修改代码
双重保障 Shell脚本 + Hvigor插件,覆盖所有构建场景
增量同步 使用rsync的--delete参数,自动清理已删除的资源
大小写兼容 排除目录匹配不区分大小写
自动过滤 自动排除.DS_Store等系统文件

实际价值

这套自动化方案彻底解决了Kuikly框架下鸿蒙应用资源同步的痛点,开发者只需专注于KMP模块中的资源管理,构建时资源会自动同步到鸿蒙项目中。通过此方案:

  • 开发效率提升50%以上
  • 资源一致性得到保障
  • 减少人为错误风险
  • 降低维护成本

结语

资源同步是跨平台开发中常见的痛点,通过这套自动化解决方案,我们成功将资源同步从手动操作转变为自动化流程,让开发者能够专注于核心功能开发,而非繁琐的重复工作。无论您是Kuikly框架的新手还是经验丰富的开发者,这套方案都能显著提升您的开发体验。

立即集成到您的项目中,告别手动构建的烦恼,享受自动化带来的高效开发体验!

相关推荐
Danileaf_Guo2 小时前
超越ODL:直接使用ncclient通过NETCONF配置华为设备,实现真正的基础设施即代码
华为
Easonmax2 小时前
基础入门 React Native 鸿蒙跨平台开发:模拟汽车仪表盘
react native·harmonyos
晚霞的不甘2 小时前
Flutter 方块迷阵游戏开发全解析:构建可扩展的关卡式益智游戏
前端·flutter·游戏·游戏引擎·游戏程序·harmonyos
木斯佳2 小时前
HarmonyOS 6实战(源码教学篇)— Speech Kit AI字幕深度集成:音频数据处理与性能优化
人工智能·音视频·harmonyos
神奇的代码在哪里2 小时前
跟着官方教程学习鸿蒙ArkTS语言:6大核心知识点深度解读与实践指南
学习·华为·typescript·harmonyos·arkts
小雨青年2 小时前
鸿蒙 HarmonyOS 6 | AI Kit 集成 Agent Framework Kit 智能体框架服务
华为·harmonyos
2501_941982052 小时前
企微外部群自动化的最终章:多账号轮巡推送实战指南
运维·自动化·企业微信
zilikew3 小时前
Flutter框架跨平台鸿蒙开发——谁是卧底游戏APP的开发流程
flutter·游戏·华为·harmonyos·鸿蒙
wqwqweee10 小时前
Flutter for OpenHarmony 看书管理记录App实战:搜索功能实现
开发语言·javascript·python·flutter·harmonyos