Flutter三方库适配OpenHarmony【flutter_libphonenumber】——format() 异步格式化的完整调用链路
本文介绍了Flutter三方库flutter_libphonenumber在OpenHarmony平台上的适配过程,重点解析了format()方法的完整调用链路。该方法实现了电话号码的跨平台异步格式化,从Dart侧发起调用,通过MethodChannel编码传输到ArkTS侧处理,最终返回格式化结果。具体步骤包括:应用层调用Dart接口、MethodChannel通信、Flutter Engine
前言
欢迎来到 Flutter三方库适配OpenHarmony 系列文章!本系列围绕 flutter_libphonenumber 这个 电话号码处理库 的鸿蒙平台适配,进行全面深入的技术分享。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


上一篇我们分析了 57 个国家格式化规则的数据结构设计。从本篇开始,我们进入 核心 API 实现 部分。本篇将追踪 format() 方法的完整调用链路——从应用层的一次函数调用开始,经过 Dart 侧的 MethodChannel 编码、Flutter Engine 的二进制传输、ArkTS 侧的消息接收与分发、AsYouTypeFormatter 的逐字符格式化,最终将结果回传到 Dart 侧。
format()是一个典型的 跨平台异步调用。理解它的完整链路,就理解了 Flutter 鸿蒙插件中 Dart ↔ ArkTS 通信的核心模式。
一、format() 的使用方式
1.1 应用层调用
// 在 example 工程中的使用
final res = await _plugin.format(
'+8613123456789', // 电话号码
'CN', // 区域代码
);
print(res['formatted']); // '+86 131 2345 6789'
1.2 方法签名
// FlutterLibphonenumberPlatform 中的定义
Future<Map<String, String>> format(
final String phone,
final String region,
) async;
| 参数 | 类型 | 说明 | 示例 |
|---|---|---|---|
phone |
String | 待格式化的电话号码 | '+8613123456789' |
region |
String | 区域代码(ISO 3166-1) | 'CN' |
| 返回值 | Map | 包含 formatted 字段 |
{'formatted': '+86 131 2345 6789'} |
二、完整调用链路(6 步)
2.1 链路总览
步骤 ① App 层
plugin.format('+8613123456789', 'CN')
│
步骤 ② Dart: FlutterLibphonenumberOhos
_channel.invokeMapMethod('format', {'phone':..., 'region':...})
│
步骤 ③ Flutter Engine
StandardMessageCodec 编码 → BinaryMessenger 传输
│
步骤 ④ ArkTS: FlutterLibphonenumberPlugin
onMethodCall → handleFormat(call, result)
│
步骤 ⑤ ArkTS: PhoneNumberUtil
AsYouTypeFormatter 逐字符格式化
│
步骤 ⑥ ArkTS → Dart
result.success({formatted: '...'}) → Future 完成
2.2 步骤 ① — 应用层调用
// example/lib/main.dart
onPressed: () async {
final res = await _plugin.format(
manualFormatController.text,
_currentSelectedCountry.countryCode,
);
setState(() =>
manualFormatController.text = res['formatted'] ?? '');
}
应用层通过 _plugin(即 FlutterLibphonenumberPlatform.instance)调用 format()。由于是 async 方法,使用 await 等待结果。
2.3 步骤 ② — Dart 侧 MethodChannel 调用
// FlutterLibphonenumberOhos.format()
Future<Map<String, String>> format(
final String phone,
final String region,
) async {
return await _channel.invokeMapMethod<String, String>(
'format',
{'phone': phone, 'region': region},
) ?? <String, String>{};
}
关键点:
| 要素 | 值 | 说明 |
|---|---|---|
| 通道 | _channel |
MethodChannel('com.bottlepay/flutter_libphonenumber_ohos') |
| 方法名 | 'format' |
ArkTS 侧 onMethodCall 的匹配依据 |
| 参数 | {'phone': ..., 'region': ...} |
Map 类型,自动序列化 |
| 返回类型 | Map<String, String>? |
可空,用 ?? <String, String>{} 兜底 |
2.4 步骤 ③ — Flutter Engine 传输
Dart Map {'phone': '+8613123456789', 'region': 'CN'}
│
↓ StandardMessageCodec.encodeMessage()
│ 将 Map 编码为二进制字节流
│
↓ BinaryMessenger.send()
│ 通过 Flutter Engine 内部通道传输
│
↓ ArkTS 侧 BinaryMessenger 接收
│
↓ StandardMessageCodec.decodeMessage()
│ 将字节流解码为 MethodCall 对象
│
↓ MethodCall { method: 'format', arguments: {...} }
StandardMessageCodec 支持的类型映射:
| Dart 类型 | ArkTS 类型 |
|---|---|
null |
null |
bool |
boolean |
int |
number |
double |
number |
String |
string |
Map |
ESObject (需 call.argument() 提取) |
2.5 步骤 ④ — ArkTS 侧消息分发
// FlutterLibphonenumberPlugin.ets
onMethodCall(call: MethodCall, result: MethodResult): void {
if (call.method === 'format') {
this.handleFormat(call, result); // ← 路由到这里
}
// ...
}
2.6 步骤 ⑤ — handleFormat 处理
private handleFormat(
call: MethodCall, result: MethodResult
): void {
let phone = call.argument('phone') as string;
let region = call.argument('region') as string;
if (phone === null || phone.length === 0) {
result.error('InvalidParameters',
"Invalid 'phone' parameter.", null);
return;
}
try {
let useRegion = region !== null ? region : 'CN';
let formatter =
this.phoneUtil.getAsYouTypeFormatter(useRegion);
let formatted = '';
formatter.clear();
for (let i = 0; i < phone.length; i++) {
formatted = formatter.inputDigit(phone.charAt(i));
}
let response: Map<string, string> = new Map();
response.set('formatted', formatted);
result.success(this.convertMapToRecord(response));
} catch (e) {
result.error('FORMAT_ERROR',
'Failed to format phone number', null);
}
}
2.7 步骤 ⑥ — 结果回传
ArkTS: result.success({formatted: '+86 131 2345 6789'})
│
↓ Record<string,string> → StandardMessageCodec 编码
│
↓ BinaryMessenger 回传
│
↓ Dart: invokeMapMethod() 的 Future 完成
│
↓ Map<String, String> {'formatted': '+86 131 2345 6789'}
│
↓ App: res['formatted'] → '+86 131 2345 6789'
三、Dart 侧的调用封装
3.1 FlutterLibphonenumberOhos 的 format() 实现
// flutter_libphonenumber_ohos.dart
const _channel = MethodChannel(
'com.bottlepay/flutter_libphonenumber_ohos');
class FlutterLibphonenumberOhos
extends FlutterLibphonenumberPlatform {
Future<Map<String, String>> format(
final String phone,
final String region,
) async {
return await _channel.invokeMapMethod<String, String>(
'format',
{'phone': phone, 'region': region},
) ?? <String, String>{};
}
}
3.2 invokeMapMethod 的内部机制
invokeMapMethod<K, V>() 是 MethodChannel 提供的类型安全调用方法。它的内部执行流程:
invokeMapMethod<String, String>('format', args)
│
├── ① 构建 MethodCall 对象
│ MethodCall('format', {'phone': ..., 'region': ...})
│
├── ② StandardMethodCodec 编码
│ 方法名 → UTF-8 字节
│ 参数 Map → 键值对序列化
│
├── ③ BinaryMessenger.send()
│ 通过通道名路由到 ArkTS 侧
│
├── ④ 等待响应(异步)
│ Dart 侧挂起当前 Future
│
├── ⑤ 接收响应字节流
│ StandardMethodCodec 解码
│
└── ⑥ 类型转换
dynamic → Map<String, String>?
3.3 与 invokeMethod 的区别
Flutter MethodChannel 提供了三种调用方法:
| 方法 | 返回类型 | 适用场景 |
|---|---|---|
invokeMethod<T> |
Future<T?> |
返回单一值(String, int 等) |
invokeListMethod<T> |
Future<List<T>?> |
返回列表 |
invokeMapMethod<K,V> |
Future<Map<K,V>?> |
返回键值对 |
format() 返回的是 Map<String, String>,因此使用 invokeMapMethod。如果使用 invokeMethod<Map>,需要手动进行类型转换:
// 使用 invokeMapMethod(推荐)
final result = await _channel
.invokeMapMethod<String, String>('format', args);
// result 类型: Map<String, String>?
// 使用 invokeMethod(需手动转换)
final raw = await _channel
.invokeMethod<Map>('format', args);
final result = raw?.cast<String, String>();
// 多了一步 cast 操作
3.4 null 安全处理
return await _channel.invokeMapMethod<String, String>(
'format', {'phone': phone, 'region': region},
) ?? <String, String>{};
?? <String, String>{} 处理了两种情况:
| 情况 | invokeMapMethod 返回值 | 最终返回值 |
|---|---|---|
| ArkTS 正常返回 | {'formatted': '...'} |
{'formatted': '...'} |
| ArkTS 返回 null | null |
{} (空 Map) |
| ArkTS 返回 error | 抛出 PlatformException | 不走 ?? 逻辑 |
注意:当 ArkTS 侧调用
result.error()时,Dart 侧会抛出PlatformException,不会走到??逻辑。??只处理result.success(null)的情况。
四、ArkTS 侧的消息接收与参数提取
4.1 onMethodCall 分发
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();
}
}
call.method 是一个字符串,与 Dart 侧 invokeMapMethod 的第一个参数完全对应。
4.2 参数提取方式
let phone = call.argument('phone') as string;
let region = call.argument('region') as string;
call.argument(key) 从 Dart 侧传入的 Map 中提取指定 key 的值。返回类型是 ESObject(类似 any),需要通过 as string 进行类型断言。
参数对应关系:
| Dart 侧 | ArkTS 侧 |
|---|---|
{'phone': '+8613123456789'} |
call.argument('phone') → '+8613123456789' |
{'region': 'CN'} |
call.argument('region') → 'CN' |
4.3 参数校验
if (phone === null || phone.length === 0) {
result.error('InvalidParameters',
"Invalid 'phone' parameter.", null);
return;
}
校验逻辑只检查 phone 参数,region 参数允许为 null(会使用默认值 'CN'):
let useRegion: string = region !== null ? region : 'CN';
| phone | region | 行为 |
|---|---|---|
null |
任意 | 返回 InvalidParameters 错误 |
'' |
任意 | 返回 InvalidParameters 错误 |
'+86...' |
null |
使用默认 region=‘CN’ |
'+86...' |
'CN' |
正常处理 |
4.4 handleFormat 的完整执行流程
handleFormat(call, result)
│
├── ① 提取参数
│ phone = '+8613123456789'
│ region = 'CN'
│
├── ② 参数校验
│ phone 非空 → 通过 ✅
│
├── ③ 确定区域
│ useRegion = 'CN'
│
├── ④ 创建 AsYouTypeFormatter
│ formatter = phoneUtil.getAsYouTypeFormatter('CN')
│ formatter.clear()
│
├── ⑤ 逐字符格式化
│ for (i = 0; i < 14; i++)
│ formatted = formatter.inputDigit(phone[i])
│
├── ⑥ 构建响应
│ response = Map { 'formatted': '+86 131 2345 6789' }
│
├── ⑦ Map → Record 转换
│ record = { formatted: '+86 131 2345 6789' }
│
└── ⑧ 返回结果
result.success(record)
五、AsYouTypeFormatter 逐字符格式化详解
5.1 格式化过程
以 '+8613123456789' 为例,handleFormat 中的循环:
for (let i = 0; i < phone.length; i++) {
formatted = formatter.inputDigit(phone.charAt(i));
}
每次 inputDigit() 的返回值:
| 步骤 | 输入字符 | 返回值 | 内部状态变化 |
|---|---|---|---|
| 1 | + |
+ |
isInternational=true |
| 2 | 8 |
+8 |
countryCode=‘8’ |
| 3 | 6 |
+86 |
countryCode=‘86’, region=‘CN’ |
| 4 | 1 |
+86 1 |
nationalNumber=‘1’ |
| 5 | 3 |
+86 13 |
nationalNumber=‘13’ |
| 6 | 1 |
+86 131 |
nationalNumber=‘131’ |
| 7 | 2 |
+86 131 2 |
nationalNumber=‘1312’ |
| 8 | 3 |
+86 131 23 |
nationalNumber=‘13123’ |
| 9 | 4 |
+86 131 234 |
nationalNumber=‘131234’ |
| 10 | 5 |
+86 131 2345 |
nationalNumber=‘1312345’ |
| 11 | 6 |
+86 131 2345 6 |
nationalNumber=‘13123456’ |
| 12 | 7 |
+86 131 2345 67 |
nationalNumber=‘131234567’ |
| 13 | 8 |
+86 131 2345 678 |
nationalNumber=‘1312345678’ |
| 14 | 9 |
+86 131 2345 6789 |
nationalNumber=‘13123456789’ |
5.2 区号识别过程
在步骤 2-3 中,inputDigit 尝试识别区号:
步骤 2: countryCode='8'
→ findRegionByCode('8') → null(没有区号为8的国家)
→ 继续累积
步骤 3: countryCode='86'
→ findRegionByCode('86') → 'CN' ✅
→ region 更新为 'CN'
→ 后续数字按中国格式化
5.3 国内号码的格式化
识别到 CN 后,nationalNumber 的格式化使用 formatPartialNumber():
// 国际模式下的输出拼接
this.currentOutput = '+' + this.countryCode + ' '
+ this.formatPartialNumber(this.nationalNumber);
formatPartialNumber() 按长度分段:
长度 1-3: 直接输出 '131'
长度 4-6: 3+N '131 2' '131 23' '131 234'
长度 7-10: 3+3+N '131 234 5' ... '131 2345 678'
长度 11+: 3+4+N '131 2345 6789'
六、多国格式化结果对比
6.1 10 国 format() 实际调用结果
| 国家 | 输入 | 格式化结果 |
|---|---|---|
| 中国手机 | +8613123456789 |
+86 131 2345 6789 |
| 中国固话 | +861012345678 |
+86 101 234 5678 |
| 美国 | +12015550123 |
+1 201 555 0123 |
| 英国 | +447400123456 |
+44 740 012 3456 |
| 日本 | +819012345678 |
+81 901 234 5678 |
| 德国 | +4915123456789 |
+49 151 2345 6789 |
| 法国 | +33612345678 |
+33 612 345 678 |
| 澳大利亚 | +61412345678 |
+61 412 345 678 |
| 巴西 | +5511912345678 |
+55 119 1234 5678 |
| 俄罗斯 | +79123456789 |
+7 912 345 6789 |
6.2 格式化差异分析
注意 format() 使用的是 AsYouTypeFormatter(逐字符格式化),它的输出与 formatInternational()(一次性格式化)可能略有不同:
| 方法 | 格式化方式 | CN 结果 |
|---|---|---|
format() |
AsYouTypeFormatter 逐字符 | +86 131 2345 6789 |
formatInternational() |
一次性 formatWithSpaces | +86 131 2345 6789 |
对于中国号码,两者结果一致。但对于某些国家,逐字符格式化可能产生不同的分组方式,因为 AsYouTypeFormatter 使用通用的 formatPartialNumber() 而非国家专用格式化函数。
七、错误处理链路
7.1 参数为空
App: plugin.format('', 'CN')
→ Dart: invokeMapMethod('format', {'phone': '', 'region': 'CN'})
→ ArkTS: phone.length === 0
→ result.error('InvalidParameters', "Invalid 'phone' parameter.", null)
→ Dart: PlatformException(InvalidParameters, ...)
→ App: catch (e) { ... }
7.2 格式化异常
App: plugin.format('+999...', 'XX')
→ ArkTS: AsYouTypeFormatter 处理
→ 如果抛出异常 → catch (e)
→ result.error('FORMAT_ERROR', 'Failed to format phone number', null)
→ Dart: PlatformException(FORMAT_ERROR, ...)
7.3 Dart 侧的防御性处理
return await _channel.invokeMapMethod<String, String>(
'format', {...}
) ?? <String, String>{}; // null 时返回空 Map
?? <String, String>{} 确保即使 ArkTS 侧返回 null,Dart 侧也不会抛出空指针异常。
八、性能分析
8.1 各阶段耗时
| 阶段 | 预估耗时 | 说明 |
|---|---|---|
| Dart 参数构建 | < 0.01ms | Map 创建 |
| StandardMessageCodec 编码 | < 0.1ms | 二进制序列化 |
| Engine 传输 | < 1ms | 进程内通信 |
| ArkTS 参数提取 | < 0.01ms | call.argument() |
| AsYouTypeFormatter | < 0.5ms | 逐字符格式化 |
| Map→Record 转换 | < 0.01ms | 1 个字段 |
| 结果回传 | < 1ms | 反向传输 |
| 总计 | < 3ms | 用户无感知 |
8.2 与 formatNumberSync 的性能对比
| 指标 | format()(异步) | formatNumberSync()(同步) |
|---|---|---|
| 耗时 | ~3ms | < 0.1ms |
| 跨平台通信 | 有(MethodChannel) | 无(纯 Dart) |
| 适用场景 | 手动触发 | 实时输入 |
| 格式化精度 | AsYouTypeFormatter | Mask 匹配 |
为什么实时输入用同步:
LibPhonenumberTextFormatter的formatEditUpdate()在每次按键时调用,如果使用异步的format()会导致格式化延迟和闪烁。因此实时输入场景必须使用同步的formatNumberSync()。
九、与 parse() 调用链路的对比
9.1 相同点
| 相同点 | 说明 |
|---|---|
| 通道 | 同一个 MethodChannel |
| 编码方式 | 同一个 StandardMessageCodec |
| 参数格式 | 都是 {'phone': ..., 'region': ...} |
| 错误处理 | 都有参数校验 + 异常兜底 |
9.2 不同点
| 对比项 | format() | parse() |
|---|---|---|
| 方法名 | 'format' |
'parse' |
| 处理方式 | AsYouTypeFormatter 逐字符 | PhoneNumberUtil.parse() 一次性 |
| 返回字段数 | 1 个(formatted) | 7 个(type, e164, …) |
| 有效性验证 | 无 | isValidNumber() |
| 错误码 | FORMAT_ERROR | InvalidNumber / PARSE_ERROR |
| Dart 返回类型 | Map<String, String> |
Map<String, dynamic> |
十、format() 在 example 工程中的使用
10.1 手动格式化按钮
ElevatedButton(
child: const Text('Format\n(Async)'),
onPressed: () async {
final res = await _plugin.format(
manualFormatController.text,
_currentSelectedCountry.countryCode,
);
setState(() =>
manualFormatController.text = res['formatted'] ?? '');
},
)
10.2 使用流程
用户输入: +8613123456789
│
├── 点击 "Format (Async)" 按钮
│
├── plugin.format('+8613123456789', 'CN')
│ → MethodChannel → ArkTS → AsYouTypeFormatter
│ → '+86 131 2345 6789'
│
└── TextField 更新为格式化后的号码
十一、AsYouTypeFormatter 的国家专用格式化
11.1 10 个国家的专用格式化函数
AsYouTypeFormatter 对 10 个主要国家提供了专用的 formatPartialXxx() 函数,其余国家使用通用的 formatPartialNumber():
| 国家 | 专用函数 | 格式特点 |
|---|---|---|
| CN | formatPartialChinese() |
3-4-4 分组 |
| US/CA | formatPartialNANP() |
(NPA) NXX-XXXX |
| GB | formatPartialUK() |
0XXXX XXXXXX |
| JP | formatPartialJapanese() |
0XX-XXXX-XXXX |
| DE | formatPartialGerman() |
0XXX XXXX XXXX |
| FR | formatPartialFrench() |
0X XX XX XX XX |
| AU | formatPartialAustralian() |
0XXX XXX XXX |
| IN | formatPartialIndian() |
XXXXX XXXXX |
| BR | formatPartialBrazilian() |
(XX) XXXXX-XXXX |
| RU | formatPartialRussian() |
8 XXX XXX-XX-XX |
11.2 国际模式 vs 国内模式
AsYouTypeFormatter 根据输入是否以 + 开头,选择不同的格式化路径:
inputDigit(digit: string): string {
if (digit === '+' && this.nationalNumber.length === 0) {
this.isInternational = true; // 国际模式
// ...
}
// ...
if (this.isInternational) {
// 国际模式:+区号 + 通用格式
this.currentOutput = '+' + this.countryCode + ' '
+ this.formatPartialNumber(this.nationalNumber);
} else {
// 国内模式:使用国家专用格式
this.currentOutput =
this.formatPartialNational(this.nationalNumber);
}
}
关键区别:
| 模式 | 触发条件 | 格式化函数 | CN 示例 |
|---|---|---|---|
| 国际 | 输入以 + 开头 |
formatPartialNumber() |
+86 131 2345 6789 |
| 国内 | 输入不以 + 开头 |
formatPartialChinese() |
131 2345 6789 |
重要发现:国际模式下,
format()使用的是 通用 的formatPartialNumber()(按 3-3-4 或 3-4-N 分组),而非国家专用函数。这意味着国际模式下所有国家的分组方式是统一的。
11.3 国内模式的中国号码格式化
private formatPartialChinese(number: string): string {
let len = number.length;
if (len <= 3) {
return number; // '131'
}
if (len <= 7) {
return number.substring(0, 3) + ' '
+ number.substring(3); // '131 2345'
}
return number.substring(0, 3) + ' '
+ number.substring(3, 7) + ' '
+ number.substring(7); // '131 2345 6789'
}
中国手机号的 3-4-4 分组:
输入: '13123456789'
len=3: '131'
len=4: '131 2'
len=7: '131 2345'
len=8: '131 2345 6'
len=11: '131 2345 6789'
11.4 国内模式的美国号码格式化
private formatPartialNANP(number: string): string {
let len = number.length;
if (len <= 3) {
return '(' + number; // '(201'
}
if (len <= 6) {
return '(' + number.substring(0, 3) + ') '
+ number.substring(3); // '(201) 555'
}
return '(' + number.substring(0, 3) + ') '
+ number.substring(3, 6) + '-'
+ number.substring(6); // '(201) 555-0123'
}
美国号码的 (NPA) NXX-XXXX 格式:
输入: '2015550123'
len=1: '(2'
len=3: '(201'
len=4: '(201) 5'
len=6: '(201) 555'
len=7: '(201) 555-0'
len=10: '(201) 555-0123'
11.5 国内模式的日本号码格式化
private formatPartialJapanese(number: string): string {
let len = number.length;
if (len <= 2) {
return '0' + number; // '090'
}
if (len <= 6) {
return '0' + number.substring(0, 2) + '-'
+ number.substring(2); // '090-1234'
}
return '0' + number.substring(0, 2) + '-'
+ number.substring(2, 6) + '-'
+ number.substring(6); // '090-1234-5678'
}
日本号码使用连字符分隔,且加上前导 0:
输入: '9012345678'
len=1: '09'
len=2: '090'
len=3: '090-1'
len=6: '090-1234'
len=7: '090-1234-5'
len=10: '090-1234-5678'
十二、format() 与 getFormattedParseResult() 的关系
12.1 getFormattedParseResult 的实现
getFormattedParseResult() 是一个更高级的 API,它内部组合了 parse() 和格式化逻辑:
Future<FormatPhoneResult?> getFormattedParseResult(
final String phoneNumber,
final CountryWithPhoneCode country, {
final PhoneNumberType phoneNumberType =
PhoneNumberType.mobile,
final PhoneNumberFormat phoneNumberFormat =
PhoneNumberFormat.international,
}) async {
try {
final res = await parse(
phoneNumber,
region: country.countryCode,
);
late final String formattedNumber;
if (phoneNumberFormat == PhoneNumberFormat.international) {
formattedNumber = res['international'] ?? '';
} else if (phoneNumberFormat == PhoneNumberFormat.national) {
formattedNumber = res['national'] ?? '';
} else {
formattedNumber = '';
}
return FormatPhoneResult(
e164: res['e164'] ?? '',
formattedNumber: formattedNumber,
);
} catch (e) {
return null;
}
}
12.2 format() vs getFormattedParseResult()
| 对比项 | format() | getFormattedParseResult() |
|---|---|---|
| 内部调用 | MethodChannel → handleFormat | MethodChannel → handleParse |
| 格式化引擎 | AsYouTypeFormatter | formatInternational/formatNational |
| 返回类型 | Map<String, String> |
FormatPhoneResult? |
| 返回字段 | formatted |
e164 + formattedNumber |
| 有效性验证 | 无 | 有(parse 内部验证) |
| 无效号码 | 返回部分格式化结果 | 返回 null |
| 适用场景 | 快速格式化 | 格式化 + 验证 |
12.3 三种格式化方式的选择指南
需要实时输入格式化?
└── YES → formatNumberSync()(同步,Dart 侧)
需要验证号码有效性?
└── YES → getFormattedParseResult()(异步,含验证)
只需要格式化结果?
└── YES → format()(异步,ArkTS 侧)
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| TextField 实时格式化 | formatNumberSync() |
同步,< 0.1ms |
| 手动点击格式化按钮 | format() |
异步,精度高 |
| 提交前验证 + 格式化 | getFormattedParseResult() |
一步完成验证和格式化 |
| 批量格式化号码列表 | formatNumberSync() |
同步,无通信开销 |
| 显示 e164 格式 | getFormattedParseResult() |
直接返回 e164 |
总结
本文完整追踪了 format() 异步格式化的 6 步调用链路。关键要点回顾:
format()是一个 跨平台异步调用,从 Dart 经 MethodChannel 到 ArkTS,再将结果回传- 参数通过
StandardMessageCodec编码为二进制字节流,在 Flutter Engine 内部传输 - ArkTS 侧使用
AsYouTypeFormatter进行 逐字符格式化,模拟用户逐个输入数字的过程 - 区号识别在输入前 1-3 个数字时完成,识别后切换到对应国家的格式化规则
- 返回值只有 1 个字段
formatted,经过 Map→Record 转换后通过result.success()回传 - 整个链路耗时约 3ms 以内,用户无感知,但不适合实时输入场景(应使用
formatNumberSync())
下一篇我们将分析 formatNumberSync() 同步格式化与 Mask 匹配原理——一个完全在 Dart 侧执行、无需跨平台通信的高性能格式化方案。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony 适配仓库:gitcode.com/oh-flutter/flutter_libphonenumber
- 开源鸿蒙跨平台社区:openharmonycrossplatform.csdn.net
- Flutter Platform Channels 官方文档:docs.flutter.dev - Platform channels
- Flutter MethodChannel API:api.flutter.dev - MethodChannel
- StandardMessageCodec:api.flutter.dev - StandardMessageCodec
- ArkTS 语言文档:developer.huawei.com - ArkTS
- Google libphonenumber:github.com/google/libphonenumber
- Flutter-OHOS 项目:gitee.com/openharmony-sig/flutter_flutter
- plugin_platform_interface:pub.dev/packages/plugin_platform_interface
更多推荐
所有评论(0)