Flutter三方库适配OpenHarmony【apple_product_name】OhosProductName类使用详解

前言

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

OhosProductName 类是apple_product_name库在OpenHarmony平台的核心API类 ,封装了所有与设备名称查询相关的功能。本文将从类的定义、设计模式、方法签名、返回值处理、异常机制等多个维度对该类进行全面解析,帮助开发者不仅学会如何使用这些API,更能理解其背后的设计理念和实现原理。

本文是apple_product_name OpenHarmony适配系列的第4篇,共30篇,深入剖析OhosProductName类的完整API设计。

一、类的定义与导入

1.1 导入方式

dart 复制代码
import 'package:apple_product_name/apple_product_name_ohos.dart';

OhosProductName类定义在apple_product_name_ohos.dart文件中,这是OpenHarmony平台专用的API入口。注意与Apple设备的导入路径区分:

平台 导入路径 核心类
OpenHarmony apple_product_name_ohos.dart OhosProductName
iOS/macOS apple_product_name.dart AppleProductName

1.2 完整源码

以下是lib/apple_product_name_ohos.dart完整真实源码

dart 复制代码
/// HarmonyOS platform support for apple_product_name.
library apple_product_name_ohos;

import 'package:flutter/services.dart';

/// 鸿蒙平台设备名称获取工具类
class OhosProductName {
  static const MethodChannel _channel = MethodChannel('apple_product_name');

  static final _instance = OhosProductName._();
  OhosProductName._();

  /// Returns the singleton instance of [OhosProductName].
  factory OhosProductName() => _instance;

  /// 获取设备型号标识符,例如: "ALN-AL00"
  Future<String> getMachineId() async {
    final String? machineId = await _channel.invokeMethod('getMachineId');
    return machineId ?? 'Unknown';
  }

  /// 获取设备产品名称,例如: "HUAWEI Mate 60 Pro"
  Future<String> getProductName() async {
    final String? productName = await _channel.invokeMethod('getProductName');
    return productName ?? 'Unknown';
  }

  /// 根据型号标识符查找产品名称
  Future<String> lookup(String machineId) async {
    final String? productName = await _channel.invokeMethod('lookup', {
      'machineId': machineId,
    });
    return productName ?? machineId;
  }

  /// 返回产品名称,如果未找到则返回 null
  Future<String?> lookupOrNull(String machineId) async {
    final String? productName = await _channel.invokeMethod('lookup', {
      'machineId': machineId,
    });
    return productName;
  }
}

整个文件只有一个类,代码非常简洁。四个公开方法 + 一个单例构造,总共不到40行有效代码,但覆盖了设备名称查询的所有场景。

二、单例模式设计

2.1 单例实现

dart 复制代码
class OhosProductName {
  static final _instance = OhosProductName._();
  OhosProductName._();
  factory OhosProductName() => _instance;
}

该类采用Dart语言中经典的单例模式设计,通过三个关键要素实现:

  1. 私有命名构造函数 OhosProductName._():阻止外部直接创建实例
  2. 静态final实例 _instance:类加载时创建唯一实例,不可替换
  3. 工厂构造函数 factory OhosProductName():始终返回同一个实例

2.2 单例模式的优势

优势 说明
内存高效 整个应用生命周期只存在一个实例
通道唯一 保证MethodChannel通信通道的一致性
使用简单 无需手动管理实例的创建和销毁
线程安全 Dart单线程模型下天然线程安全

设计理念 :无论在应用的任何位置调用OhosProductName(),返回的都是同一个实例,内部的MethodChannel也始终是同一个通道。

2.3 与Apple平台的对比

Apple平台的AppleProductName类也采用了相同的单例模式:

dart 复制代码
class AppleProductName {
  static final _instance = AppleProductName._();
  AppleProductName._();
  factory AppleProductName() => _instance;

  String lookup(String machineId) => _lookup(machineId) ?? machineId;
  String? lookupOrNull(String machineId) => _lookup(machineId);
}

两者的关键区别:

  • AppleProductNamelookup是同步方法,映射表在Dart层(自动生成的.g.dart文件)
  • OhosProductNamelookup是异步方法,映射表在ArkTS原生层

三、MethodChannel通信机制

3.1 通道声明

dart 复制代码
static const MethodChannel _channel = MethodChannel('apple_product_name');

MethodChannel是Flutter插件体系中最核心的跨平台通信机制 。通道名称'apple_product_name'是Dart侧和原生侧的唯一匹配标识,两端必须完全一致。

3.2 原生侧通道创建

AppleProductNamePlugin.ets中,原生侧创建了同名通道:

