Flutter三方库适配OpenHarmony【flutter_libphonenumber】——库的介绍与鸿蒙适配全景概览
本文介绍了Flutter三方库flutter_libphonenumber适配OpenHarmony的技术方案。该库原本支持Android、iOS和Web平台,提供电话号码格式化、验证等7大核心功能。随着HarmonyOS NEXT发展,通过联合插件架构新增了纯ArkTS实现的鸿蒙平台适配,实现了功能完全对齐、API兼容、覆盖57个国家/地区的目标。技术方案采用Flutter官方推荐的联合插件架构
前言
欢迎来到 Flutter三方库适配OpenHarmony 系列文章!本系列将围绕 flutter_libphonenumber 这个 电话号码处理库 的鸿蒙平台适配,从架构设计、核心实现、API 详解到各国号码处理,进行全面深入的技术分享。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在移动应用开发中,电话号码的格式化和验证 是用户注册、联系人管理、通讯录等场景的基础能力。flutter_libphonenumber 是 Flutter 生态中一个功能完善的电话号码处理库,但在鸿蒙适配之前,它仅支持 Android、iOS 和 Web 三个平台。本文将全面介绍该库的功能定位、原始架构、鸿蒙适配的背景与动机、适配成果以及整体技术方案。
本文是系列的第 1 篇,完整目录请查看本系列文章目录
一、flutter_libphonenumber 是什么
1.1 库的定位
flutter_libphonenumber 是一个 Flutter 跨平台电话号码处理库,它封装了 Google 的 libphonenumber 能力,并融合了以下两个社区库的优点:
- phone_number — 提供号码解析能力
- flutter_multi_formatter — 提供实时输入格式化能力
它的核心价值在于:既能通过原生平台调用进行异步的电话号码格式化和解析,又能通过预加载的 mask 数据在 Dart 侧进行同步格式化,避免频繁的平台通道调用带来的性能开销。
提示:该库的同步格式化能力是其最大亮点——在
init()阶段预加载所有国家的 mask 数据后,后续的格式化操作完全在 Dart 侧完成,无需任何原生调用,性能极佳。
1.2 原始平台支持
在鸿蒙适配之前,该库支持以下平台:
| 平台 | 底层库 | 版本 | 实现语言 |
|---|---|---|---|
| Android | libphonenumber | 8.13.43 | Kotlin/Java |
| iOS | PhoneNumberKit | 3.8.0 | Swift |
| Web | libphonenumber-js | - | JavaScript |
1.3 核心功能一览
该库提供以下 7 大核心能力:
| 功能 | 方法 | 调用方式 | 说明 |
|---|---|---|---|
| 初始化 | init() |
异步 | 加载所有国家数据和 mask |
| 异步格式化 | format() |
异步 | 通过原生平台调用格式化号码 |
| 同步格式化 | formatNumberSync() |
同步 | 纯 Dart 侧 mask 匹配格式化 |
| 号码解析 | parse() |
异步 | 解析号码元数据(类型、e164、国家等) |
| 获取所有地区 | getAllSupportedRegions() |
异步 | 获取所有支持的国家/地区数据 |
| 实时格式化 | LibPhonenumberTextFormatter |
同步 | TextField 输入时实时格式化 |
| 格式化+验证 | getFormattedParseResult() |
异步 | 一步完成格式化和有效性验证 |
二、为什么要做鸿蒙适配
2.1 鸿蒙生态的发展
随着 HarmonyOS NEXT 的推进,越来越多的应用需要适配鸿蒙平台。Flutter 作为跨平台框架,已经通过 Flutter-OHOS 项目支持了 OpenHarmony 平台。但大量 Flutter 三方库仍然缺少鸿蒙平台的实现,flutter_libphonenumber 就是其中之一。
2.2 电话号码处理的刚需
电话号码的格式化和验证是几乎所有涉及以下场景的基础能力:
- 用户注册/登录 — 手机号验证码登录
- 联系人管理 — 通讯录号码格式化显示
- 国际化应用 — 不同国家号码格式的自动适配
- 表单验证 — 输入时实时校验号码格式
没有鸿蒙平台的实现,意味着使用该库的 Flutter 应用 无法在鸿蒙设备上正常运行 电话号码相关功能。
2.3 适配目标
本次适配的核心目标:
- 功能完全对齐:鸿蒙平台实现与 Android/iOS 平台功能一致
- API 完全兼容:上层 Dart 代码无需任何修改即可在鸿蒙平台运行
- 广泛的国家支持:覆盖亚洲、欧洲、北美、南美、大洋洲、非洲共 57 个国家/地区
- 纯 ArkTS 实现:不依赖任何第三方原生库,降低维护成本
三、整体架构
3.1 联合插件(Federated Plugin)架构
flutter_libphonenumber 采用 Flutter 官方推荐的联合插件架构,将代码分为多个包:
flutter_libphonenumber/ # 主包(应用层 API)
├── flutter_libphonenumber_platform_interface/ # 平台接口层(抽象定义)
├── flutter_libphonenumber_android/ # Android 实现(libphonenumber)
├── flutter_libphonenumber_ios/ # iOS 实现(PhoneNumberKit)
├── flutter_libphonenumber_web/ # Web 实现(libphonenumber-js)
└── flutter_libphonenumber_ohos/ # 🆕 鸿蒙实现(纯 ArkTS)
3.2 各层职责
| 层级 | 包名 | 职责 |
|---|---|---|
| 应用层 | flutter_libphonenumber |
对外暴露 API,开发者直接使用 |
| 接口层 | flutter_libphonenumber_platform_interface |
定义抽象接口、数据模型(CountryWithPhoneCode、PhoneNumberType 等)、同步格式化逻辑 |
| 平台实现层 | flutter_libphonenumber_ohos 等 |
各平台的原生实现,通过 MethodChannel 与 Dart 侧通信 |
关键点:联合插件架构的好处是——添加新平台支持时,不需要修改主包和接口层的任何代码,只需新增一个平台实现包即可。
3.3 鸿蒙适配包的内部结构
flutter_libphonenumber_ohos/
├── lib/
│ └── flutter_libphonenumber_ohos.dart # Dart 侧:MethodChannel 调用封装
├── ohos/
│ └── src/main/ets/components/plugin/
│ ├── FlutterLibphonenumberPlugin.ets # ArkTS 侧:MethodChannel 消息分发
│ └── PhoneNumberUtil.ets # ArkTS 侧:核心格式化/解析逻辑
├── pubspec.yaml # 包配置与依赖声明
└── example/ # 演示工程
└── lib/main.dart # 演示页面
3.4 pubspec.yaml 关键配置
flutter:
plugin:
implements: flutter_libphonenumber
platforms:
ohos:
package: com.bottlepay.flutter_libphonenumber
pluginClass: FlutterLibphonenumberPlugin
dartPluginClass: FlutterLibphonenumberOhos
这段配置告诉 Flutter 框架:
- 本包 实现了
flutter_libphonenumber插件 - 目标平台是 ohos(OpenHarmony)
- 原生侧入口类是
FlutterLibphonenumberPlugin - Dart 侧入口类是
FlutterLibphonenumberOhos
四、鸿蒙适配的技术方案
4.1 与 Android/iOS 的关键差异
Android 和 iOS 平台分别使用了成熟的第三方库,而鸿蒙平台采用了 纯 ArkTS 实现 的方案:
| 对比项 | Android | iOS | 鸿蒙(OHOS) |
|---|---|---|---|
| 实现语言 | Kotlin/Java | Swift | ArkTS |
| 底层库 | Google libphonenumber | PhoneNumberKit | 无(纯自研) |
| 数据来源 | libphonenumber 内置 | PhoneNumberKit 内置 | 手动定义 57 国数据 |
| 格式化方式 | 调用库 API | 调用库 API | 自实现格式化算法 |
| 号码类型检测 | 库内置正则 | 库内置规则 | 自实现按国家判定 |
| 维护成本 | 依赖上游更新 | 依赖上游更新 | 完全自主可控 |
4.2 三层调用链路
一次完整的 format() 调用经过以下三层链路:
┌─────────────┐ MethodChannel ┌──────────────────────────┐ ┌────────────────┐
│ Dart 侧 │ ──────────────────→ │ FlutterLibphonenumber │ ──→│ PhoneNumberUtil │
│ App 代码 │ │ Plugin.ets │ │ .ets │
│ │ ←────────────────── │ (消息分发) │ ←──│ (格式化/解析) │
└─────────────┘ 返回结果 └──────────────────────────┘ └────────────────┘
完整的调用步骤:
- App 调用
format("+8613123456789", "CN") - Dart 侧 通过 MethodChannel 发送
{method: "format", phone: "+8613123456789", region: "CN"} - ArkTS 侧 Plugin 接收消息,分发到
handleFormat方法 handleFormat调用PhoneNumberUtil.getAsYouTypeFormatter("CN")逐字符格式化- 返回
{formatted: "+86 131 2345 6789"},结果沿原路返回到 App
4.3 Dart 侧实现
Dart 侧的实现非常简洁,核心就是通过 MethodChannel 转发调用到原生侧:
const _channel = MethodChannel('com.bottlepay/flutter_libphonenumber_ohos');
class FlutterLibphonenumberOhos extends FlutterLibphonenumberPlatform {
/// 注册为默认平台实现
static void registerWith() {
FlutterLibphonenumberPlatform.instance = FlutterLibphonenumberOhos();
}
Future<Map<String, String>> format(String phone, String region) async {
return await _channel.invokeMapMethod<String, String>('format', {
'phone': phone,
'region': region,
}) ?? <String, String>{};
}
Future<Map<String, dynamic>> parse(String phone, {String? region}) async {
return await _channel.invokeMapMethod<String, dynamic>('parse', {
'phone': phone,
'region': region,
}) ?? <String, dynamic>{};
}
Future<void> init({Map<String, CountryWithPhoneCode> overrides = const {}}) async {
return CountryManager().loadCountries(
phoneCodesMap: await getAllSupportedRegions(),
overrides: overrides,
);
}
}
注意:
init()方法的实现逻辑是——先调用getAllSupportedRegions()从原生侧获取所有国家数据,然后交给CountryManager加载到内存中,供后续的同步格式化使用。
4.4 ArkTS 侧实现概览
ArkTS 侧由两个核心文件组成:
FlutterLibphonenumberPlugin.ets — 负责接收 MethodChannel 消息并分发到对应的处理方法:
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method === 'format') {
this.handleFormat(call, result);
} else if (call.method === 'parse') {
this.handleParse(call, result);
} else if (call.method === 'get_all_supported_regions') {
this.handleGetAllSupportedRegions(result);
} else {
result.notImplemented();
}
}
PhoneNumberUtil.ets — 核心工具类,采用 单例模式,包含以下能力:
- 57 个国家的完整数据定义(
CountryData类) - 10+ 个国家的 专属格式化规则(中国、美国、英国、日本、德国、法国、澳大利亚、巴西、印度、俄罗斯)
- 号码解析(支持 国际号码 和 国内号码 两种输入格式)
- 号码类型检测(mobile / fixedLine / fixedOrMobile)
- AsYouType 实时格式化器(逐字符输入并实时格式化)
五、核心数据模型
5.1 CountryData(ArkTS 侧)
每个国家在 ArkTS 侧用 CountryData 类表示:
export class CountryData {
name: string = ''; // 国家名称,如 "China"
code: string = ''; // 国家区号,如 "86"
mobileExample: string = ''; // 手机号示例,如 "13123456789"
fixedLineExample: string = '';// 固话示例,如 "1012345678"
mobilePattern: string = ''; // 手机号正则,如 "1[3-9]\\d{9}"
fixedLinePattern: string = '';// 固话正则,如 "\\d{2,4}\\d{7,8}"
nationalPrefix: string = ''; // 国内拨号前缀,如 "0"
}
5.2 CountryWithPhoneCode(Dart 侧)
Dart 侧使用 CountryWithPhoneCode 类,包含更丰富的格式化信息:
class CountryWithPhoneCode {
final String phoneCode; // '86'
final String countryCode; // 'CN'
final String countryName; // 'China'
final String exampleNumberMobileNational; // '131 2345 6789'
final String exampleNumberFixedLineNational; // '010 1234 5678'
final String phoneMaskMobileNational; // '000 0000 0000'
final String phoneMaskFixedLineNational; // '000 0000 0000'
final String exampleNumberMobileInternational; // '+86 131 2345 6789'
final String exampleNumberFixedLineInternational; // '+86 10 1234 5678'
final String phoneMaskMobileInternational; // '+00 000 0000 0000'
final String phoneMaskFixedLineInternational; // '+00 00 0000 0000'
}
关键:
phoneMask字段是同步格式化的核心——它定义了号码的格式模板,formatNumberSync()就是用这个 mask 来匹配和格式化输入的号码。
六、支持的 57 个国家/地区
鸿蒙适配版本覆盖六大洲 57 个国家和地区:
| 洲 | 国家/地区代码 | 数量 |
|---|---|---|
| 亚洲 | CN、HK、MO、TW、JP、KR、IN、SG、MY、TH、VN、PH、ID、PK、BD、AE、SA、IL、TR | 19 |
| 欧洲 | GB、DE、FR、IT、ES、PT、NL、BE、AT、CH、SE、NO、DK、FI、PL、RU、UA、CZ、GR、HU、RO、IE | 22 |
| 北美洲 | US、CA、MX | 3 |
| 南美洲 | BR、AR、CL、CO、PE、VE | 6 |
| 大洋洲 | AU、NZ | 2 |
| 非洲 | ZA、EG、NG、KE、MA | 5 |
其中 10 个国家 拥有专属的国内格式化规则:
- 中国(CN):
131 2345 6789 - 美国/加拿大(US/CA):
(201) 555-0123 - 英国(GB):
07400 123456 - 日本(JP):
090-1234-5678 - 德国(DE):
0151 2345 6789 - 法国(FR):
06 12 34 56 78 - 澳大利亚(AU):
0412 345 678 - 巴西(BR):
(11) 91234-5678 - 印度(IN):
81234 56789 - 俄罗斯(RU):
8 912 345-67-89
七、演示效果
7.1 Demo 页面布局
演示工程的页面布局与 主包 example 完全对齐,分为三个区域:
| 区域 | 内容 | 功能说明 |
|---|---|---|
| 顶部左侧 | Print all region data 按钮 + 国家选择按钮 | 获取全量国家数据 / 切换当前国家 |
| 顶部右侧 | 4 个 Switch 开关 | 切换号码类型、格式风格、区号模式、光标模式 |
| 中间 | Format as you type 输入框 | 实时同步格式化(纯 Dart 侧 mask) |
| 底部 | 手动输入框 + 3 个操作按钮 | 异步格式化 / 同步格式化 / 号码解析 |
7.2 格式化效果示例
以美国号码为例,在 Format as you type 输入框中输入:
输入:+12015550123
实时格式化结果:+1 201-555-0123
以中国号码为例:
输入:+8613123456789
实时格式化结果:+86 131 2345 6789
7.3 解析效果示例
在底部手动输入框中输入 +8613123456789,点击 Parse 按钮,返回的 JSON 数据:
{
"type": "mobile",
"e164": "+8613123456789",
"international": "+86 131 2345 6789",
"national": "131 2345 6789",
"country_code": "86",
"region_code": "CN",
"national_number": "13123456789"
}
各字段含义:
| 字段 | 含义 | 示例值 |
|---|---|---|
type |
号码类型 | mobile(手机)/ fixedLine(固话) |
e164 |
E.164 国际标准格式 | +8613123456789 |
international |
国际格式(带空格) | +86 131 2345 6789 |
national |
国内格式 | 131 2345 6789 |
country_code |
国家区号 | 86 |
region_code |
国家/地区代码 | CN |
national_number |
国内号码(纯数字) | 13123456789 |
八、快速上手
8.1 添加依赖
dependencies:
flutter_libphonenumber: ^2.0.0
说明:鸿蒙平台会自动引入
flutter_libphonenumber_ohos包,无需手动添加。这是联合插件架构的 endorsed(背书) 机制在起作用。
8.2 初始化
import 'package:flutter_libphonenumber/flutter_libphonenumber.dart';
// 在应用启动时调用,加载所有国家数据
await init();
8.3 同步格式化(推荐)
final formatted = formatNumberSync(
'+8613123456789',
country: CountryManager().countries.firstWhere((c) => c.countryCode == 'CN'),
phoneNumberType: PhoneNumberType.mobile,
phoneNumberFormat: PhoneNumberFormat.international,
);
// 结果: "+86 131 2345 6789"
8.4 异步格式化
final result = await format('+8613123456789', 'CN');
print(result['formatted']); // "+86 131 2345 6789"
8.5 号码解析
final parsed = await parse('+8613123456789', region: 'CN');
print(parsed['type']); // "mobile"
print(parsed['e164']); // "+8613123456789"
8.6 实时格式化(TextField)
TextField(
inputFormatters: [
LibPhonenumberTextFormatter(
country: const CountryWithPhoneCode.us(),
phoneNumberType: PhoneNumberType.mobile,
phoneNumberFormat: PhoneNumberFormat.international,
inputContainsCountryCode: true,
shouldKeepCursorAtEndOfInput: true,
),
],
)
九、开发环境
| 项目 | 版本 |
|---|---|
| Flutter SDK | >= 3.0.0 |
| Dart SDK | >= 2.12.0 |
| OpenHarmony SDK | API 20 |
| Flutter | 3.35.7-dev |
| DevEco Studio | 6.0.2 Release(构建版本:6.0.2.640,2026年1月19日) |
| ROM | 6.0.0.130 SP8 |
提示:OpenHarmony 开发需要安装支持 Flutter-OHOS 的 DevEco Studio,普通版 DevEco Studio 不支持 Flutter 开发。
十、系列文章预告
本文是系列的第 1 篇,后续文章将深入每个技术细节:
- 第 2 篇:联合插件(Federated Plugin)架构解析
- 第 3 篇:鸿蒙平台插件包的创建与注册
- 第 4 篇:init() 初始化流程与国家数据加载机制
- 第 5 篇:CountryWithPhoneCode 数据模型详解
- 第 6 篇:CountryManager 国家列表管理与缓存机制
完整 30 篇目录请查看本系列文章目录
总结
本文从全局视角介绍了 flutter_libphonenumber 的功能定位、联合插件架构、鸿蒙适配的背景与技术方案。关键要点回顾:
- flutter_libphonenumber 是一个功能完善的电话号码处理库,支持格式化、解析、实时输入格式化等 7 大核心能力
- 鸿蒙适配 采用 纯 ArkTS 实现,不依赖任何第三方原生库,手动定义了 57 个国家 的格式化规则
- 联合插件架构 确保了上层 API 完全兼容,开发者 无需修改任何业务代码 即可在鸿蒙平台运行
- 三层调用链路(Dart → MethodChannel → ArkTS)清晰分离了关注点,每层职责明确
下一篇我们将深入解析联合插件的架构设计,看看 platform_interface 是如何定义抽象接口,各平台实现包又是如何注册和生效的。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony 适配仓库:gitcode.com/oh-flutter/flutter_libphonenumber
- 开源鸿蒙跨平台社区:openharmonycrossplatform.csdn.net
- Flutter 联合插件文档:docs.flutter.dev - Federated plugins
- Google libphonenumber:github.com/google/libphonenumber
- PhoneNumberKit(iOS):github.com/marmelroy/PhoneNumberKit
- Flutter-OHOS 项目:gitee.com/openharmony-sig/flutter_flutter
- phone_number 库:github.com/nashfive/phone_number
- flutter_multi_formatter 库:github.com/caseyryan/flutter_multi_formatter
更多推荐
所有评论(0)