前言

欢迎来到 Flutter三方库适配OpenHarmony 系列文章!本系列将围绕 flutter_libphonenumber 这个 电话号码处理库 的鸿蒙平台适配,从架构设计、核心实现、API 详解到各国号码处理,进行全面深入的技术分享。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在这里插入图片描述

在移动应用开发中,电话号码的格式化和验证 是用户注册、联系人管理、通讯录等场景的基础能力。flutter_libphonenumber 是 Flutter 生态中一个功能完善的电话号码处理库,但在鸿蒙适配之前,它仅支持 AndroidiOSWeb 三个平台。本文将全面介绍该库的功能定位、原始架构、鸿蒙适配的背景与动机、适配成果以及整体技术方案。

本文是系列的第 1 篇,完整目录请查看本系列文章目录


一、flutter_libphonenumber 是什么

1.1 库的定位

flutter_libphonenumber 是一个 Flutter 跨平台电话号码处理库,它封装了 Google 的 libphonenumber 能力,并融合了以下两个社区库的优点:

它的核心价值在于:既能通过原生平台调用进行异步的电话号码格式化和解析,又能通过预加载的 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 电话号码处理的刚需

电话号码的格式化和验证是几乎所有涉及以下场景的基础能力:

  1. 用户注册/登录 — 手机号验证码登录
  2. 联系人管理 — 通讯录号码格式化显示
  3. 国际化应用 — 不同国家号码格式的自动适配
  4. 表单验证 — 输入时实时校验号码格式

没有鸿蒙平台的实现,意味着使用该库的 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 定义抽象接口、数据模型(CountryWithPhoneCodePhoneNumberType 等)、同步格式化逻辑
平台实现层 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 框架:

  1. 本包 实现了 flutter_libphonenumber 插件
  2. 目标平台是 ohos(OpenHarmony)
  3. 原生侧入口类是 FlutterLibphonenumberPlugin
  4. 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            │
│              │ ←────────────────── │ (消息分发)                │ ←──│ (格式化/解析)    │
└─────────────┘    返回结果          └──────────────────────────┘    └────────────────┘

完整的调用步骤:

  1. App 调用 format("+8613123456789", "CN")
  2. Dart 侧 通过 MethodChannel 发送 {method: "format", phone: "+8613123456789", region: "CN"}
  3. ArkTS 侧 Plugin 接收消息,分发到 handleFormat 方法
  4. handleFormat 调用 PhoneNumberUtil.getAsYouTypeFormatter("CN") 逐字符格式化
  5. 返回 {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 篇,后续文章将深入每个技术细节:

  1. 第 2 篇:联合插件(Federated Plugin)架构解析
  2. 第 3 篇:鸿蒙平台插件包的创建与注册
  3. 第 4 篇:init() 初始化流程与国家数据加载机制
  4. 第 5 篇:CountryWithPhoneCode 数据模型详解
  5. 第 6 篇:CountryManager 国家列表管理与缓存机制

完整 30 篇目录请查看本系列文章目录


总结

本文从全局视角介绍了 flutter_libphonenumber 的功能定位、联合插件架构、鸿蒙适配的背景与技术方案。关键要点回顾:

  1. flutter_libphonenumber 是一个功能完善的电话号码处理库,支持格式化、解析、实时输入格式化等 7 大核心能力
  2. 鸿蒙适配 采用 纯 ArkTS 实现,不依赖任何第三方原生库,手动定义了 57 个国家 的格式化规则
  3. 联合插件架构 确保了上层 API 完全兼容,开发者 无需修改任何业务代码 即可在鸿蒙平台运行
  4. 三层调用链路(Dart → MethodChannel → ArkTS)清晰分离了关注点,每层职责明确

下一篇我们将深入解析联合插件的架构设计,看看 platform_interface 是如何定义抽象接口,各平台实现包又是如何注册和生效的。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