typescript 复制代码
onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "apple_product_name");
  this.channel.setMethodCallHandler(this);
}

通道名称"apple_product_name"与Dart侧完全一致,这是通信能够建立的前提。setMethodCallHandler(this)将当前插件实例注册为消息处理器。

3.3 通信流程

复制代码
Dart层调用 _channel.invokeMethod('getProductName')
  → Flutter引擎序列化方法名和参数
  → 通过BinaryMessenger传递到原生层
  → 原生层MethodChannel接收消息
  → onMethodCall() 路由到对应方法
  → 执行原生逻辑,调用result.success()
  → 结果序列化返回Dart层
  → Future<String> 完成

整个通信过程是异步的,通常在毫秒级完成。

四、getMachineId方法详解

4.1 方法签名与实现

dart 复制代码
/// 获取设备型号标识符,例如: "ALN-AL00"
Future<String> getMachineId() async {
  final String? machineId = await _channel.invokeMethod('getMachineId');
  return machineId ?? 'Unknown';
}

getMachineId()用于获取当前设备的原始型号标识符 。返回值类型为Future<String>,是异步操作。内部对null值做了安全处理,返回'Unknown'兜底。

4.2 原生侧实现

typescript 复制代码
private getMachineId(result: MethodResult): void {
  try {
    result.success(deviceInfo.productModel);
  } catch (e) {
    const errorMsg = e instanceof Error ? e.message : String(e);
    result.error("GET_MACHINE_ID_ERROR", errorMsg, null);
  }
}

原生侧直接调用deviceInfo.productModel系统API获取型号标识符。try-catch确保异常时返回结构化错误而非崩溃。

4.3 返回值示例

设备 getMachineId()返回值
HUAWEI Mate 70 "CFR-AN00"
HUAWEI Mate 60 Pro "ALN-AL00"
HUAWEI Pura 70 Ultra "HBK-AL00"
Honor Magic6 Pro "PGT-AN00"
HUAWEI MatePad Pro 13.2 "GROK-W09"

五、getProductName方法详解

5.1 方法签名与实现

dart 复制代码
/// 获取设备产品名称,例如: "HUAWEI Mate 60 Pro"
Future<String> getProductName() async {
  final String? productName = await _channel.invokeMethod('getProductName');
  return productName ?? 'Unknown';
}

getProductName()是该类中最常用的方法,直接返回当前设备的用户友好产品名称。

5.2 原生侧两级查找策略

typescript 复制代码
private getProductName(result: MethodResult): void {
  try {
    const model = deviceInfo.productModel;
    // 先从映射表查找
    let productName = HUAWEI_DEVICE_MAP[model];
    // 如果映射表没有,使用系统的 marketName
    if (!productName) {
      productName = deviceInfo.marketName || model;
    }
    result.success(productName);
  } catch (e) {
    const errorMsg = e instanceof Error ? e.message : String(e);
    result.error("GET_PRODUCT_NAME_ERROR", errorMsg, null);
  }
}

这是一个三级降级策略

  1. 首先在HUAWEI_DEVICE_MAP映射表中查找(最精确)
  2. 映射表未收录时,使用系统deviceInfo.marketName(次精确)
  3. marketName也不可用时,返回原始型号model(兜底)

设计亮点:这种降级策略确保即使遇到映射表尚未收录的新设备,也不会返回空值,用户始终能看到有意义的设备信息。

5.3 getProductName vs getMachineId

对比项 getProductName() getMachineId()
返回值 "HUAWEI Mate 60 Pro" "ALN-AL00"
可读性 高,用户友好 低,技术标识
数据来源 映射表 + marketName + model deviceInfo.productModel
典型场景 UI展示、关于页面 日志记录、问题排查

上图展示了两个方法返回值在应用界面中的对比效果。

六、lookup方法详解

6.1 方法签名与实现

dart 复制代码
/// 根据型号标识符查找产品名称
Future<String> lookup(String machineId) async {
  final String? productName = await _channel.invokeMethod('lookup', {
    'machineId': machineId,
  });
  return productName ?? machineId;
}

lookup()允许通过任意型号标识符查询对应的产品名称,不限于当前设备。参数通过Map传递到原生层。

6.2 原生侧实现

typescript 复制代码
private lookup(call: MethodCall, result: MethodResult): void {
  try {
    const machineId = call.argument("machineId") as string;
    if (!machineId) {
      result.error("INVALID_ARGUMENT", "machineId is required", null);
      return;
    }
    const productName = HUAWEI_DEVICE_MAP[machineId];
    result.success(productName); // 如果没找到返回 null
  } catch (e) {
    const errorMsg = e instanceof Error ? e.message : String(e);
    result.error("LOOKUP_ERROR", errorMsg, null);
  }
}

