Flutter三方库适配OpenHarmony【flutter_libphonenumber】——PhoneNumberUtil.ets 核心类的整体设计
本文深入解析了Flutter三方库适配OpenHarmony中的核心业务逻辑引擎——PhoneNumberUtil.ets。该文件是鸿蒙平台适配中最复杂的部分,完全用ArkTS从零实现,包含号码解析、格式化、验证等核心功能。文章详细介绍了其架构中的5个导出类:CountryData(国家原始数据)、PhoneNumber(解析后的号码对象)、RegionInfo(格式化区域信息)、PhoneNum
前言
欢迎来到 Flutter三方库适配OpenHarmony 系列文章!本系列围绕 flutter_libphonenumber 这个 电话号码处理库 的鸿蒙平台适配,进行全面深入的技术分享。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


上一篇我们深入分析了 FlutterLibphonenumberPlugin.ets 的消息分发实现。本篇将聚焦它背后的 业务逻辑引擎——PhoneNumberUtil.ets,这是鸿蒙平台适配中 代码量最大、逻辑最复杂 的文件。它承担了号码解析、格式化、类型判断、区域数据管理等所有核心功能。
与 Android 和 iOS 平台不同,鸿蒙平台没有现成的 libphonenumber 或 PhoneNumberKit 可用。
PhoneNumberUtil.ets是完全用 ArkTS 从零实现 的,这也是本次适配中最具挑战性的部分。
一、文件定位与职责
1.1 在插件架构中的位置
flutter_libphonenumber_ohos/
└── ohos/
└── src/main/ets/components/plugin/
├── FlutterLibphonenumberPlugin.ets ← 消息分发(上一篇)
└── PhoneNumberUtil.ets ← 本文主角(业务逻辑)
FlutterLibphonenumberPlugin.ets 是"前台接待",负责接收和分发消息;PhoneNumberUtil.ets 是"后台引擎",负责所有实际的号码处理逻辑。
1.2 核心职责
| 职责 | 说明 |
|---|---|
| 国家数据管理 | 存储和管理 57 个国家的电话号码规则 |
| 号码解析 | 将字符串解析为结构化的 PhoneNumber 对象 |
| 号码格式化 | 支持 National、International、E.164 三种格式 |
| 号码类型判断 | 区分 mobile、fixedLine、fixedOrMobile |
| 号码有效性验证 | 基于长度规则验证号码是否有效 |
| 区域信息聚合 | 生成包含 10 个字段的完整区域信息 |
| 逐字符格式化 | 通过 AsYouTypeFormatter 支持实时输入格式化 |
1.3 代码规模
| 指标 | 数值 |
|---|---|
| 总行数 | ~500 行 |
| 导出类 | 5 个 |
| 公开方法 | 15 个 |
| 私有方法 | 14 个 |
| 支持国家 | 57 个 |
二、5 个导出类概览
PhoneNumberUtil.ets 文件导出了 5 个类,各有明确的职责:
PhoneNumberUtil.ets
│
├── CountryData ← 国家原始数据(输入)
├── PhoneNumber ← 解析后的号码对象(中间产物)
├── RegionInfo ← 格式化后的区域信息(输出)
├── PhoneNumberUtil ← 核心工具类(处理引擎)
└── AsYouTypeFormatter ← 逐字符格式化器(特殊场景)
2.1 数据流向
CountryData(原始数据)
│
│ PhoneNumberUtil 处理
│
├── parse() → PhoneNumber(解析结果)
│ ├── formatNational()
│ ├── formatInternational()
│ ├── formatE164()
│ ├── getNumberType()
│ └── isValidNumber()
│
└── getAllRegionInfo() → RegionInfo(聚合输出)
└── 10 个字段 × 57 国 → MethodChannel → Dart
三、CountryData 类详解
3.1 类定义
export class CountryData {
name: string = ''; // 国家名称
code: string = ''; // 电话区号(不含+)
mobileExample: string = ''; // 手机号示例(纯数字)
fixedLineExample: string = ''; // 固话示例(纯数字)
mobilePattern: string = ''; // 手机号正则表达式
fixedLinePattern: string = ''; // 固话正则表达式
nationalPrefix: string = ''; // 国内拨号前缀
}
3.2 7 个字段的设计意图
| 字段 | 类型 | 示例(CN) | 用途 |
|---|---|---|---|
name |
string | 'China' |
UI 显示国家名称 |
code |
string | '86' |
国际区号匹配 |
mobileExample |
string | '13123456789' |
生成示例号码和 Mask |
fixedLineExample |
string | '1012345678' |
生成固话示例和 Mask |
mobilePattern |
string | '1[3-9]\\d{9}' |
手机号类型判断 |
fixedLinePattern |
string | '\\d{2,4}\\d{7,8}' |
固话类型判断 |
nationalPrefix |
string | '0' |
国内格式化时添加前缀 |
3.3 nationalPrefix 的特殊性
nationalPrefix 是各国在国内拨号时使用的前缀,不同国家差异很大:
| 国家 | nationalPrefix | 说明 |
|---|---|---|
| 中国 | '0' |
国内长途前缀(如 010、021) |
| 日本 | '0' |
国内前缀(如 090、03) |
| 英国 | '0' |
国内前缀(如 07400、0121) |
| 美国 | '' |
无前缀(直接拨 10 位号码) |
| 意大利 | '' |
无前缀(区号是号码的一部分) |
| 俄罗斯 | '8' |
国内长途前缀 |
| 匈牙利 | '06' |
两位数前缀 |
设计决策:
nationalPrefix为空字符串表示该国没有国内拨号前缀。在formatNational()中,前缀会被添加到格式化结果的开头。
四、PhoneNumber 类详解
4.1 类定义
export class PhoneNumber {
countryCode: number = 0; // 国家区号(数字)
nationalNumber: string = ''; // 国内号码(纯数字)
rawInput: string = ''; // 原始输入
constructor(
countryCode: number,
nationalNumber: string,
rawInput: string = ''
) {
this.countryCode = countryCode;
this.nationalNumber = nationalNumber;
this.rawInput = rawInput;
}
}
4.2 字段说明
| 字段 | 类型 | 示例 | 说明 |
|---|---|---|---|
countryCode |
number | 86 |
数字类型的国家区号 |
nationalNumber |
string | '13123456789' |
去掉区号后的国内号码 |
rawInput |
string | '+8613123456789' |
用户的原始输入 |
4.3 为什么 countryCode 是 number 而非 string
CountryData.code 是 string(如 '86'),而 PhoneNumber.countryCode 是 number(如 86)。这是因为:
- 存储时用 string — 方便字符串匹配和拼接
- 解析后用 number — 方便数值比较和 E.164 格式拼接
parseInt()在构造 PhoneNumber 时完成转换
五、RegionInfo 类详解
5.1 类定义
export class RegionInfo {
countryName: string = '';
phoneCode: string = '';
exampleNumberMobileNational: string = '';
exampleNumberFixedLineNational: string = '';
phoneMaskMobileNational: string = '';
phoneMaskFixedLineNational: string = '';
exampleNumberMobileInternational: string = '';
exampleNumberFixedLineInternational: string = '';
phoneMaskMobileInternational: string = '';
phoneMaskFixedLineInternational: string = '';
}
5.2 10 个字段的对称结构
RegionInfo 的 10 个字段(除 countryName 和 phoneCode 外的 8 个)按 2×2×2 的对称结构组织:
Mobile FixedLine
┌──────────────┐ ┌──────────────┐
National │ exampleNumber│ │ exampleNumber│
│ phoneMask │ │ phoneMask │
└──────────────┘ └──────────────┘
International │ exampleNumber│ │ exampleNumber│
│ phoneMask │ │ phoneMask │
└──────────────┘ └──────────────┘
5.3 RegionInfo 与 CountryData 的关系
RegionInfo 不是 CountryData 的简单映射,而是经过 格式化处理 后的产物:
| CountryData 字段 | 处理过程 | RegionInfo 字段 |
|---|---|---|
mobileExample |
formatNational() |
exampleNumberMobileNational |
mobileExample |
formatInternational() |
exampleNumberMobileInternational |
fixedLineExample |
formatNational() |
exampleNumberFixedLineNational |
fixedLineExample |
formatInternational() |
exampleNumberFixedLineInternational |
| 格式化结果 | maskNumber() |
phoneMask*(4 个) |
关键转换:
mobileExample是纯数字(如13123456789),经过formatNational()变成131 2345 6789,再经过maskNumber()变成000 0000 0000。
六、PhoneNumberUtil 核心类
6.1 单例模式实现
export class PhoneNumberUtil {
private static instance: PhoneNumberUtil | null = null;
private countryDataMap: Map<string, CountryData> = new Map();
private constructor() {
this.initCountryData();
}
static getInstance(): PhoneNumberUtil {
if (PhoneNumberUtil.instance === null) {
PhoneNumberUtil.instance = new PhoneNumberUtil();
}
return PhoneNumberUtil.instance;
}
}
这是经典的 懒汉式单例:
| 特征 | 实现 |
|---|---|
| 私有构造函数 | private constructor() 阻止外部创建 |
| 静态实例 | private static instance 存储唯一实例 |
| 懒加载 | 首次调用 getInstance() 时才创建 |
| 初始化 | 构造函数中调用 initCountryData() 加载数据 |
6.2 与 Dart 侧 CountryManager 的对比
| 对比项 | ArkTS PhoneNumberUtil | Dart CountryManager |
|---|---|---|
| 单例方式 | 懒汉式 getInstance() |
工厂构造 factory |
| 数据加载 | 构造函数中同步加载 | loadCountries() 异步加载 |
| 数据来源 | 硬编码在 initCountryData() |
从 ArkTS 侧通过 MethodChannel 获取 |
| 数据量 | 57 国 × 7 字段 = 399 个值 | 57 国 × 11 字段 = 627 个值 |
6.3 公开方法分类
PhoneNumberUtil 的 15 个公开方法分为 4 组:
第一组:数据查询方法(5 个)
getSupportedRegions(): string[]
getCountryCodeForRegion(region: string): number
getCountryNameForRegion(region: string): string
getExampleNumberForMobile(region: string): PhoneNumber | null
getExampleNumberForFixedLine(region: string): PhoneNumber | null
这组方法提供对 countryDataMap 的 只读访问,将内部数据转换为外部可用的格式。
第二组:格式化方法(5 个)
formatNational(phoneNumber: PhoneNumber, region: string): string
formatInternational(phoneNumber: PhoneNumber): string
formatE164(phoneNumber: PhoneNumber): string
maskNumber(phoneString: string): string
getAsYouTypeFormatter(region: string): AsYouTypeFormatter
这组方法将 PhoneNumber 对象转换为不同格式的字符串。
第三组:解析与验证方法(4 个)
parse(phoneString: string, defaultRegion: string): PhoneNumber | null
isValidNumber(phoneNumber: PhoneNumber): boolean
getNumberType(phoneNumber: PhoneNumber): string
getRegionCodeForNumber(phoneNumber: PhoneNumber): string
这组方法将字符串解析为 PhoneNumber 对象,并提供验证和类型判断。
第四组:聚合方法(1 个)
getAllRegionInfo(): Map<string, RegionInfo>
这是最重要的方法,它综合调用前三组方法,生成完整的区域信息供 Dart 侧使用。
七、格式化方法详解
7.1 formatInternational — 国际格式
formatInternational(phoneNumber: PhoneNumber): string {
let formatted = this.formatWithSpaces(phoneNumber.nationalNumber);
return '+' + phoneNumber.countryCode.toString() + ' ' + formatted;
}
逻辑简单直接:+区号 + 空格 + 带空格的国内号码。
示例:
PhoneNumber(86, '13123456789')
→ '+' + '86' + ' ' + '131 2345 6789'
→ '+86 131 2345 6789'
7.2 formatE164 — E.164 标准格式
formatE164(phoneNumber: PhoneNumber): string {
return '+' + phoneNumber.countryCode.toString()
+ phoneNumber.nationalNumber;
}
E.164 是最简洁的格式:+区号国内号码,无任何分隔符。
示例:
PhoneNumber(86, '13123456789')
→ '+8613123456789'
7.3 formatNational — 国内格式(核心复杂度所在)
formatNational(phoneNumber: PhoneNumber, region: string): string {
let data = this.countryDataMap.get(region);
if (data === undefined) {
return phoneNumber.nationalNumber;
}
return this.applyNationalFormat(
phoneNumber.nationalNumber, region, data.nationalPrefix);
}
formatNational 本身很简单,但它调用的 applyNationalFormat 是整个文件中 最复杂的部分——因为每个国家的国内格式都不同。
7.4 applyNationalFormat — 国家路由器
private applyNationalFormat(
number: string, region: string, prefix: string
): string {
if (region === 'CN') {
return this.formatChineseNumber(number);
} else if (region === 'US' || region === 'CA') {
return this.formatNANPNumber(number);
} else if (region === 'GB') {
return this.formatUKNumber(number, prefix);
} else if (region === 'JP') {
return this.formatJapaneseNumber(number, prefix);
} else if (region === 'DE') {
return this.formatGermanNumber(number, prefix);
} else if (region === 'FR') {
return this.formatFrenchNumber(number, prefix);
} else if (region === 'AU') {
return this.formatAustralianNumber(number, prefix);
} else if (region === 'BR') {
return this.formatBrazilianNumber(number, prefix);
} else if (region === 'IN') {
return this.formatIndianNumber(number, prefix);
} else if (region === 'RU') {
return this.formatRussianNumber(number, prefix);
}
return prefix + this.formatWithSpaces(number);
}
这是一个 策略路由器:根据国家代码选择对应的格式化策略。10 个主要国家有专用格式化函数,其余国家使用通用的 formatWithSpaces()。
7.5 10 个国家专用格式化函数
| 函数 | 适用国家 | 格式示例 |
|---|---|---|
formatChineseNumber() |
CN | 131 2345 6789 / 010 1234 5678 |
formatNANPNumber() |
US, CA | (201) 555-0123 |
formatUKNumber() |
GB | 07400 123456 |
formatJapaneseNumber() |
JP | 090-1234-5678 |
formatGermanNumber() |
DE | 0151 2345 6789 |
formatFrenchNumber() |
FR | 06 12 34 56 78 |
formatAustralianNumber() |
AU | 0412 345 678 |
formatBrazilianNumber() |
BR | 011 91234-5678 |
formatIndianNumber() |
IN | 081234 56789 |
formatRussianNumber() |
RU | 8 912 345-67-89 |
7.6 formatWithSpaces — 通用格式化
private formatWithSpaces(number: string): string {
let len = number.length;
if (len <= 4) return number;
if (len <= 7) return number.substring(0, 3) + ' '
+ number.substring(3);
if (len <= 10) return number.substring(0, 3) + ' '
+ number.substring(3, 6) + ' ' + number.substring(6);
return number.substring(0, 3) + ' '
+ number.substring(3, 7) + ' ' + number.substring(7);
}
对于没有专用格式化函数的国家,使用这个通用方法按 3-3-4 或 3-4-N 的模式添加空格。
7.7 maskNumber — Mask 生成
maskNumber(phoneNumber: string): string {
return phoneNumber.replace(/\d/g, '0');
}
将格式化后的号码中所有数字替换为 0,生成 Mask 模板:
'+86 131 2345 6789' → '+00 000 0000 0000'
'(201) 555-0123' → '(000) 000-0000'
八、解析方法详解
8.1 parse — 入口方法
parse(phoneString: string, defaultRegion: string):
PhoneNumber | null {
let cleanNumber = phoneString.replace(
/[\s\-\(\)\.]/g, '');
if (cleanNumber.charAt(0) === '+') {
return this.parseInternationalNumber(cleanNumber);
}
if (defaultRegion.length > 0) {
return this.parseNationalNumber(
cleanNumber, defaultRegion);
}
return null;
}
解析流程:
输入号码
│
├── 清理格式字符(空格、连字符、括号、点)
│
├── 以 '+' 开头?
│ ├── YES → parseInternationalNumber()
│ └── NO ↓
│
├── 有默认区域?
│ ├── YES → parseNationalNumber()
│ └── NO → return null
│
└── 无法解析
8.2 parseInternationalNumber — 国际号码解析
private parseInternationalNumber(
number: string
): PhoneNumber | null {
let withoutPlus = number.substring(1);
// 从 3 位到 1 位尝试匹配区号
for (let len = 3; len >= 1; len--) {
let possibleCode = withoutPlus.substring(0, len);
let region = this.getRegionForCountryCode(
parseInt(possibleCode));
if (region !== null) {
return new PhoneNumber(
parseInt(possibleCode),
withoutPlus.substring(len),
number
);
}
}
return null;
}
匹配策略:从长到短 尝试区号(3位→2位→1位),优先匹配更长的区号:
输入: '+8613123456789'
去掉+: '8613123456789'
尝试 3 位: '861' → 无匹配
尝试 2 位: '86' → 匹配 CN ✅
→ PhoneNumber(86, '13123456789', '+8613123456789')
8.3 parseNationalNumber — 国内号码解析
private parseNationalNumber(
number: string, region: string
): PhoneNumber | null {
let data = this.countryDataMap.get(region);
if (data === undefined) return null;
let nationalNumber = number;
// 去掉国内前缀
if (data.nationalPrefix.length > 0
&& number.indexOf(data.nationalPrefix) === 0) {
nationalNumber = number.substring(
data.nationalPrefix.length);
}
return new PhoneNumber(
parseInt(data.code), nationalNumber, number);
}
国内号码解析的关键是 去掉 nationalPrefix:
输入: '01012345678', region: 'CN'
nationalPrefix: '0'
去掉前缀: '1012345678'
→ PhoneNumber(86, '1012345678', '01012345678')
九、号码类型判断
9.1 getNumberType 方法
getNumberType() 根据国家和号码特征判断号码类型,返回 'mobile'、'fixedLine'、'fixedOrMobile' 或 'unknown'。
9.2 各国判断规则
| 国家 | 手机号特征 | 固话特征 |
|---|---|---|
| CN | 11位 + 首位为1 | 其他 |
| US/CA | — | — (返回 fixedOrMobile) |
| GB | 首位为7 | 其他 |
| JP | 首位为9/8/7 | 其他 |
| DE | 首位为1 | 其他 |
| FR | 首位为6或7 | 其他 |
| AU | 首位为4 | 其他 |
| IN | 首位为6-9 | 其他 |
| BR | 长度≥11 + 第3位为9 | 其他 |
| RU | 首位为9 | 其他 |
美国/加拿大特殊处理:NANP 体系下手机号和固话格式完全相同(都是 10 位),无法通过号码特征区分,因此返回
'fixedOrMobile'。
9.3 默认判断逻辑
对于没有专用规则的国家:
// 默认判断
if (number.length >= 10
&& number.charAt(0) >= '1'
&& number.charAt(0) <= '9') {
return 'mobile';
}
return 'unknown';
十、getAllRegionInfo — 聚合方法
10.1 完整实现
getAllRegionInfo(): Map<string, RegionInfo> {
let result = new Map<string, RegionInfo>();
this.countryDataMap.forEach(
(data: CountryData, region: string) => {
let mobileEx = this.getExampleNumberForMobile(region);
let fixedEx = this.getExampleNumberForFixedLine(region);
if (mobileEx !== null && fixedEx !== null) {
let info = new RegionInfo();
info.countryName = data.name;
info.phoneCode = data.code;
// National 格式
info.exampleNumberMobileNational =
this.formatNational(mobileEx, region);
info.exampleNumberFixedLineNational =
this.formatNational(fixedEx, region);
info.phoneMaskMobileNational =
this.maskNumber(info.exampleNumberMobileNational);
info.phoneMaskFixedLineNational =
this.maskNumber(info.exampleNumberFixedLineNational);
// International 格式
info.exampleNumberMobileInternational =
this.formatInternational(mobileEx);
info.exampleNumberFixedLineInternational =
this.formatInternational(fixedEx);
info.phoneMaskMobileInternational =
this.maskNumber(info.exampleNumberMobileInternational);
info.phoneMaskFixedLineInternational =
this.maskNumber(info.exampleNumberFixedLineInternational);
result.set(region, info);
}
}
);
return result;
}
10.2 数据生成流程
对于每个国家,getAllRegionInfo() 执行以下步骤:
CountryData('China', '86', '13123456789', '1012345678', ...)
│
├── getExampleNumberForMobile('CN')
│ → PhoneNumber(86, '13123456789')
│
├── getExampleNumberForFixedLine('CN')
│ → PhoneNumber(86, '1012345678')
│
├── formatNational(mobile, 'CN')
│ → '131 2345 6789'
│ → maskNumber() → '000 0000 0000'
│
├── formatNational(fixed, 'CN')
│ → '010 1234 5678'
│ → maskNumber() → '000 0000 0000'
│
├── formatInternational(mobile)
│ → '+86 131 2345 6789'
│ → maskNumber() → '+00 000 0000 0000'
│
└── formatInternational(fixed)
→ '+86 101 234 5678'
→ maskNumber() → '+00 000 000 0000'
10.3 方法调用次数
对于 57 个国家:
| 被调用方法 | 每国调用次数 | 总调用次数 |
|---|---|---|
getExampleNumberForMobile() |
1 | 57 |
getExampleNumberForFixedLine() |
1 | 57 |
formatNational() |
2 | 114 |
formatInternational() |
2 | 114 |
maskNumber() |
4 | 228 |
| 合计 | 10 | 570 |
十一、AsYouTypeFormatter 逐字符格式化器
11.1 类结构
export class AsYouTypeFormatter {
private region: string;
private currentOutput: string = '';
private nationalNumber: string = '';
private countryCode: string = '';
private isInternational: boolean = false;
private countryDataMap: Map<string, CountryData>;
constructor(region: string,
countryDataMap: Map<string, CountryData>) {
this.region = region;
this.countryDataMap = countryDataMap;
}
clear(): void { ... }
inputDigit(digit: string): string { ... }
}
11.2 状态管理
AsYouTypeFormatter 是一个 有状态 的格式化器,维护了 5 个内部状态:
| 状态 | 类型 | 说明 |
|---|---|---|
region |
string | 当前区域(可能在输入过程中更新) |
currentOutput |
string | 当前格式化输出 |
nationalNumber |
string | 已输入的国内号码部分 |
countryCode |
string | 已输入的区号部分 |
isInternational |
boolean | 是否为国际号码(以+开头) |
11.3 inputDigit 核心逻辑
inputDigit(digit)
│
├── digit === '+' 且首次输入?
│ └── isInternational = true, return '+'
│
├── 非数字?
│ └── 忽略,return currentOutput
│
├── 国际模式 + 区号未满3位?
│ └── 累积区号,尝试匹配国家
│
└── 累积 nationalNumber
├── 国际模式 → '+区号 ' + formatPartialNumber()
└── 国内模式 → formatPartialNational()
11.4 10 个国家专用的 Partial 格式化
与 PhoneNumberUtil 的完整格式化类似,AsYouTypeFormatter 也为 10 个主要国家提供了专用的 部分格式化 函数:
| 函数 | 格式化过程示例 |
|---|---|
formatPartialChinese() |
1 → 13 → 131 → 131 2 → 131 23 → … |
formatPartialNANP() |
(2 → (20 → (201) → (201) 5 → … |
formatPartialUK() |
07 → 074 → 0740 → 07400 → … |
formatPartialJapanese() |
09 → 090- → 090-1 → 090-12 → … |
11.5 与 PhoneNumberUtil 格式化的区别
| 对比项 | PhoneNumberUtil.format*() | AsYouTypeFormatter |
|---|---|---|
| 输入 | 完整号码 | 逐个字符 |
| 状态 | 无状态 | 有状态 |
| 调用方式 | 一次调用 | 多次调用 |
| 使用场景 | parse/getAllRegionInfo | handleFormat |
| 结果 | 最终格式 | 每步的中间格式 |
十二、与 Android/iOS 核心类的对比
12.1 三平台核心类对比
| 对比项 | Android | iOS | 鸿蒙 |
|---|---|---|---|
| 核心类 | PhoneNumberUtil |
PhoneNumberKit |
PhoneNumberUtil |
| 语言 | Java | Swift | ArkTS |
| 数据来源 | metadata (protobuf) | metadata (JSON) | 硬编码 |
| 国家数量 | 200+ | 200+ | 57 |
| 代码量 | ~10000 行 | ~5000 行 | ~500 行 |
| 实现方式 | Google 三方库 | 社区三方库 | 纯自研 |
12.2 鸿蒙平台的设计取舍
由于没有现成的三方库可用,鸿蒙平台的实现做了以下 设计取舍:
| 取舍 | 选择 | 原因 |
|---|---|---|
| 国家数量 | 57 国(非 200+) | 覆盖主要国家,避免代码膨胀 |
| 数据存储 | 硬编码(非 metadata 文件) | 简化实现,无需解析器 |
| 格式化精度 | 10 国专用 + 通用兜底 | 主要国家精确,其余可用 |
| 正则验证 | 简化规则 | 满足基本需求 |
| 号码类型 | 基于首位数字判断 | 简单有效 |
设计哲学:在鸿蒙平台上,目标不是 100% 复刻 libphonenumber 的全部功能,而是以 最小代码量 实现 最大覆盖度。57 个国家覆盖了全球 90% 以上的人口和电话流量。
总结
本文全面分析了 PhoneNumberUtil.ets 核心类的整体设计。关键要点回顾:
- 文件导出 5 个类:CountryData(原始数据)、PhoneNumber(解析结果)、RegionInfo(格式化输出)、PhoneNumberUtil(核心引擎)、AsYouTypeFormatter(逐字符格式化)
- PhoneNumberUtil 使用 懒汉式单例,构造时加载 57 国数据到
countryDataMap - 15 个公开方法 分为数据查询(5)、格式化(5)、解析验证(4)、聚合(1)四组
formatNational()通过applyNationalFormat()路由到 10 个国家专用格式化函数,其余国家使用通用formatWithSpaces()parse()支持国际号码(从长到短匹配区号)和国内号码(去掉 nationalPrefix)两种解析模式getAllRegionInfo()是最重要的聚合方法,为每个国家生成 10 个字段 的完整信息,总计调用 570 次内部方法- 与 Android/iOS 相比,鸿蒙平台以 ~500 行代码 实现了核心功能,是代码量最小但完全自研的实现
下一篇我们将深入分析 57 个国家格式化规则的数据结构设计,了解每个国家的 phoneCode、mask、exampleNumber 数据如何在 ArkTS 中组织和存储。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony 适配仓库:gitcode.com/oh-flutter/flutter_libphonenumber
- 开源鸿蒙跨平台社区:openharmonycrossplatform.csdn.net
- Google libphonenumber:github.com/google/libphonenumber
- PhoneNumberKit (iOS):github.com/marmelroy/PhoneNumberKit
- ArkTS 语言文档:developer.huawei.com - ArkTS
- Flutter 鸿蒙 SDK:@ohos/flutter_ohos
- E.164 国际电话号码标准:itu.int - E.164
- Flutter Platform Channels 官方文档:docs.flutter.dev - Platform channels
- Flutter-OHOS 项目:gitee.com/openharmony-sig/flutter_flutter
- plugin_platform_interface:pub.dev/packages/plugin_platform_interface
更多推荐



所有评论(0)