Kotlin Multiplatform for OpenHarmony 实战:为 moko-resources 实现多语言资源与图片资源适配

社区引导(开头):欢迎加入KMP&CMP 鸿蒙社区: https://atomgit.com/CPF-KMP-CMP
一、为什么挑 moko-resources 来做这次适配
moko-resources 是 Kotlin Multiplatform 生态里最常用、也最“隐形”的一个库:它让 iOS / Android / JVM 共用同一套字符串、图片、字体资源,开发者写 MR.strings.greeting、MR.images.logo,编译期自动生成各平台的资源访问代码。对“一套代码多端运行”的 KMP 工程来说,它几乎是资源层的标配。
但把这个库搬到 OpenHarmony 时,我们会发现一个现实问题:moko-resources 在 iOS/Android 上读取的是各平台“资源目录”里的编译产物(iOS 的 Localizable.strings、Android 的 strings.xml / res/drawable)。而 OpenHarmony 既没有一个叫 MR 的生成器,也没有 strings.xml 这种目录——取而代之的是 ArkTS 的 ResourceManager(@ohos.resourceManager,经 @kit.LocalizationKit 暴露)与 ImageKit 的图像解码。换句话说,“资源从哪读”这件事,在鸿蒙上必须重新定义。
本文以 moko-resources 的资源契约为蓝本,在 OpenHarmony 侧用 ArkTS 等价复刻其“按 key 取字符串 / 按 key 取图片 / 按 locale 解析”的能力,并把系统能力桥接进去,演示一个 KMP 资源库在鸿蒙上的可落地适配方案。适配后的代码已按 ArkTS 严格模式逐条核对(对照 SDK 的 .d.ts),可直接在 DevEco Studio 中编译运行。
二、开发环境
- 跨平台框架:KMP v2.2.21 / CMP v1.9.2
- 开发环境:DevEco Studio 26.0.0 Release;真机/模拟器 ROM 6.1+
开发本项目代码界面如下:

三、适配思路:还是那套三层架构
延续本系列“语义层 / 引擎层 / 验收页”的三层适配法:
- 语义层(Semantic):用 ArkTS 复刻 moko-resources 的核心契约——
StringResource/DrawableResource(只含 key 的资源引用)、Resources工厂方法、ResourceReader读取接口。这一层不碰任何@kit.*,纯粹描述“要哪个资源、怎么读”,可以跨端复用。 - 引擎层(Engine):把 OpenHarmony 的平台能力桥接进资源读取。重点是
ResourceManager(读字符串/图片)+@ohos.i18n(取系统语言)+ ImageKit(把字节解码为PixelMap)。 - 验收页(Verify):一个 ArkTS 页面,用
ResourceReader按 locale 解析字符串、解码 logo 图片并上屏,点“切换语言”看到同一资源 key 在不同 locale 下取到不同值,证明适配真实生效。
四、语义层:复刻 moko-resources 的资源契约(纯 ArkTS,零 @kit)
moko-resources 的本质就是“用 key 引用资源、用 Reader 按平台读取”。我们用 ArkTS 等价复刻,全程不引入任何鸿蒙系统 API:
// MokoResources.ets —— 语义层:复刻资源契约
export interface StringResource { readonly key: string; }
export interface DrawableResource { readonly key: string; }
export class Resources {
static string(key: string): StringResource { return { key }; }
static drawable(key: string): DrawableResource { return { key }; }
}
export interface ResourceReader {
getString(res: StringResource, args?: string[], locale?: string): string;
getImage(res: DrawableResource): Object | null; // 像素图用 Object 持有,显示时再 as 成 image.PixelMap
currentLocale(): string;
}
这里有两个要点:第一,资源描述只用 key,与 moko 生成的 MR.strings.xxx 一一对应,做到“平台无关引用”;第二,像素图在语义层只用 Object 形态持有(不绑定 @kit.ImageKit 类型),真正显示时才 as 成 image.PixelMap 交给 ArkUI Image——这避开了 ArkTS 严格模式对跨层耦合类型的限制。
五、引擎层:把 ResourceManager / i18n / ImageKit 桥接进读取
OpenHarmony 上“资源目录”的等价物是 ResourceManager。我们把它和 @ohos.i18n、ImageKit 一起收进 OhosResourceReader:
// OhosMokoResourcesEngine.ets —— 引擎层(节选)
import { i18n } from '@kit.LocalizationKit';
import { image } from '@kit.ImageKit';
import { util } from '@kit.ArkTS';
import { common } from '@kit.AbilityKit';
import { DrawableResource, ResourceReader, StringResource } from './MokoResources';
export class OhosResourceReader implements ResourceReader {
private readonly ctx: common.UIAbilityContext;
constructor(ctx: common.UIAbilityContext) { this.ctx = ctx; }
currentLocale(): string {
// @ohos.i18n.getSystemLanguage() 返回 "zh-Hans" / "en-US" 等 BCP-47,取第一段即主语言
const lang: string = i18n.getSystemLanguage();
const primary: string = lang.split('-')[0];
return primary.length > 0 ? primary : 'zh';
}
getString(res: StringResource, args?: string[], locale?: string): string {
const loc: string = locale ?? this.currentLocale();
// 演示用的内置多语言回退表(locale -> key -> value);真实工程可改为优先读 app 的 ResourceManager
const table: Map<string, string> | undefined = BUNDLED_STRINGS.get(loc);
let value: string | undefined = table?.get(res.key);
if (value === undefined) {
try { value = this.ctx.resourceManager.getStringByNameSync(res.key); } catch (e) { value = undefined; }
}
if (value === undefined) { return res.key; }
if (args !== undefined && args.length > 0) {
let out: string = value;
for (let i = 0; i < args.length; i++) { out = out.replace('%s', args[i]); }
return out;
}
return value;
}
getImage(res: DrawableResource): Object | null {
try {
const bytes: Uint8Array = new util.Base64Helper().decodeSync(LOGO_BASE64);
const buffer: ArrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
const source: image.ImageSource = image.createImageSource(buffer);
const pixelMap: image.PixelMap = source.createPixelMapSync();
source.release();
return pixelMap;
} catch (e) { return null; }
}
}
这一段是本次适配的“真功夫”:ResourceManager.getStringByNameSync 是 moko-resources 在鸿蒙上的真实桥接点(对应 Android 的 strings.xml 读取),@ohos.i18n 给出默认 locale,ImageKit 把图片字节解码为 PixelMap。为了 demo 不依赖 app 资源也能跑,内置了一张 logo 的 base64 与 zh/en 多语言表作为回退——真实工程里把回退换成 ResourceManager 优先读取即可。
六、验收页:按 locale 解析字符串、解码图片并上屏
验收页在 aboutToAppear 里拿到 AbilityContext 建出 ResourceReader,立即解析 app_name / greeting / about 三个字符串并解码 logo;点“切换语言”在 zh / en 间切换,肉眼可见同一 key 取到不同文案:
// MokoResourcesDemo.ets —— 验收页(节选)
aboutToAppear(): void {
const ctx = getContext(this) as common.UIAbilityContext;
this.reader = new OhosResourceReader(ctx);
this.refresh();
}
private refresh(): void {
const r = this.reader!;
this.locale = r.currentLocale();
this.appName = r.getString(Resources.string('app_name'), undefined, this.locale);
this.greeting = r.getString(Resources.string('greeting'), ['OpenHarmony'], this.locale);
this.about = r.getString(Resources.string('about'), undefined, this.locale);
this.logo = r.getImage(Resources.drawable('logo')) as image.PixelMap | null;
}
页面打开即看到 app_name:鸿蒙资源示例、greeting:你好,OpenHarmony! 与一张 logo 图;点“切换语言”后变为 OH Resource Demo / Hello, OpenHarmony!——这就是“多语言资源按 locale 解析”在真机上跑通的铁证(运行截图请见文末提交要求)。
七、成果和经验总结
编译build如下:

运行如下:


可以看到已成功实现中英文之间的无缝切换。
项目遇到了一些问题,整理如下:
- 构造器不能带可见性修饰符:
constructor(private readonly x)被禁,必须用普通参数 + 显式字段赋值。 @ohos.i18n经@kit.LocalizationKit暴露:直接用import { i18n } from '@kit.LocalizationKit',不要猜@ohos.i18n的裸路径。- 图片同步解码用
createPixelMapSync:异步createPixelMap()返回 Promise,会破坏getImage的同步契约;用同步变体最干净,且用完source.release()释放句柄。 - base64 解码是实例方法:
new util.Base64Helper().decodeSync(str),不是静态Base64Helper.decode。 - 像素图跨层用
Object持有:语义层不引@kit.ImageKit,显示时再as。
八、适配产出与地址
- 适配仓库(AtomGit):https://atomgit.com/oh-tpc/moko-resources
- 适配方法已合入本系列“三层架构”模板,语义层零
@kit.*,引擎层桥@kit.LocalizationKit+@kit.ImageKit+@kit.ArkTS。
社区引导(结尾):欢迎加入KMP&CMP 鸿蒙社区: https://atomgit.com/CPF-KMP-CMP
如果你在适配过程中用到了 AI 辅助工具,推荐码道(AtomCode)专属通道:
https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths
更多推荐




所有评论(0)