原生侧的lookup方法有参数校验 :如果machineId为空,直接返回INVALID_ARGUMENT错误。查找逻辑只在映射表中查找,不使用marketName降级(因为查询的不一定是当前设备)。

6.3 使用示例

dart 复制代码
// 查询已知型号
final name1 = await OhosProductName().lookup('ALN-AL00');
print(name1);  // "HUAWEI Mate 60 Pro"

// 查询未知型号 → 返回原始machineId
final name2 = await OhosProductName().lookup('XXX-XX00');
print(name2);  // "XXX-XX00"

// 批量查询
final models = ['CFR-AN00', 'HBN-AL00', 'PGT-AN00'];
for (final model in models) {
  final name = await OhosProductName().lookup(model);
  print('$model → $name');
}
// CFR-AN00 → HUAWEI Mate 70
// HBN-AL00 → HUAWEI Pura 70
// PGT-AN00 → Honor Magic6 Pro

注意lookup()未找到时返回原始machineId而非"Unknown",这是与getProductName()的重要区别。

七、lookupOrNull方法详解

7.1 方法签名与实现

dart 复制代码
/// 返回产品名称,如果未找到则返回 null
Future<String?> lookupOrNull(String machineId) async {
  final String? productName = await _channel.invokeMethod('lookup', {
    'machineId': machineId,
  });
  return productName;
}

lookupOrNull()lookup()的变体,返回类型为Future<String?>,未找到时返回null而非原始machineId。

7.2 lookup vs lookupOrNull

dart 复制代码
// lookup: 未找到时返回原始machineId
final name1 = await OhosProductName().lookup('UNKNOWN-MODEL');
print(name1);       // "UNKNOWN-MODEL"
print(name1 == null); // false

// lookupOrNull: 未找到时返回null
final name2 = await OhosProductName().lookupOrNull('UNKNOWN-MODEL');
print(name2);       // null
print(name2 == null); // true

两者在原生侧调用的是同一个方法,区别仅在Dart层的null处理:

对比项 lookup() lookupOrNull()
返回类型 Future<String> Future<String?>
未找到时 返回原始machineId 返回null
是否可空 永远非空 可能为空
适用场景 UI展示(保证有值) 逻辑判断(区分已知/未知)

7.3 典型应用场景

dart 复制代码
// 场景:筛选映射表未收录的设备
Future<List<String>> findUnknownDevices(List<String> models) async {
  final unknown = <String>[];
  final ohos = OhosProductName();
  for (final model in models) {
    final name = await ohos.lookupOrNull(model);
    if (name == null) {
      unknown.add(model);
    }
  }
  return unknown;
}

// 使用
final unknowns = await findUnknownDevices(['ALN-AL00', 'XXX-XX00', 'YYY-YY00']);
print('未收录型号: $unknowns');  // ['XXX-XX00', 'YYY-YY00']

lookupOrNull在需要区分"已知设备"和"未知设备"的场景中非常有用,比如统计映射表覆盖率、上报未收录型号等。

八、四个方法完整对比

8.1 API速查表

dart 复制代码
final ohos = OhosProductName();

// 1. 获取当前设备产品名称
final productName = await ohos.getProductName();    // "HUAWEI Mate 60 Pro"

// 2. 获取当前设备型号标识符
final machineId = await ohos.getMachineId();         // "ALN-AL00"

// 3. 查询任意型号(未找到返回原始id)
final name = await ohos.lookup('ALN-AL00');          // "HUAWEI Mate 60 Pro"

// 4. 查询任意型号(未找到返回null)
final nameOrNull = await ohos.lookupOrNull('ALN-AL00'); // "HUAWEI Mate 60 Pro"

8.2 完整对比表

方法 参数 返回类型 未找到时 数据来源 场景
getProductName() Future<String> "Unknown" 映射表→marketName→model UI展示
getMachineId() Future<String> "Unknown" deviceInfo.productModel 日志记录
lookup(id) machineId Future<String> 原始id 映射表 批量转换
lookupOrNull(id) machineId Future<String?> null 映射表 覆盖率检测

8.3 选择决策树

选择哪个方法,可以按以下逻辑判断:

  1. 需要当前设备的友好名称?→ 用getProductName()
  2. 需要当前设备的原始型号?→ 用getMachineId()
  3. 需要查询其他型号且保证非空?→ 用lookup()
  4. 需要判断型号是否在映射表中?→ 用lookupOrNull()

九、原生侧方法路由

9.1 onMethodCall路由逻辑

以下是AppleProductNamePlugin.ets中的方法路由真实源码

