前言

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

在这里插入图片描述

前一篇我们分析了边界情况的容错处理策略。本篇将从 宏观架构 的角度,对 flutter_libphonenumber 在 Android、iOS、鸿蒙三个平台的技术实现进行全面对比。三个平台分别采用了完全不同的技术路线:Android 依赖 Google 官方的 libphonenumber Java 库,iOS 使用社区维护的 PhoneNumberKit Swift 库,而鸿蒙平台则采用 纯 ArkTS 手写实现。这三种路线各有优劣,本篇将从依赖库、语言特性、API 设计、数据流、性能表现等多个维度进行深入对比。


一、三平台技术栈总览

1.1 核心依赖对比

维度 🤖 Android 🍎 iOS 🔷 鸿蒙 (OHOS)
原生语言 Kotlin Swift ArkTS
核心依赖库 google/libphonenumber PhoneNumberKit 无(纯手写)
库维护方 Google 官方 社区(marmelroy) 自研
数据来源 Google 元数据 Apple 元数据 手写 46 国数据
包体积影响 较大(含完整元数据) 中等 最小(仅含所需数据)
支持国家数 240+ 240+ 46

1.2 Dart 侧实现对比

// Android 平台 Dart 侧
const _channel = MethodChannel('com.bottlepay/flutter_libphonenumber_android');

class FlutterLibphonenumberAndroid extends FlutterLibphonenumberPlatform {
  static void registerWith() {
    FlutterLibphonenumberPlatform.instance = FlutterLibphonenumberAndroid();
  }
}

// iOS 平台 Dart 侧
const _channel = MethodChannel('com.bottlepay/flutter_libphonenumber_ios');

class FlutterLibphonenumberIOS extends FlutterLibphonenumberPlatform {
  static void registerWith() {
    FlutterLibphonenumberPlatform.instance = FlutterLibphonenumberIOS();
  }
}

// 鸿蒙平台 Dart 侧
const _channel = MethodChannel('com.bottlepay/flutter_libphonenumber_ohos');

class FlutterLibphonenumberOhos extends FlutterLibphonenumberPlatform {
  static void registerWith() {
    FlutterLibphonenumberPlatform.instance = FlutterLibphonenumberOhos();
  }
}

三个平台的 Dart 侧代码结构完全一致,都继承自 FlutterLibphonenumberPlatform,通过 registerWith() 注册为平台实例。差异完全在原生层。

1.3 联合插件架构中的位置

flutter_libphonenumber(主包,面向开发者)
  ├── flutter_libphonenumber_platform_interface(平台接口层)
  │     ├── FlutterLibphonenumberPlatform(抽象类)
  │     ├── CountryWithPhoneCode(数据模型)
  │     ├── CountryManager(国家管理器)
  │     └── LibPhonenumberTextFormatter(输入格式化器)
  ├── flutter_libphonenumber_android(Android 实现)
  │     ├── Dart: FlutterLibphonenumberAndroid
  │     └── Kotlin: FlutterLibphonenumberPlugin + google/libphonenumber
  ├── flutter_libphonenumber_ios(iOS 实现)
  │     ├── Dart: FlutterLibphonenumberIOS
  │     └── Swift: SwiftFlutterLibphonenumberIosPlugin + PhoneNumberKit
  └── flutter_libphonenumber_ohos(鸿蒙实现)
        ├── Dart: FlutterLibphonenumberOhos
        └── ArkTS: FlutterLibphonenumberPlugin + PhoneNumberUtil(纯手写)

二、原生层插件注册机制对比

2.1 Android 插件注册(Kotlin)

// FlutterLibphonenumberPlugin.kt
public class FlutterLibphonenumberPlugin : FlutterPlugin, MethodCallHandler {
  private lateinit var channel: MethodChannel

  override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(
      flutterPluginBinding.getFlutterEngine().getDartExecutor(),
      "com.bottlepay/flutter_libphonenumber_android"
    )
    channel.setMethodCallHandler(this)
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }
}

2.2 iOS 插件注册(Swift)

// SwiftFlutterLibphonenumberIosPlugin.swift
public class SwiftFlutterLibphonenumberPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(
      name: "com.bottlepay/flutter_libphonenumber_ios",
      binaryMessenger: registrar.messenger()
    )
    let instance = SwiftFlutterLibphonenumberPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }
}

2.3 鸿蒙插件注册(ArkTS)

