鸿蒙Flutter 三方库 country_codes 的 适配实战
获取设备当前语言和国家(如zhCN获取本地化的国家名称(如在英文环境下显示 “China”,中文环境下显示 “中国”)ISO 3166-1 双字母/三字母代码查询国际电话区号查询该插件最初支持 Android、iOS、macOS 三个平台,本次任务将其适配到平台。项目地址将一个 Flutter 插件适配到 OHOS 平台,核心路径可以概括为三步走1. 找对应 ── 找到 OHOS 对每个 Andr
鸿蒙Flutter 三方库 country_codes 的 适配实战
本文记录了将开源 Flutter 插件
country_codes适配到 OpenHarmony / HarmonyOS NEXT 平台的完整过程,包含适配思路、代码改动对照、关键决策和踩坑复盘。
一、背景
1.1 插件简介
country_codes 是一个 Flutter 社区广泛使用的国家代码辅助插件,提供以下能力:
- 获取设备当前语言和国家(如
zh/CN) - 获取本地化的国家名称(如在英文环境下显示 “China”,中文环境下显示 “中国”)
- ISO 3166-1 双字母/三字母代码查询
- 国际电话区号查询
该插件最初支持 Android、iOS、macOS 三个平台,本次任务将其适配到 OpenHarmony / HarmonyOS NEXT 平台。
1.2 适配目标
| 维度 | 要求 |
|---|---|
| 功能一致性 | 三个原生方法(getLocale / getRegion / getLanguage)返回的数据结构与 Android 完全一致 |
| Dart 层零改动 | 现有 Dart API 无需修改即可在 OHOS 上工作 |
| 性能 | 本地化国家名称查询需完整覆盖所有 ISO 3166-1 国家 |
| 工程规范 | 遵循 Flutter OHOS 插件标准目录结构 |
二、适配路线图
整个适配分为 4 个阶段:
第 1 阶段:项目初始化 ── 创建 ohos/ 目录,配置标准模板
第 2 阶段:原生实现 ── 编写 CountryCodesPlugin.ets,实现三个原生方法
第 3 阶段:插件注册 ── 在 pubspec.yaml 中注册 ohos 平台
第 4 阶段:示例验证 ── 创建 example/ohos/ 验证端到端流程
三、逐步适配过程
第 1 阶段:项目初始化
使用 Flutter 命令行生成 OHOS 模板
flutter create . --template=plugin --platforms=ohos
该命令会自动生成 ohos/ 目录的标准模板结构,包含必要的构建配置和入口文件。开发者只需关注核心插件逻辑的实现。
创建的目录结构
ohos/
├── index.ets # 模块入口,导出插件类
├── oh-package.json5 # 包配置
├── build-profile.json5 # 构建配置
├── src/main/
│ ├── module.json5 # HAR 模块配置
│ └── ets/components/plugin/
│ └── CountryCodesPlugin.ets # 原生插件实现(核心)
关键文件说明:
oh-package.json5— 声明包名、入口文件、许可证和依赖module.json5— 声明模块类型为har(静态共享库),支持设备类型build-profile.json5— 配置构建目标index.ets— 模块导出入口
index.ets(入口导出文件)
import CountryCodesPlugin from './src/main/ets/components/plugin/CountryCodesPlugin';
export default CountryCodesPlugin;
oh-package.json5
{
"name": "country_codes",
"version": "1.0.0",
"main": "index.ets",
"license": "Apache-2.0",
"dependencies": {}
}
注:
@ohos/flutter_ohos由 Flutter 引擎在构建时自动链接,无需在dependencies中显式声明。
module.json5
{
"module": {
"name": "country_codes",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
第 2 阶段:原生实现(核心)
这是适配的核心工作。我们将 Android 平台的 Kotlin 实现逐一翻译为 ArkTS。
2.1 整体架构对比
Android (Kotlin) OHOS (ArkTS)
──────────────────── ────────────────────
class CountryCodesPlugin class CountryCodesPlugin
implements FlutterPlugin, implements FlutterPlugin,
MethodCallHandler MethodCallHandler
import { FlutterPlugin,
import io.flutter.embedding.engine. FlutterPluginBinding,
plugins.FlutterPlugin MethodCall,
import io.flutter.plugin.common. MethodCallHandler,
MethodChannel MethodChannel,
import java.util.Locale MethodResult
} from '@ohos/flutter_ohos'
import i18n from '@ohos.i18n'
2.2 方法通道注册
| 平台 | 代码 |
|---|---|
| Android | MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "country_codes") |
| OHOS | new MethodChannel(binding.getBinaryMessenger(), "country_codes") |
差异:OHOS 使用
getBinaryMessenger()替代 Android 的getFlutterEngine().getDartExecutor(),接口更简洁。
2.3 三个原生方法实现对照
方法 getLanguage — 获取系统语言
| 平台 | 实现 | 返回值 |
|---|---|---|
| Android | Locale.getDefault().language |
"zh", "en", etc. |
| OHOS | i18n.System.getSystemLanguage() |
"zh", "en", etc. |
方法 getRegion — 获取系统地区
| 平台 | 实现 | 返回值 |
|---|---|---|
| Android | Locale.getDefault().country |
"CN", "US", etc. |
| OHOS | i18n.System.getSystemRegion() |
"CN", "US", etc. |
方法 getLocale — 获取完整语言环境 + 本地化名称
这是最复杂的方法,需要返回 [语言, 地区, 本地化国家名称映射] 三元组。
Android 实现(Kotlin):
result.success(
listOf(
Locale.getDefault().language,
Locale.getDefault().country,
getLocalizedCountryNames(call.arguments as String?)
)
)
OHOS 实现(ArkTS):
const language = i18n.System.getSystemLanguage();
const region = i18n.System.getSystemRegion();
const localeTag = call.args as string;
const localizedCountries = this.getLocalizedCountryNames(localeTag);
result.success([language, region, localizedCountries]);
关键差异点:Android 使用
call.arguments(带s),而 ArkTS 使用call.args(不带s)。
2.4 本地化国家名称的实现差异
这是适配中最值得展开的部分。
Android 的做法:
// 1. 利用 Java 标准库获取所有 ISO 国家代码
for (countryCode in Locale.getISOCountries()) {
// 2. 为目标国家代码创建 Locale 对象
val locale = Locale(localeTag ?: deviceCountry, countryCode)
// 3. 获取本地化显示名称
var countryName = locale.getDisplayCountry(
Locale.forLanguageTag(localeTag ?: deviceCountry)
)
localizedCountries[countryCode.toUpperCase()] = countryName ?: ""
}
OHOS 的做法:
// 1. OHOS 没有类似 Locale.getISOCountries() 的系统 API
// → 解决方案:硬编码完整 ISO 3166-1 国家代码列表(249 个)
const countryCodes = this.getISOCountryCodes();
for (const countryCode of countryCodes) {
// 2. 使用 @ohos.i18n 的 getDisplayCountry API
const countryName = i18n.getDisplayCountry(
countryCode, targetLocale, false
);
localizedCountries[countryCode.toUpperCase()] = countryName ?? "";
}
2.5 硬编码国家代码列表
OHOS 的 @ohos.i18n 提供了语言、地区获取和本地化名称显示的能力,但 没有提供获取所有 ISO 国家代码列表的系统 API(类似于 Java 的 Locale.getISOCountries())。
决策:硬编码 vs 动态获取
| 方案 | 优点 | 缺点 |
|---|---|---|
| 硬编码 249 个 ISO 代码 ✅ | 实现简单,与 Android 数据一致,无运行时开销 | 需维护列表,新增国家需更新 |
| 动态从 i18n API 获取 | 未来扩展性好 | OHOS 不提供该 API |
最终选择硬编码方案,直接在 CountryCodesPlugin.ets 中以数组形式列出全部 249 个 ISO 3166-1 alpha-2 代码,确保与 Android 端 Locale.getISOCountries() 返回的数据一致。
第 3 阶段:插件注册
在 pubspec.yaml 中添加 OHOS 平台注册:
flutter:
plugin:
platforms:
android:
package: com.miguelruivo.flutter.plugin.countrycodes.country_codes
pluginClass: CountryCodesPlugin
ios:
pluginClass: CountryCodesPlugin
macos:
pluginClass: CountryCodesPlugin
ohos: # ← 新增
pluginClass: CountryCodesPlugin # ← 对应 index.ets 默认导出
Flutter 的 OHOS 引擎在构建时会读取 pubspec.yaml 中的 ohos 配置,自动加载 ohos/index.ets 中导出的插件类。
第 4 阶段:示例应用创建
创建 example/ohos/ 目录用于端到端测试:
example/ohos/
├── AppScope/app.json5 # 应用配置
├── build-profile.json5 # 项目构建配置(含 SDK 版本)
├── hvigor/hvigor-config.json5 # 构建工具配置
├── oh-package.json5 # 顶层包配置
├── hvigorfile.ts # 构建入口
├── entry/
│ ├── build-profile.json5
│ ├── oh-package.json5
│ ├── src/main/
│ │ ├── module.json5 # entry 模块配置
│ │ ├── ets/
│ │ │ ├── entryability/
│ │ │ │ └── EntryAbility.ets # Ability 生命周期
│ │ │ └── pages/
│ │ │ └── Index.ets # UI 页面(Flutter 容器)
│ │ └── resources/
│ └── src/ohosTest/ # 测试目录
四、完整代码对照
4.1 Android vs OHOS 完整实现对照
| 维度 | Android (Kotlin) | OHOS (ArkTS) |
|---|---|---|
| 语言 | Kotlin / Java | ArkTS (TypeScript 语法) |
| 系统语言 API | Locale.getDefault().language |
i18n.System.getSystemLanguage() |
| 系统地区 API | Locale.getDefault().country |
i18n.System.getSystemRegion() |
| 本地化国家名 | Locale.getDisplayCountry() |
i18n.getDisplayCountry() |
| ISO 国家列表 | Locale.getISOCountries() |
硬编码 249 个 alpha-2 代码 |
| 方法通道名 | "country_codes" |
"country_codes"(保持一致) |
| 参数获取 | call.arguments |
call.args |
| 返回值格式 | listOf(lang, region, map) |
[lang, region, map] |
4.2 关键 ArkTS 语法差异
| Android 语法 | ArkTS 语法 | 备注 |
|---|---|---|
import io.flutter... |
import { ... } from '@ohos/flutter_ohos' |
OHOS 使用模块化导入 |
Locale.getDefault() |
i18n.System.getSystemXxx() |
API 命名不同 |
HashMap |
Record<string, string> |
类型系统差异 |
call.arguments |
call.args |
属性名不同 |
result.success(listOf(...)) |
result.success([...]) |
语法差异 |
五、关键决策说明
决策 1:保持通道名不变 — "country_codes"
Dart 层 MethodChannel('country_codes') 已经固定,OHOS 原生侧必须使用完全相同的通道名。这是跨平台插件适配的铁律——通道名是 Dart 与原生之间的通信契约。
决策 2:Dart 层零改动
country_codes.dart 的 MethodChannel.invokeMethod('getLocale', ...) 调用在 OHOS 上无需任何修改,因为:
- 方法签名
invokeMethod('getLocale', localeTag)完全不变 - 返回的数据结构
[String, String, Map]与 Android 一致 - 异常处理逻辑(失败返回
["", "", {}])也一致
决策 3:硬编码 ISO 国家列表
原因:OHOS 的 @ohos.i18n 基于 Unicode CLDR 提供了本地化能力,但没有暴露获取所有国家代码列表的系统 API。硬编码是当前最务实的方案。
维护策略:列表直接从 Android 的 Locale.getISOCountries() 输出中提取,以后跟随 Android SDK 更新即可。
决策 4:错误处理采用静默降级
private handleGetRegion(result: MethodResult): void {
try {
const region = i18n.System.getSystemRegion();
result.success(region);
} catch (err) {
result.success(""); // ← 静默降级,返回空字符串
}
}
所有原生方法在异常时返回空值而非抛出异常,保证 Dart 层不因原生异常而崩溃。
六、测试与验证
测试环境
| 项目 | 版本 |
|---|---|
| Flutter | 3.41.10-ohos-0.0.1-canary1 |
| Dart | 3.11.5 |
| HarmonyOS SDK | 5.1.0(18) |
| IDE | DevEco Studio 6.1.0 |
| 设备 ROM | ALN-AL00 6.1.0.117(SP6C00E115R4P9) |
验证要点
- 初始化 —
await CountryCodes.init()返回true - 设备 Locale —
CountryCodes.getDeviceLocale()返回Locale('zh', 'CN') - 国家详情 —
CountryCodes.detailsForLocale()返回正确的国家信息 - 所有国家列表 —
CountryCodes.countryCodes()返回 249 条记录 - 本地化名称 — 传入
en返回英文名,传入zh返回中文名
七、运行效果
适配完成后,通过 flutter screenshot 命令获取 OpenHarmony 设备上的运行截图:
flutter screenshot -d 192.168.10.251:42923

八、遗留问题与改进方向
已知问题
- 静态国家代码列表 — 硬编码列表不随系统更新自动增加新国家代码
- 参数类型安全 — 当前
call.args as string直接强转,传入非字符串参数会抛出异常
未来优化
- 从系统 API 动态发现国家代码 — 待 OHOS SDK 提供类似
Locale.getISOCountries()的 API 后可替换 - 参数校验 — 增加
typeof call.args === 'string'类型检查 - 单元测试 — 编写 OHOS 平台插件的自动化测试用例
八、总结
将一个 Flutter 插件适配到 OHOS 平台,核心路径可以概括为 三步走:
1. 找对应 ── 找到 OHOS 对每个 Android 原生 API 的等价实现
2. 保契约 ── 确保方法通道名、方法名、返回值结构完全一致
3. 补缺口 ── 对于 OHOS 不提供的 API(如 getISOCountries),用合理方案弥补
对于 country_codes 插件,适配涉及两个文件的新增(CountryCodesPlugin.ets + index.ets)和一行 pubspec.yaml 的修改。Dart 层和其他平台的代码完全不受影响——这正是 Flutter 跨平台插件生态的魅力所在。
参考文档
更多推荐


所有评论(0)