前言

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

在这里插入图片描述

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


一、三平台技术栈总览

1.1 核心依赖对比

维度🤖 Android🍎 iOS🔷 鸿蒙 (OHOS)
原生语言KotlinSwiftArkTS
核心依赖库google/libphonenumberPhoneNumberKit无(纯手写)
库维护方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)
注册方式onAttachedToEngineregister(with:)onAttachedToEngine
注销方式onDetachedFromEngine自动管理onDetachedFromEngine
接口实现FlutterPlugin, MethodCallHandlerNSObject, FlutterPluginFlutterPlugin, MethodCallHandler
Channel 名称com.bottlepay/...androidcom.bottlepay/...ioscom.bottlepay/...ohos
线程模型主线程 + Handler主线程 + DispatchQueue主线程

三、消息分发机制对比

3.1 三平台 onMethodCall 实现

三个平台都处理相同的三个方法:format、parse、get_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 分发模式对比

特性AndroidiOS鸿蒙
分发语法when 表达式switch-caseif-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() 返回字段对比

返回字段AndroidiOS鸿蒙
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 线程模型对比表

维度AndroidiOS鸿蒙
异步方式Thread + HandlerDispatchQueue同步执行
回主线程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 需要额外的转换步骤。

序列化方式AndroidiOS鸿蒙
原生类型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 类型检测能力对比

号码类型AndroidiOS鸿蒙
mobile✅✅✅
fixedLine✅✅✅
fixedOrMobile✅✅✅
tollFree✅✅❌
premiumRate✅✅❌
sharedCost✅✅❌
voip✅✅❌
personalNumber✅✅❌
pager✅✅❌
uan✅✅❌
voicemail✅✅❌

鸿蒙平台仅支持 mobile、fixedLine、fixedOrMobile 三种核心类型,这覆盖了 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 错误码对比

错误场景AndroidiOS鸿蒙
参数缺失InvalidParametersInvalidArgumentInvalidParameters
号码无效InvalidNumberInvalidNumberInvalidNumber
格式化失败InvalidNumber不会失败FORMAT_ERROR
解析失败返回 null返回 nilPARSE_ERROR
未实现方法result.notImplemented()FlutterMethodNotImplementedresult.notImplemented()

鸿蒙平台的错误码更加细化(区分了 FORMAT_ERROR 和 PARSE_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 构建配置对比表

维度AndroidiOS鸿蒙
构建工具GradleCocoaPodshvigor
配置文件build.gradle.podspecoh-package.json5
第三方依赖libphonenumber 8.13.6PhoneNumberKit ~> 3.7无
最低版本SDK 16iOS 12.0API 20
语言版本Kotlin 1.7+Swift 5.0ArkTS (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 初始化性能

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

13.2 格式化性能

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

13.3 包体积影响

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

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


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

14.1 新增国家支持的工作量

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

14.2 Bug 修复的响应速度

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

14.3 定制化能力

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

十五、开发体验对比

15.1 开发环境

维度AndroidiOS鸿蒙
IDEAndroid StudioXcodeDevEco Studio
调试工具LogcatConsoleHiLog
热重载✅ 支持✅ 支持✅ 支持
模拟器Android EmulatoriOS SimulatorOHOS Previewer
真机调试USB/WiFiUSBUSB

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 语言特性对比表

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

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

16.1 综合评分

维度AndroidiOS鸿蒙说明
国家覆盖⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐鸿蒙 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

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