// FlutterLibphonenumberPlugin.ets
export default class FlutterLibphonenumberPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
    this.channel.setMethodCallHandler(this);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel !== null) {
      this.channel.setMethodCallHandler(null);
      this.channel = null;
    }
  }
}

Android 和鸿蒙的注册模式非常相似,都使用 onAttachedToEngine / onDetachedFromEngine 生命周期。iOS 使用 register(with:) 静态方法,风格更偏 Objective-C 传统。

2.4 注册机制对比表

维度 Android (Kotlin) iOS (Swift) 鸿蒙 (ArkTS)
注册方式 onAttachedToEngine register(with:) onAttachedToEngine
注销方式 onDetachedFromEngine 自动管理 onDetachedFromEngine
接口实现 FlutterPlugin, MethodCallHandler NSObject, FlutterPlugin FlutterPlugin, MethodCallHandler
Channel 名称 com.bottlepay/...android com.bottlepay/...ios com.bottlepay/...ohos
线程模型 主线程 + Handler 主线程 + DispatchQueue 主线程

三、消息分发机制对比

3.1 三平台 onMethodCall 实现

三个平台都处理相同的三个方法:formatparseget_all_supported_regions

// Android: 使用 when 表达式
override fun onMethodCall(call: MethodCall, result: Result) {
  when (call.method) {
    "get_all_supported_regions" -> getAllSupportedRegions(result)
    "parse" -> parse(call, result)
    "format" -> format(call, result)
    else -> result.notImplemented()
  }
}
// iOS: 使用 switch-case
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  switch(call.method) {
    case "parse": parse(call, result: result)
    case "format": format(call, result: result)
    case "get_all_supported_regions": getAllSupportedRegions(result: result)
    default: result(FlutterMethodNotImplemented)
  }
}
// 鸿蒙: 使用 if-else 链
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();
  }
}

3.2 分发模式对比

特性 Android iOS 鸿蒙
分发语法 when 表达式 switch-case if-else
方法命名 直接方法名 直接方法名 handle 前缀
错误处理 result.notImplemented() result(FlutterMethodNotImplemented) result.notImplemented()
结果回调 Result 接口 FlutterResult 闭包 MethodResult 接口

四、format() 实现对比

4.1 Android format() — 依赖 libphonenumber

private fun format(call: MethodCall, result: Result) {
  val region = call.argument<String>("region")
  val phone = call.argument<String>("phone")
  try {
    val util = PhoneNumberUtil.getInstance()
    val formatter = util.getAsYouTypeFormatter(region)
    var formatted = ""
    formatter.clear()
    for (character in phone.toCharArray()) {
      formatted = formatter.inputDigit(character)
    }
    val res = HashMap<String, String>()
    res["formatted"] = formatted
    result.success(res)
  } catch (exception: Exception) {
    result.error("InvalidNumber", "Number $phone is invalid", null)
  }
}

Android 使用 Google libphonenumber 的 AsYouTypeFormatter,逐字符输入实现渐进式格式化。这是最成熟的实现,支持所有 240+ 国家。

4.2 iOS format() — 依赖 PhoneNumberKit

private func format(_ call: FlutterMethodCall, result: FlutterResult) {
  guard
    let arguments = call.arguments as? [String : Any],
    let number = arguments["phone"] as? String,
    let region = arguments["region"] as? String
  else {
    result(FlutterError(code: "InvalidArgument",
                        message: "The 'phone' argument is missing.",
                        details: nil))
    return
  }
  let formatted = PartialFormatter(defaultRegion: region).formatPartial(number)
  let res: [String: String] = ["formatted": formatted]
  result(res)
}

iOS 使用 PhoneNumberKit 的 PartialFormatter,一行代码完成格式化。代码最简洁,但依赖第三方库。

4.3 鸿蒙 format() — 纯 ArkTS 手写

private handleFormat(call: MethodCall, result: MethodResult): void {
  let phone = call.argument('phone') as string;
  let region = call.argument('region') as string;
  try {
    let useRegion: string = region !== null ? region : 'CN';
    let formatter = this.phoneUtil.getAsYouTypeFormatter(useRegion);
    let formatted: string = '';
    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);
  }
}

鸿蒙的实现模式与 Android 最为接近(逐字符输入),但底层的 PhoneNumberUtil 是完全手写的 ArkTS 代码,不依赖任何第三方库。