typescript 复制代码
onMethodCall(call: MethodCall, result: MethodResult): void {
  switch (call.method) {
    case "getMachineId":
      this.getMachineId(result);
      break;
    case "getProductName":
      this.getProductName(result);
      break;
    case "lookup":
      this.lookup(call, result);
      break;
    default:
      result.notImplemented();
      break;
  }
}

onMethodCall通过switch语句将Dart层的方法调用路由到对应的原生实现。call.method是Dart层invokeMethod的第一个参数(方法名字符串)。未识别的方法名会调用result.notImplemented()

9.2 方法名映射关系

Dart层调用 call.method值 原生层方法
_channel.invokeMethod('getMachineId') "getMachineId" this.getMachineId(result)
_channel.invokeMethod('getProductName') "getProductName" this.getProductName(result)
_channel.invokeMethod('lookup', {...}) "lookup" this.lookup(call, result)

关键点lookup方法需要传递参数,所以原生侧方法签名多了一个call参数,用于通过call.argument("machineId")提取传入的型号标识符。

十、异常处理与错误码

10.1 推荐的异常处理模式

dart 复制代码
import 'package:flutter/services.dart';
import 'package:apple_product_name/apple_product_name_ohos.dart';

Future<String> safeGetProductName() async {
  try {
    return await OhosProductName().getProductName();
  } on PlatformException catch (e) {
    print('平台异常: ${e.code} - ${e.message}');
    return 'Unknown Device';
  } on MissingPluginException {
    print('插件未注册,请检查GeneratedPluginRegistrant');
    return 'Unknown Device';
  } catch (e) {
    print('未知异常: $e');
    return 'Unknown Device';
  }
}

推荐的异常处理层级:

  1. PlatformException :原生侧通过result.error()返回的结构化异常
  2. MissingPluginException:插件未注册,MethodChannel找不到处理器
  3. 通用catch:兜底处理所有其他异常

10.2 错误码一览

错误码 触发方法 含义
GET_MACHINE_ID_ERROR getMachineId 获取型号标识符失败
GET_PRODUCT_NAME_ERROR getProductName 获取产品名称失败
LOOKUP_ERROR lookup 查询映射表失败
INVALID_ARGUMENT lookup machineId参数为空

10.3 MissingPluginException排查

如果遇到MissingPluginException,按以下步骤排查:

  • 检查GeneratedPluginRegistrant.ets是否导入了AppleProductNamePlugin
  • 检查EntryAbility.ets是否调用了registerWith
  • 确认oh-package.json5中的依赖配置正确
  • 执行flutter clean后重新构建

十一、完整使用示例

11.1 综合展示页面

dart 复制代码
import 'package:flutter/material.dart';
import 'package:apple_product_name/apple_product_name_ohos.dart';

class OhosProductNameDemo extends StatefulWidget {
  @override
  _OhosProductNameDemoState createState() => _OhosProductNameDemoState();
}

class _OhosProductNameDemoState extends State<OhosProductNameDemo> {
  final _ohos = OhosProductName();
  String _productName = '加载中...';
  String _machineId = '加载中...';
  String _lookupResult = '加载中...';
  String? _lookupOrNullResult;

  @override
  void initState() {
    super.initState();
    _loadAll();
  }

  Future<void> _loadAll() async {
    try {
      final productName = await _ohos.getProductName();
      final machineId = await _ohos.getMachineId();
      final lookupResult = await _ohos.lookup('CFR-AN00');
      final lookupOrNullResult = await _ohos.lookupOrNull('XXX-XX00');

      setState(() {
        _productName = productName;
        _machineId = machineId;
        _lookupResult = lookupResult;
        _lookupOrNullResult = lookupOrNullResult;
      });
    } catch (e) {
      setState(() {
        _productName = '获取失败: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('OhosProductName 详解')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildInfoRow('getProductName()', _productName),
            _buildInfoRow('getMachineId()', _machineId),
            _buildInfoRow('lookup("CFR-AN00")', _lookupResult),
            _buildInfoRow('lookupOrNull("XXX-XX00")',
                _lookupOrNullResult ?? 'null'),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('$label: ', style: const TextStyle(fontWeight: FontWeight.bold)),
          Expanded(child: Text(value)),
        ],
      ),
    );
  }
}

这个页面综合展示了四个方法的调用结果,使用_buildInfoRow辅助方法统一构建每行的布局,代码更加整洁。

11.2 预期运行结果

方法调用 预期返回值
getProductName() 当前设备的友好名称(如"HUAWEI Mate 60 Pro")
getMachineId() 当前设备的型号标识符(如"ALN-AL00")
lookup("CFR-AN00") "HUAWEI Mate 70"
lookupOrNull("XXX-XX00") null(映射表未收录)