关键差异:Android 和 iOS 的格式化依赖成熟的第三方库,自动支持所有国家;鸿蒙需要为每个国家手写格式化规则,工作量大但完全可控。


五、parse() 实现对比

5.1 Android parse() — 完整的号码解析

private fun parseStringAndRegion(
  string: String, region: String?, util: PhoneNumberUtil
): HashMap<String, String>? {
  return try {
    val phoneNumber = util.parse(string, region)
    if (!util.isValidNumber(phoneNumber)) {
      null
    } else object : HashMap<String, String>() {
      init {
        val type = util.getNumberType(phoneNumber)
        put("type", numberTypeToString(type))
        put("e164", util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164))
        put("international", util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL))
        put("national", util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.NATIONAL))
        put("country_code", phoneNumber.countryCode.toString())
        put("region_code", util.getRegionCodeForNumber(phoneNumber))
        put("national_number", phoneNumber.nationalNumber.toString())
      }
    }
  } catch (e: NumberParseException) { null }
}

5.2 iOS parse() — Swift 风格的解析

private func parse(phone: String, region: String?) -> [String: String]? {
  do {
    var phoneNumber: PhoneNumber
    if let region = region {
      phoneNumber = try kit.parse(phone, withRegion: region)
    } else {
      phoneNumber = try kit.parse(phone)
    }
    return [
      "type": phoneNumber.type.toString(),
      "e164": kit.format(phoneNumber, toType: .e164),
      "international": kit.format(phoneNumber, toType: .international, withPrefix: true),
      "national": kit.format(phoneNumber, toType: .national),
      "country_code": String(phoneNumber.countryCode),
      "region_code": String(kit.getRegionCode(of: phoneNumber) ?? ""),
      "national_number": String(phoneNumber.nationalNumber)
    ]
  } catch { return nil }
}

5.3 鸿蒙 parse() — ArkTS 手写解析

private handleParse(call: MethodCall, result: MethodResult): void {
  let phone = call.argument('phone') as string;
  let region = call.argument('region') as string;
  try {
    let phoneNumber: PhoneNumber | null = this.phoneUtil.parse(phone, useRegion);
    if (phoneNumber === null || !this.phoneUtil.isValidNumber(phoneNumber)) {
      result.error('InvalidNumber', 'Number ' + phone + ' is invalid', null);
      return;
    }
    let response: Map<string, string> = new Map();
    response.set('type', this.phoneUtil.getNumberType(phoneNumber));
    response.set('e164', this.phoneUtil.formatE164(phoneNumber));
    response.set('international', this.phoneUtil.formatInternational(phoneNumber));
    response.set('national', this.phoneUtil.formatNational(phoneNumber, regionCode));
    response.set('country_code', phoneNumber.countryCode.toString());
    response.set('region_code', regionCode);
    response.set('national_number', phoneNumber.nationalNumber);
    result.success(this.convertMapToRecord(response));
  } catch (e) {
    result.error('PARSE_ERROR', 'Failed to parse phone number', null);
  }
}

5.4 parse() 返回字段对比

返回字段 Android iOS 鸿蒙
type numberTypeToString() phoneNumber.type.toString() getNumberType()
e164 PhoneNumberFormat.E164 .e164 formatE164()
international PhoneNumberFormat.INTERNATIONAL .international formatInternational()
national PhoneNumberFormat.NATIONAL .national formatNational()
country_code phoneNumber.countryCode phoneNumber.countryCode phoneNumber.countryCode
region_code getRegionCodeForNumber() getRegionCode(of:) getRegionCodeForNumber()
national_number phoneNumber.nationalNumber phoneNumber.nationalNumber phoneNumber.nationalNumber

三个平台返回的字段完全一致,保证了 Dart 侧消费数据时的一致性体验。


六、getAllSupportedRegions() 实现对比

6.1 线程模型差异

三个平台在获取全量国家数据时,都需要处理耗时操作:

// Android: 使用 Thread + Handler 切回主线程
private fun getAllSupportedRegions(result: Result) {
  Thread(Runnable {
    val regionsMap = mutableMapOf<String, MutableMap<String, String>>()
    for (region in PhoneNumberUtil.getInstance().supportedRegions) {
      // ... 构建数据
    }
    Handler(Looper.getMainLooper()).post(Runnable {
      result.success(regionsMap)
    })
  }).start()
}
// iOS: 使用 DispatchQueue 异步执行
private func getAllSupportedRegions(result: @escaping FlutterResult) {
  dispQueue.async {
    var regionsMap: [String: [String: String]] = [:]
    self.kit.allCountries().forEach { (regionCode) in
      // ... 构建数据
    }
    DispatchQueue.main.async {
      result(regionsMap)
    }
  }
}
// 鸿蒙: 同步执行(数据量小,46 国)
private handleGetAllSupportedRegions(result: MethodResult): void {
  try {
    let regionsInfo: Map<string, RegionInfo> = this.phoneUtil.getAllRegionInfo();
    // ... 构建数据
    result.success(this.convertRegionsMapToRecord(regionsMap));
  } catch (e) {
    result.error('REGIONS_ERROR', 'Failed to get supported regions', null);
  }
}

6.2 线程模型对比表

维度 Android iOS 鸿蒙
异步方式 Thread + Handler DispatchQueue 同步执行
回主线程 Looper.getMainLooper() DispatchQueue.main 不需要
数据量 240+ 国家 240+ 国家 46 国家
耗时 较长(需异步) 较长(需异步) 极短(可同步)
阻塞风险 低(已异步) 低(已异步) 低(数据量小)

Android 和 iOS 因为需要遍历 240+ 国家并生成示例号码,必须在后台线程执行。鸿蒙仅 46 国,数据量小到可以同步完成。


七、数据返回格式对比

7.1 Map 到 Dart 的序列化差异

三个平台在将数据返回给 Dart 时,使用了不同的序列化方式:

// Android: 直接使用 HashMap
val res = HashMap<String, String>()
res["formatted"] = formatted
result.success(res)
// iOS: 使用 Swift Dictionary
let res: [String: String] = ["formatted": formatted]
result(res)
// 鸿蒙: 需要 Map -> Record 转换
let response: Map<string, string> = new Map();
response.set('formatted', formatted);
result.success(this.convertMapToRecord(response));

7.2 鸿蒙特有的 Map-Record 转换

鸿蒙平台有一个独特的技术细节:ArkTS 的 Map 类型不能直接通过 MethodChannel 传递,需要转换为 Record 类型:

private convertMapToRecord(map: Map<string, string>): Record<string, string> {
  let record: Record<string, string> = {} as Record<string, string>;
  map.forEach((value: string, key: string) => {
    record[key] = value;
  });
  return record;
}

这是 ArkTS 与 Kotlin/Swift 的一个重要差异。Kotlin 的 HashMap 和 Swift 的 Dictionary 可以直接被 Flutter 引擎序列化,而 ArkTS 的 Map 需要额外的转换步骤。