十二、设备映射表覆盖范围

12.1 映射表代码片段

typescript 复制代码
const HUAWEI_DEVICE_MAP: Record<string, string> = {
  // Mate 70 系列
  "CFR-AN00": "HUAWEI Mate 70",
  "CFS-AN00": "HUAWEI Mate 70 Pro",
  "CFT-AN00": "HUAWEI Mate 70 Pro+",
  "CFU-AN00": "HUAWEI Mate 70 RS 非凡大师",
  // Mate 60 系列
  "BRA-AL00": "HUAWEI Mate 60",
  "ALN-AL00": "HUAWEI Mate 60 Pro",
  "GGK-AL10": "HUAWEI Mate 60 Pro+",
  // Pura 70 系列
  "HBN-AL00": "HUAWEI Pura 70",
  "DUA-AL00": "HUAWEI Pura 70 Pro",
  "HBK-AL00": "HUAWEI Pura 70 Ultra",
  // 荣耀系列
  "PGT-AN00": "Honor Magic6 Pro",
  "BVL-AN00": "Honor Magic6",
  // ... 共90+个型号映射
};

12.2 设备系列覆盖统计

设备系列 代表型号 映射数量 说明
Mate系列 Mate 70/60/X5/X3 22个 旗舰手机+折叠屏
Pura系列 Pura 70/P60 12个 影像旗舰
nova系列 nova 13/12/11 14个 年轻时尚系列
Pocket系列 Pocket 2/S 4个 小折叠屏
MatePad系列 MatePad Pro/Air 10个 平板设备
荣耀Magic Magic6/5 8个 荣耀旗舰
荣耀数字 Honor 200/100 8个 荣耀中端
华为手表 WATCH GT/Ultimate 12个 智能手表

映射表会随新设备发布持续更新,社区开发者可以通过提交Pull Request贡献新设备数据。

十三、高级用法

13.1 缓存优化

由于每次调用都会经过MethodChannel通信,频繁调用可能影响性能。建议在应用启动时缓存结果:

dart 复制代码
class DeviceInfoCache {
  static String? _productName;
  static String? _machineId;

  static Future<String> getProductName() async {
    _productName ??= await OhosProductName().getProductName();
    return _productName!;
  }

  static Future<String> getMachineId() async {
    _machineId ??= await OhosProductName().getMachineId();
    return _machineId!;
  }
}

// 使用:多次调用只会触发一次MethodChannel通信
final name1 = await DeviceInfoCache.getProductName(); // 第一次:走MethodChannel
final name2 = await DeviceInfoCache.getProductName(); // 第二次:直接返回缓存

??=运算符确保只在首次调用时执行异步操作,后续调用直接返回缓存值。

13.2 与FutureBuilder结合

dart 复制代码
FutureBuilder<String>(
  future: OhosProductName().getProductName(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }
    if (snapshot.hasError) {
      return Text('错误: ${snapshot.error}');
    }
    return Text(snapshot.data ?? 'Unknown');
  },
)

FutureBuilder自动管理异步状态,适合简单的数据展示场景。

总结

OhosProductName 类通过单例模式 确保资源高效利用,通过MethodChannel 实现可靠的跨平台通信,通过多级降级策略 保证返回值的可用性。四个核心方法(getProductNamegetMachineIdlookuplookupOrNull)覆盖了从UI展示到数据分析的所有场景,API设计简洁而完备。

下一篇文章将深入介绍设备型号标识符转换原理,解析映射表的数据结构和查找算法。

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


相关资源:

相关推荐
lqj_本人1 小时前
Flutter三方库适配OpenHarmony【apple_product_name】异步调用与错误处理
flutter
哈__2 小时前
基础入门 Flutter for OpenHarmony:animations 动画组件详解
flutter
不爱吃糖的程序媛2 小时前
Flutter-OH标准化适配流程
flutter
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】MethodChannel通信机制详解
flutter
无巧不成书02182 小时前
Flutter-OH 概述与未来发展全景分析
flutter
钛态3 小时前
Flutter for OpenHarmony 实战:animated_text_kit 灵动文字动效与教育场景交互
flutter·交互·harmonyos
哈__3 小时前
基础入门 Flutter for OpenHarmony:video_player 视频播放组件详解
flutter·音视频
SoaringHeart3 小时前
Flutter 顶部滚动行为限制实现:NoTopOverScrollPhysics
前端·flutter
哈__3 小时前
基础入门 Flutter for OpenHarmony:two_dimensional_scrollables 二维滚动详解
flutter