序列化方式 Android iOS 鸿蒙
原生类型 HashMap<String, String> [String: String] Map<string, string>
直接传递 ✅ 可以 ✅ 可以 ❌ 不可以
需要转换 是(→ Record
嵌套处理 自动 自动 需要递归转换

八、号码类型检测对比

8.1 类型枚举映射

三个平台都需要将原生的号码类型枚举转换为统一的字符串:

// Android: PhoneNumberType 枚举 → 字符串
private fun numberTypeToString(type: PhoneNumberType): String {
  return when (type) {
    PhoneNumberType.FIXED_LINE -> "fixedLine"
    PhoneNumberType.MOBILE -> "mobile"
    PhoneNumberType.FIXED_LINE_OR_MOBILE -> "fixedOrMobile"
    PhoneNumberType.TOLL_FREE -> "tollFree"
    PhoneNumberType.PREMIUM_RATE -> "premiumRate"
    PhoneNumberType.SHARED_COST -> "sharedCost"
    PhoneNumberType.VOIP -> "voip"
    PhoneNumberType.PERSONAL_NUMBER -> "personalNumber"
    PhoneNumberType.PAGER -> "pager"
    PhoneNumberType.UAN -> "uan"
    PhoneNumberType.VOICEMAIL -> "voicemail"
    PhoneNumberType.UNKNOWN -> "unknown"
    else -> "notParsed"
  }
}
// iOS: PhoneNumberType 扩展
extension PhoneNumberType {
  func toString() -> String {
    switch self {
      case .fixedLine: return "fixedLine"
      case .mobile: return "mobile"
      case .fixedOrMobile: return "fixedOrMobile"
      case .tollFree: return "tollFree"
      case .premiumRate: return "premiumRate"
      // ... 其他类型
    }
  }
}
// 鸿蒙: 直接返回字符串(手写判断逻辑)
getNumberType(phoneNumber: PhoneNumber): string {
  // 基于号码长度和前缀的正则匹配
  // 返回 "mobile" | "fixedLine" | "fixedOrMobile" | "unknown"
}

8.2 类型检测能力对比

号码类型 Android iOS 鸿蒙
mobile
fixedLine
fixedOrMobile
tollFree
premiumRate
sharedCost
voip
personalNumber
pager
uan
voicemail

鸿蒙平台仅支持 mobilefixedLinefixedOrMobile 三种核心类型,这覆盖了 99% 的实际使用场景。特殊类型(tollFree、voip 等)在鸿蒙上返回 unknown


九、错误处理策略对比

9.1 错误返回方式

// Android: FlutterError 三参数
result.error("InvalidNumber", "Number $phone is invalid", null)
result.error("InvalidParameters", "Invalid 'phone' parameter.", null)
// iOS: FlutterError 三参数
result(FlutterError(code: "InvalidArgument",
                    message: "The 'phone' argument is missing.",
                    details: nil))
result(FlutterError(code: "InvalidNumber",
                    message: "Failed to parse phone number string '\(phone)'.",
                    details: nil))
// 鸿蒙: MethodResult error 三参数
result.error('InvalidParameters', "Invalid 'phone' parameter.", null);
result.error('InvalidNumber', 'Number ' + phone + ' is invalid', null);
result.error('FORMAT_ERROR', 'Failed to format phone number', null);

9.2 错误码对比

错误场景 Android iOS 鸿蒙
参数缺失 InvalidParameters InvalidArgument InvalidParameters
号码无效 InvalidNumber InvalidNumber InvalidNumber
格式化失败 InvalidNumber 不会失败 FORMAT_ERROR
解析失败 返回 null 返回 nil PARSE_ERROR
未实现方法 result.notImplemented() FlutterMethodNotImplemented result.notImplemented()

鸿蒙平台的错误码更加细化(区分了 FORMAT_ERRORPARSE_ERROR),而 Android 统一使用 InvalidNumber


十、依赖库深度对比

10.1 Android — google/libphonenumber

  1. Google 官方维护,更新频率高(每月更新元数据)
  2. 基于 Java 实现,包含完整的全球电话号码元数据
  3. 支持 240+ 国家和地区
  4. 包体积较大(约 2.5MB 元数据)
  5. 提供 AsYouTypeFormatter 渐进式格式化

核心优势:

  • 数据最全面、最准确
  • Google 官方维护,可靠性高
  • 社区生态成熟

10.2 iOS — PhoneNumberKit

  1. 社区维护的 Swift 库(marmelroy/PhoneNumberKit)
  2. 基于 Google libphonenumber 的 Swift 移植
  3. 支持 240+ 国家和地区
  4. 纯 Swift 实现,与 iOS 生态集成良好
  5. 提供 PartialFormatter 部分格式化

核心优势:

  • Swift 原生,API 设计优雅
  • 与 iOS 生态无缝集成
  • CocoaPods/SPM 包管理支持

10.3 鸿蒙 — 纯 ArkTS 手写

  1. 完全自研,无第三方依赖
  2. 手写 46 个国家的格式化规则
  3. 包含 PhoneNumberUtil 核心类和数据定义
  4. 包体积最小(仅包含所需数据)
  5. 完全可控,可按需扩展

核心优势:

  • 零依赖,包体积最小
  • 完全可控,可定制化
  • 无第三方库兼容性风险

十一、构建配置对比

11.1 Android 构建配置(build.gradle)

// android/build.gradle
dependencies {
    implementation 'com.googlecode.libphonenumber:libphonenumber:8.13.6'
}

android {
    compileSdkVersion 33
    defaultConfig {
        minSdkVersion 16
    }
}

11.2 iOS 构建配置(podspec)

# flutter_libphonenumber_ios.podspec
Pod::Spec.new do |s|
  s.name             = 'flutter_libphonenumber_ios'
  s.dependency 'Flutter'
  s.dependency 'PhoneNumberKit', '~> 3.7'
  s.platform         = :ios, '12.0'
  s.swift_version    = '5.0'
end

11.3 鸿蒙构建配置(oh-package.json5)

{
  "name": "flutter_libphonenumber_ohos",
  "version": "1.0.0",
  "description": "flutter_libphonenumber ohos plugin",
  "main": "index.ets",
  "dependencies": {
    "@ohos/flutter_ohos": "file:./oh_modules/@ohos/flutter_ohos"
  }
}

11.4 构建配置对比表

维度 Android iOS 鸿蒙
构建工具 Gradle CocoaPods hvigor
配置文件 build.gradle .podspec oh-package.json5
第三方依赖 libphonenumber 8.13.6 PhoneNumberKit ~> 3.7
最低版本 SDK 16 iOS 12.0 API 20
语言版本 Kotlin 1.7+ Swift 5.0 ArkTS (ES2021)

十二、Dart 侧实现一致性分析

12.1 四个核心方法的 Dart 实现

三个平台的 Dart 侧代码几乎完全相同,仅 MethodChannel 名称不同:

// 三个平台共享的方法签名(来自 FlutterLibphonenumberPlatform)
abstract class FlutterLibphonenumberPlatform {
  Future<void> init({Map<String, CountryWithPhoneCode> overrides = const {}});
  Future<Map<String, String>> format(String phone, String region);
  Future<Map<String, dynamic>> parse(String phone, {String? region});
  Future<Map<String, CountryWithPhoneCode>> getAllSupportedRegions();
  String formatNumberSync(String phoneNumber, {required CountryWithPhoneCode country, ...});
}

12.2 init() 实现完全一致

// 三个平台的 init() 实现完全相同

Future<void> init({
  final Map<String, CountryWithPhoneCode> overrides = const {},
}) async {
  return CountryManager().loadCountries(
    phoneCodesMap: await getAllSupportedRegions(),
    overrides: overrides,
  );
}

init() 方法在三个平台上的实现完全一致:调用 getAllSupportedRegions() 获取原生数据,然后通过 CountryManager 加载到内存。差异完全封装在原生层的 getAllSupportedRegions() 实现中。

12.3 formatNumberSync() — 纯 Dart 共享实现

formatNumberSync() 是定义在 FlutterLibphonenumberPlatform 基类中的纯 Dart 方法,三个平台共享同一份代码:

// platform_interface 中的共享实现
String formatNumberSync(
  String phoneNumber, {
  required CountryWithPhoneCode country,
  PhoneNumberType phoneNumberType = PhoneNumberType.mobile,
  PhoneNumberFormat phoneNumberFormat = PhoneNumberFormat.international,
  bool inputContainsCountryCode = true,
}) {
  // 纯 Dart 侧 mask 匹配算法
  // 不经过 MethodChannel,不调用原生代码
}

这意味着同步格式化在三个平台上的行为完全一致,因为它根本不涉及原生层。


十三、性能特性对比

13.1 初始化性能

指标 Android iOS 鸿蒙
国家数据量 240+ 国家 240+ 国家 46 国家
init() 耗时 200-500ms 150-400ms 50-100ms
内存占用 较高 中等 最低
首次格式化延迟 极低

13.2 格式化性能

指标 Android iOS 鸿蒙
format() 异步 1-3ms 1-3ms 1-2ms
formatNumberSync() <1ms <1ms <1ms
parse() 解析 2-5ms 2-5ms 1-3ms
MethodChannel 开销 标准 标准 标准

13.3 包体积影响

  1. Android:libphonenumber 库约增加 2.5MB(含元数据)
  2. iOS:PhoneNumberKit 约增加 1.5MB
  3. 鸿蒙:纯 ArkTS 代码约增加 50KB

鸿蒙平台的包体积优势非常明显,仅为 Android 的 2%。这是纯手写实现的最大优势之一。


十四、可维护性与扩展性对比

14.1 新增国家支持的工作量

步骤 Android iOS 鸿蒙
更新依赖库 升级 libphonenumber 版本 升级 PhoneNumberKit 版本 不适用
手写格式化规则 不需要 不需要 需要
手写解析规则 不需要 不需要 需要
手写类型判断 不需要 不需要 需要
测试验证 简单 简单 需要逐国验证
总工作量 极低 极低 较高

14.2 Bug 修复的响应速度

  1. Android:等待 Google 发布新版本(通常每月更新)
  2. iOS:等待 PhoneNumberKit 社区修复(不确定时间)
  3. 鸿蒙:自行修复,即时生效

14.3 定制化能力

能力 Android iOS 鸿蒙
自定义格式化规则 ❌ 受限于库 ❌ 受限于库 ✅ 完全可控
自定义号码类型 ❌ 受限于枚举 ❌ 受限于枚举 ✅ 可自由扩展
自定义验证逻辑 ❌ 受限于库 ❌ 受限于库 ✅ 完全可控
移除不需要的国家 ❌ 无法精简 ❌ 无法精简 ✅ 按需包含

十五、开发体验对比

15.1 开发环境

维度 Android iOS 鸿蒙
IDE Android Studio Xcode DevEco Studio
调试工具 Logcat Console HiLog
热重载 ✅ 支持 ✅ 支持 ✅ 支持
模拟器 Android Emulator iOS Simulator OHOS Previewer
真机调试 USB/WiFi USB USB

15.2 语言特性对比

// Kotlin: 空安全 + 扩展函数 + when 表达式
val phone = call.argument<String>("phone") ?: ""
val type = when (phoneType) {
    PhoneNumberType.MOBILE -> "mobile"
    else -> "unknown"
}
// Swift: Optional + guard let + switch
guard let phone = arguments["phone"] as? String else {
    result(FlutterError(code: "Error", message: "Missing phone", details: nil))
    return
}
// ArkTS: TypeScript 风格 + 严格类型
let phone = call.argument('phone') as string;
if (phone === null || phone.length === 0) {
    result.error('Error', 'Missing phone', null);
    return;
}

15.3 语言特性对比表

特性 Kotlin Swift ArkTS
空安全 ? / !! ? / ! | null
类型推断 val / var let / var let / const
模式匹配 when switch if-else
扩展函数
协程/异步 ✅ Coroutines ✅ async/await ✅ Promise
泛型

十六、三平台技术路线总结

16.1 综合评分

维度 Android iOS 鸿蒙 说明
国家覆盖 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 鸿蒙 46 国 vs 240+
包体积 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 鸿蒙最小
初始化速度 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 鸿蒙最快
可定制性 ⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐ 鸿蒙完全可控
维护成本 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ 鸿蒙需手动维护
数据准确性 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ 鸿蒙手写可能有偏差
类型检测 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 鸿蒙仅核心类型

16.2 适用场景建议

选择 Android 路线(依赖 libphonenumber)的场景

  • 需要支持全球 240+ 国家
  • 对号码类型检测要求高(tollFree、voip 等)
  • 希望自动跟随 Google 元数据更新

选择 iOS 路线(依赖 PhoneNumberKit)的场景

  • iOS 平台开发
  • 追求 Swift 原生 API 体验
  • 需要 CocoaPods/SPM 生态集成

选择鸿蒙路线(纯 ArkTS 手写)的场景

  • 鸿蒙平台开发(唯一选择)
  • 对包体积有严格要求
  • 需要高度定制化的格式化规则
  • 仅需支持有限数量的国家

十七、未来演进方向

17.1 鸿蒙平台的改进空间

  1. 扩展国家支持:从 46 国逐步扩展到 100+
  2. 引入元数据文件:将格式化规则从代码中抽离为 JSON 配置
  3. 增加号码类型:支持 tollFree、voip 等特殊类型
  4. 性能优化:引入缓存机制减少重复解析
  5. 自动化测试:建立与 libphonenumber 的对比测试套件

17.2 跨平台一致性改进

  • 统一错误码命名规范
  • 统一号码类型枚举
  • 建立跨平台回归测试
  • 考虑引入 FFI 方案替代 MethodChannel

三个平台的技术路线各有千秋。Android 和 iOS 依赖成熟的第三方库,开发效率高但灵活性受限;鸿蒙采用纯手写实现,工作量大但完全可控。这种差异正是 联合插件架构 的价值所在——通过统一的平台接口层,将平台差异完全封装在原生层,让上层开发者无感知地使用一致的 API。


总结

本篇从插件注册、消息分发、format/parse/getAllSupportedRegions 三大 API、数据序列化、错误处理、依赖库、构建配置、性能、可维护性等多个维度,全面对比了 Android(Kotlin + libphonenumber)、iOS(Swift + PhoneNumberKit)、鸿蒙(ArkTS 纯手写)三个平台的技术实现。三个平台在 Dart 侧保持了高度一致的接口,差异完全封装在原生层。

下一篇(也是本系列最后一篇)将进行总结与展望,讨论如何扩展更多国家支持与功能增强方向。

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


相关资源

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