Flutter三方库适配OpenHarmony【apple_product_name】deviceInfo系统API调用

前言

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

deviceInfo 是 OpenHarmony 系统提供的设备信息 API ,apple_product_name 库通过它获取设备的型号标识符和市场名称。作为 @kit.BasicServicesKit 基础服务工具包的重要组成部分,deviceInfo 模块为应用开发者提供了一套标准化的接口来访问设备硬件和系统信息,无需申请任何权限即可直接使用。本文将详细分析该 API 的每一个属性、在插件中的实际调用方式、三级降级策略的实现原理,以及性能优化和异常处理的最佳实践。

先给出结论式摘要:

  • apple_product_name 使用 2 个属性 :productModel(型号标识符)和 marketName(市场名称),构成三级降级的前两级数据源
  • 基础属性无需权限:productModel、marketName、brand、deviceType 等基础属性开箱即用
  • 属性值在设备生命周期内不变:型号、品牌等硬件信息不会因系统升级或用户设置而改变,天然适合缓存

提示:本文所有源码来源于 apple_product_name 库的 AppleProductNamePlugin.ets 文件,建议对照源码阅读。

目录

  1. [deviceInfo 模块导入](#deviceInfo 模块导入)
  2. [BasicServicesKit 工具包概览](#BasicServicesKit 工具包概览)
  3. [productModel 属性详解](#productModel 属性详解)
  4. [marketName 属性详解](#marketName 属性详解)
  5. [brand 与 manufacturer 属性](#brand 与 manufacturer 属性)
  6. [deviceType 设备类型属性](#deviceType 设备类型属性)
  7. 系统版本信息属性
  8. [deviceInfo 完整属性一览](#deviceInfo 完整属性一览)
  9. [getMachineId 中的 API 调用](#getMachineId 中的 API 调用)
  10. [getProductName 三级降级实现](#getProductName 三级降级实现)
  11. 降级策略的设计哲学
  12. 权限模型与安全性
  13. 异常处理与防御性编程
  14. 性能特征与缓存策略
  15. 设备类型判断实战
  16. 获取完整设备信息
  17. [与 iOS deviceInfo 的对比](#与 iOS deviceInfo 的对比)
  18. [Dart 侧设备信息验证](#Dart 侧设备信息验证)
  19. 常见问题与排查
  20. 总结

一、deviceInfo 模块导入

1.1 导入语句

typescript 复制代码
import { deviceInfo } from '@kit.BasicServicesKit';

1.2 导入方式说明

使用 ES 模块的命名导入 语法,从 @kit.BasicServicesKit 包中精确提取 deviceInfo 对象。这种导入方式的优势:

  • 只引入需要的模块,避免加载不必要的代码
  • 编译器可以进行 tree-shaking 优化
  • 导入后直接通过 deviceInfo.属性名 访问,无需额外初始化

1.3 与旧版导入方式的对比

导入方式 语法 API 版本 推荐度
Kit 导入(新) import { deviceInfo } from '@kit.BasicServicesKit' API 9+ ★★★★★
模块导入(旧) import deviceInfo from '@ohos.deviceInfo' API 6+ ★★★☆☆

apple_product_name 使用新版 Kit 导入方式,兼容 API 9 及以上版本。

提示:@kit.BasicServicesKit 是 OpenHarmony 的基础服务工具包,除了 deviceInfo 外还包含电池状态、系统设置等多种基础能力。详见 OpenHarmony 设备信息 API 文档。

二、BasicServicesKit 工具包概览

2.1 工具包内容

@kit.BasicServicesKit 包含了 OpenHarmony 系统的多种基础服务模块:

模块 功能 apple_product_name 是否使用
deviceInfo 设备硬件信息 ✓ 核心依赖
batteryInfo 电池状态信息 ✗
systemParameter 系统参数 ✗
settings 系统设置 ✗
power 电源管理 ✗

2.2 apple_product_name 的最小依赖

apple_product_name 只使用了 deviceInfo 一个模块,且只使用了其中的 2 个属性(productModel 和 marketName),体现了最小依赖原则。

2.3 导入后的使用方式

typescript 复制代码
import { deviceInfo } from '@kit.BasicServicesKit';

// 直接访问属性,无需初始化
const model = deviceInfo.productModel;   // "ALN-AL00"
const name = deviceInfo.marketName;      // "HUAWEI Mate 60 Pro"
const brand = deviceInfo.brand;          // "HUAWEI"

注意:deviceInfo 的属性是同步访问的,不需要 async/await 或回调,属性值在系统启动时就已经确定并缓存在内存中。

三、productModel 属性详解

3.1 属性定义

typescript 复制代码
// 获取设备型号标识符
const model: string = deviceInfo.productModel;
// 示例: "ALN-AL00"、"CFR-AN00"、"HBN-AL00"

3.2 返回值特征

特征 说明
类型 string
格式 通常为 "XXX-XXXX"(三字母前缀 + 连字符 + 四字符后缀)
唯一性 同一设备始终返回相同值
稳定性 不会因系统升级或用户设置而改变
可空性 理论上不为空,但防御性编程仍需考虑

3.3 型号标识符结构

复制代码
ALN-AL00
 │    │
 │    └── 后缀编码
 │        AL = 全网通(国内)
 │        AN = 全网通(另一编码)
 │        LX = 国际版
 │        W  = WiFi 版本
 │        00/10/80 = 版本号
 │
 └─────── 产品代号
          ALN = Mate 60 Pro
          CFR = Mate 70
          HBN = Pura 70

3.4 在 apple_product_name 中的使用

productModel 是插件的核心数据来源,被两个方法使用:

typescript 复制代码
// getMachineId --- 直接返回 productModel
private getMachineId(result: MethodResult): void {
  result.success(deviceInfo.productModel);
}

// getProductName --- 用 productModel 作为映射表的查询键
private getProductName(result: MethodResult): void {
  const model = deviceInfo.productModel;
  let productName = HUAWEI_DEVICE_MAP[model]; // 用 model 查映射表
  // ...
}

提示:productModel 是设备在系统层面的唯一标识,由制造商在硬件设计阶段确定。同一款产品可能有多个 productModel(对应不同网络制式),但同一个 productModel 只对应一款产品。

四、marketName 属性详解

4.1 属性定义

typescript 复制代码
// 获取市场名称
const name: string = deviceInfo.marketName;
// 示例: "HUAWEI Mate 60 Pro"、"Honor Magic6 Pro"

4.2 返回值特征

特征 说明
类型 string
格式 用户友好的产品名称
来源 设备厂商在系统固件中预设
可空性 可能为空字符串或 undefined
稳定性 可能随系统更新而变化

4.3 与 productModel 的对比

维度 productModel marketName
示例 "ALN-AL00" "HUAWEI Mate 60 Pro"
可读性 低(内部编码) 高(用户友好)
稳定性 极高(不变) 中(可能变化)
可空性 不为空 可能为空
唯一性 高(一对一) 低(多对一)
用途 设备识别、映射表键 用户展示、降级方案

4.4 在三级降级中的角色

marketName 是三级降级策略的第二级数据源:

typescript 复制代码
const model = deviceInfo.productModel;
let productName = HUAWEI_DEVICE_MAP[model];  // 第一级:映射表
if (!productName) {
  productName = deviceInfo.marketName || model; // 第二级:marketName
}                                               // 第三级:productModel

注意:marketName 可能为空,因此在使用时必须进行空值检查。apple_product_name 使用 || 运算符实现了 marketName 和 productModel 之间的自动降级。

五、brand 与 manufacturer 属性

5.1 属性定义

typescript 复制代码
const brand: string = deviceInfo.brand;
// 示例: "HUAWEI"、"Honor"

const manufacturer: string = deviceInfo.manufacturer;
// 示例: "HUAWEI"

5.2 brand vs manufacturer

属性 含义 华为设备示例 荣耀设备示例
brand 品牌名称 "HUAWEI" "Honor"
manufacturer 制造商名称 "HUAWEI" "HUAWEI"

在大多数情况下两者相同,但在 OEM/ODM 模式下可能不同。例如荣耀独立后,brand 为 "Honor" 但 manufacturer 可能仍为 "HUAWEI"。

5.3 品牌判断示例

typescript 复制代码
function isHuaweiBrand(): boolean {
  return deviceInfo.brand === 'HUAWEI';
}

function isHonorBrand(): boolean {
  return deviceInfo.brand === 'Honor';
}

5.4 apple_product_name 未使用这两个属性

apple_product_name 没有使用 brand 和 manufacturer,因为映射表中的产品名称已经包含了品牌前缀(如 "HUAWEI Mate 60 Pro"、"Honor Magic6 Pro")。

提示:如果需要在应用中区分华为和荣耀设备,可以使用 brand 属性,也可以通过 getProductName() 返回的名称前缀判断。

六、deviceType 设备类型属性

6.1 属性定义

typescript 复制代码
const type: string = deviceInfo.deviceType;
// 示例: "phone"、"tablet"、"wearable"

6.2 设备类型枚举

deviceType 值 设备形态 映射表中的代表设备
"phone" 智能手机 Mate 70、Pura 70、nova 13
"tablet" 平板电脑 MatePad Pro 13.2、MatePad 11.5
"wearable" 智能穿戴 WATCH GT 4、WATCH Ultimate
"tv" 智慧屏 ---
"car" 车机 ---

6.3 设备类型判断函数

typescript 复制代码
function getDeviceCategory(): string {
  switch (deviceInfo.deviceType) {
    case 'phone': return '手机';
    case 'tablet': return '平板';
    case 'wearable': return '手表';
    default: return '其他';
  }
}

6.4 结合产品名称的设备分类

dart 复制代码
// Dart 侧结合 deviceType 和 productName 进行精细分类
final name = await OhosProductName().getProductName();
if (name.contains('MatePad')) {
  print('华为平板设备');
} else if (name.contains('WATCH')) {
  print('华为手表设备');
} else if (name.contains('Mate X') || name.contains('Pocket')) {
  print('华为折叠屏设备');
} else {
  print('华为直板手机');
}

注意:apple_product_name 没有直接暴露 deviceType 属性给 Dart 层,但开发者可以通过产品名称中的关键词间接判断设备类型。

七、系统版本信息属性

7.1 版本相关属性

typescript 复制代码
// 完整系统名称
const osFullName: string = deviceInfo.osFullName;
// 示例: "OpenHarmony 4.0.0.0"

// SDK API 版本号
const sdkApiVersion: number = deviceInfo.sdkApiVersion;
// 示例: 20

// 显示版本号
const displayVersion: string = deviceInfo.displayVersion;
// 示例: "HarmonyOS 4.0.0"

7.2 版本判断工具函数

typescript 复制代码
function isApiVersionAtLeast(minVersion: number): boolean {
  return deviceInfo.sdkApiVersion >= minVersion;
}

// 使用示例
if (isApiVersionAtLeast(12)) {
  // 使用 API 12+ 的新特性
  console.log('支持新版 Kit 导入方式');
}

7.3 版本信息在调试中的价值

版本信息在问题排查中非常有用:

typescript 复制代码
function getDebugInfo(): string {
  return `OS: ${deviceInfo.osFullName}, ` +
         `API: ${deviceInfo.sdkApiVersion}, ` +
         `Model: ${deviceInfo.productModel}`;
}
// 输出: "OS: OpenHarmony 4.0.0.0, API: 20, Model: ALN-AL00"

提示:sdkApiVersion 是数字类型,可以直接用 >= 进行版本比较。osFullName 是字符串类型,适合在日志和用户界面中展示。

八、deviceInfo 完整属性一览

8.1 属性分类表

属性名 类型 分类 apple_product_name 使用
productModel string 设备标识 ✓ 核心
marketName string 设备标识 ✓ 降级
brand string 品牌信息 ✗
manufacturer string 品牌信息 ✗
deviceType string 设备形态 ✗
osFullName string 系统版本 ✗
sdkApiVersion number 系统版本 ✗
displayVersion string 系统版本 ✗
serial string 设备唯一标识 ✗ 需权限
udid string 设备唯一标识 ✗ 需权限

8.2 属性访问权限

权限级别 属性 说明
无需权限 productModel, marketName, brand, manufacturer, deviceType, osFullName, sdkApiVersion 开箱即用
需要权限 serial, udid 需申请 ohos.permission.READ_DEVICE_INFO

8.3 apple_product_name 的最小属性集

apple_product_name 只使用了 productModel 和 marketName 两个属性,这是实现设备名称转换功能的最小属性集:

typescript 复制代码
// 完整的 deviceInfo 使用范围
private getMachineId(result: MethodResult): void {
  result.success(deviceInfo.productModel);  // 属性 1
}

private getProductName(result: MethodResult): void {
  const model = deviceInfo.productModel;     // 属性 1
  let name = HUAWEI_DEVICE_MAP[model];
  if (!name) {
    name = deviceInfo.marketName || model;   // 属性 2
  }
  result.success(name);
}

注意:使用最小属性集意味着插件不需要申请任何权限,集成该库的应用不需要在配置文件中声明额外权限,提供了最简化的集成体验。

九、getMachineId 中的 API 调用

9.1 完整源码

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);
  }
}

9.2 调用链路

复制代码
Dart: getMachineId()
  → invokeMethod('getMachineId')
    → onMethodCall → case "getMachineId"
      → getMachineId(result)
        → deviceInfo.productModel  ← 系统 API 调用点
          → result.success("ALN-AL00")

9.3 为什么用 try-catch 包裹

虽然 deviceInfo.productModel 在正常情况下不会抛出异常,但防御性编程考虑以下场景:

  1. 系统服务异常导致 deviceInfo 模块不可用
  2. 设备固件缺陷导致属性返回异常
  3. 未来系统版本更新改变了 API 行为
  4. 特殊的沙箱或测试环境限制了 API 访问

提示:getMachineId 是插件中最简单的方法------一行代码获取系统属性,一行代码返回结果。但 try-catch 的防御性包裹确保了它在任何环境下都不会导致应用崩溃。

十、getProductName 三级降级实现

10.1 完整源码

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);
  }
}

10.2 三级降级流程

复制代码
第一级:HUAWEI_DEVICE_MAP[productModel]
  ↓ 未命中
第二级:deviceInfo.marketName
  ↓ 为空
第三级:deviceInfo.productModel(原始型号)

三级降级的核心逻辑只有三行代码,但每一级都有明确的职责:

降级级别 数据来源 返回示例 可靠性
第一级 HUAWEI_DEVICE_MAP 映射表 "HUAWEI Mate 60 Pro" 最高(人工维护)
第二级 deviceInfo.marketName "HUAWEI Mate 60 Pro" 中等(系统提供)
第三级 deviceInfo.productModel "ALN-AL00" 最低(原始编码)

10.3 降级触发条件

typescript 复制代码
// 第一级命中:映射表包含该型号
const model = "ALN-AL00";
HUAWEI_DEVICE_MAP[model]; // "HUAWEI Mate 60 Pro" ✓

// 第一级未命中,第二级命中:新设备未收录但系统有 marketName
const model = "NEW-AL00";
HUAWEI_DEVICE_MAP[model]; // undefined
deviceInfo.marketName;     // "HUAWEI New Device" ✓

// 第一级和第二级都未命中:极端情况
const model = "UNKNOWN-00";
HUAWEI_DEVICE_MAP[model]; // undefined
deviceInfo.marketName;     // "" 或 undefined
// 最终返回 "UNKNOWN-00"

提示:在实际使用中,绝大多数设备会在第一级或第二级命中。第三级降级主要是一个安全网,确保方法永远不会返回空值。

十一、降级策略的设计哲学

11.1 为什么需要三级降级

单一数据源存在固有的局限性:

  • 映射表需要人工维护,新设备发布后存在收录延迟
  • marketName 由设备厂商在固件中预设,可能为空或格式不统一
  • productModel 虽然始终可用,但对用户不友好

三级降级将三个数据源按可靠性排列,逐级尝试,确保在任何情况下都能返回有意义的结果。

11.2 降级策略对比

策略 优点 缺点
仅映射表 名称最准确 新设备返回 null
仅 marketName 无需维护 可能为空、格式不一
仅 productModel 永不为空 用户无法理解
三级降级(当前方案) 兼顾准确性和可用性 实现稍复杂

11.3 || 运算符的妙用

typescript 复制代码
productName = deviceInfo.marketName || model;

这行代码利用了 JavaScript/TypeScript 的短路求值特性:

  1. 如果 marketName 是非空字符串 → 返回 marketName
  2. 如果 marketName 是空字符串 "" → 被视为 falsy,返回 model
  3. 如果 marketName 是 undefined → 被视为 falsy,返回 model
  4. 如果 marketName 是 null → 被视为 falsy,返回 model

注意:|| 运算符同时处理了 undefined、null 和空字符串三种情况,比显式的 if 判断更加简洁。这是 TypeScript 中常见的空值处理惯用法。

十二、权限模型与安全性

12.1 无权限访问

apple_product_name 使用的两个属性都不需要申请权限:

typescript 复制代码
// 无需权限,直接访问
deviceInfo.productModel;  // ✓ 无权限
deviceInfo.marketName;    // ✓ 无权限

12.2 需要权限的属性

属性 权限 用途
serial ohos.permission.READ_DEVICE_INFO 设备序列号
udid ohos.permission.READ_DEVICE_INFO 设备唯一标识

12.3 权限声明方式

如果需要使用受权限保护的属性,需要在 module.json5 中声明:

json5 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_DEVICE_INFO",
        "reason": "$string:permission_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

12.4 apple_product_name 的零权限优势

dart 复制代码
// 集成 apple_product_name 不需要在 module.json5 中添加任何权限声明
// 这意味着:
// 1. 不会触发用户的权限授权弹窗
// 2. 不会增加应用的权限列表
// 3. 不会影响应用商店的隐私审核
// 4. 用户可以放心使用,无隐私顾虑

提示:零权限设计是 apple_product_name 的一个重要特性。在隐私保护日益受到重视的今天,能够在不申请任何权限的情况下获取设备名称,对于应用的用户体验和隐私合规都非常有利。

十三、异常处理与防御性编程

13.1 统一的异常处理模式

插件中所有方法都采用相同的异常处理模式:

typescript 复制代码
private someMethod(result: MethodResult): void {
  try {
    // 业务逻辑
    result.success(value);
  } catch (e) {
    const errorMsg = e instanceof Error ? e.message : String(e);
    result.error("ERROR_CODE", errorMsg, null);
  }
}

13.2 错误类型判断

typescript 复制代码
const errorMsg = e instanceof Error ? e.message : String(e);

这行代码处理了两种异常类型:

异常类型 判断条件 提取方式 示例
Error 对象 e instanceof Error e.message "Cannot read property..."
非 Error 值 其他情况 String(e) "unknown error"

13.3 错误码设计

方法 错误码 含义
getMachineId GET_MACHINE_ID_ERROR 获取型号标识符失败
getProductName GET_PRODUCT_NAME_ERROR 获取产品名称失败
lookup LOOKUP_ERROR 查询映射表失败
lookup INVALID_ARGUMENT 参数缺失或无效

13.4 Dart 侧的异常接收

dart 复制代码
try {
  final name = await OhosProductName().getProductName();
} on PlatformException catch (e) {
  print('错误码: ${e.code}');    // "GET_PRODUCT_NAME_ERROR"
  print('错误信息: ${e.message}'); // 具体的异常描述
  print('详细信息: ${e.details}'); // null
}

注意:原生侧通过 result.error(code, message, details) 返回的错误,在 Dart 侧会被封装为 PlatformException。三个参数分别对应 PlatformException 的 code、message 和 details 属性。

十四、性能特征与缓存策略

14.1 deviceInfo 属性的访问性能

typescript 复制代码
// deviceInfo 属性是同步访问的
// 系统在启动时已将设备信息加载到内存中
const model = deviceInfo.productModel;  // 微秒级,内存读取
const name = deviceInfo.marketName;     // 微秒级,内存读取

14.2 性能特征对比

操作 耗时 说明
deviceInfo.productModel < 0.1ms 内存中的系统属性读取
deviceInfo.marketName < 0.1ms 内存中的系统属性读取
HUAWEI_DEVICE_MAPmodel < 0.01ms 对象属性查找,O(1)
MethodChannel 通信 1-5ms 跨平台消息传递开销

14.3 性能瓶颈分析

复制代码
总耗时 ≈ MethodChannel通信(1-5ms) + 原生逻辑(<0.2ms)

实际的性能瓶颈不在 deviceInfo API 调用或映射表查找,而在 MethodChannel 的跨平台通信。因此缓存策略应该在 Dart 侧实现,减少 MethodChannel 调用次数:

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

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

14.4 为什么设备信息适合缓存

  1. 不变性:设备型号在硬件生命周期内不会改变
  2. 确定性:同一设备每次调用返回相同结果
  3. 无副作用:读取属性不会修改任何状态

提示:设备信息是纯函数式的数据------输入(设备硬件)不变,输出(型号和名称)就不变。这使得它成为缓存的理想候选对象,可以放心地缓存而不用担心数据过期。

十五、设备类型判断实战

15.1 基于 productName 的设备分类

dart 复制代码
enum DeviceFormFactor {
  phone,
  tablet,
  foldable,
  watch,
  unknown,
}

DeviceFormFactor classifyDevice(String productName) {
  if (productName.contains('MatePad')) return DeviceFormFactor.tablet;
  if (productName.contains('Mate X') || productName.contains('Pocket')) {
    return DeviceFormFactor.foldable;
  }
  if (productName.contains('WATCH')) return DeviceFormFactor.watch;
  if (productName.contains('Mate') || productName.contains('Pura') ||
      productName.contains('nova') || productName.contains('Honor')) {
    return DeviceFormFactor.phone;
  }
  return DeviceFormFactor.unknown;
}

15.2 根据设备类型调整 UI

dart 复制代码
final name = await OhosProductName().getProductName();
final formFactor = classifyDevice(name);

switch (formFactor) {
  case DeviceFormFactor.tablet:
    // 使用平板布局:双栏、更大的字体
    return TabletLayout();
  case DeviceFormFactor.foldable:
    // 使用折叠屏布局:适配展开/折叠状态
    return FoldableLayout();
  case DeviceFormFactor.watch:
    // 使用手表布局:圆形界面、精简内容
    return WatchLayout();
  default:
    // 使用手机布局:标准单栏
    return PhoneLayout();
}

15.3 设备系列与映射表的对应关系

设备形态 产品名称关键词 映射表中的型号前缀
直板手机 Mate 60/70, Pura, nova ALN, CFR, HBN, FOA
折叠屏 Mate X, Pocket GGK-W, PAL, BAL
平板 MatePad GROK, GOT, DBY2, BTK
手表 WATCH MNA-B, OCE-B, FRG-B

注意:设备形态的判断基于产品名称中的关键词,而非 deviceType 属性。这是因为 apple_product_name 没有暴露 deviceType 给 Dart 层,但产品名称中已经包含了足够的信息来推断设备形态。

十六、获取完整设备信息

16.1 扩展插件获取更多信息

如果项目需要更多的设备信息,可以在原生侧扩展一个新方法:

typescript 复制代码
private getFullDeviceInfo(result: MethodResult): void {
  try {
    const info: Record<string, string | number> = {
      productModel: deviceInfo.productModel,
      marketName: deviceInfo.marketName,
      brand: deviceInfo.brand,
      manufacturer: deviceInfo.manufacturer,
      deviceType: deviceInfo.deviceType,
      osFullName: deviceInfo.osFullName,
      sdkApiVersion: deviceInfo.sdkApiVersion,
    };
    result.success(JSON.stringify(info));
  } catch (e) {
    const errorMsg = e instanceof Error ? e.message : String(e);
    result.error("GET_FULL_INFO_ERROR", errorMsg, null);
  }
}

16.2 Dart 侧解析

dart 复制代码
Future<Map<String, dynamic>> getFullDeviceInfo() async {
  final jsonStr = await _channel.invokeMethod<String>('getFullDeviceInfo');
  return jsonDecode(jsonStr!) as Map<String, dynamic>;
}

// 使用
final info = await getFullDeviceInfo();
print('品牌: ${info['brand']}');
print('型号: ${info['productModel']}');
print('系统: ${info['osFullName']}');
print('API: ${info['sdkApiVersion']}');

16.3 当前插件为什么不提供完整信息

apple_product_name 的设计目标是设备名称转换,而非通用的设备信息获取工具。只暴露必要的接口有以下好处:

  • API 表面积小,易于理解和使用
  • 不需要申请任何权限
  • 减少跨平台通信的数据量
  • 职责单一,便于维护

提示:如果需要获取完整的设备信息,建议使用专门的设备信息库(如 device_info_plus),或者按照上面的示例自行扩展插件。apple_product_name 专注于做好一件事------将型号标识符转换为用户友好的产品名称。

十七、与 iOS deviceInfo 的对比

17.1 跨平台对比

维度 OpenHarmony iOS
API 来源 @kit.BasicServicesKit utsname 系统调用
型号标识符 deviceInfo.productModel utsname().machine
标识符格式 "ALN-AL00" "iPhone15,2"
市场名称 deviceInfo.marketName 无系统 API
映射表位置 原生侧 ArkTS Dart 侧自动生成
权限要求 无 无

17.2 映射表维护方式对比

复制代码
iOS 映射表:
  gen/main.dart → 从 GitHub 拉取 JSON → 自动生成 .g.dart

OpenHarmony 映射表:
  AppleProductNamePlugin.ets → 手动维护 HUAWEI_DEVICE_MAP
对比项 iOS OpenHarmony
维护方式 自动生成 手动维护
数据来源 apple-device-identifiers 仓库 开发者社区贡献
更新频率 随上游仓库更新 随新设备发布手动添加
降级方案 返回原始标识符 三级降级(映射表→marketName→productModel)

17.3 OpenHarmony 的独特优势

OpenHarmony 平台提供了 marketName 属性,这是 iOS 所没有的。这意味着即使映射表没有收录某个设备,getProductName 仍然可以通过 marketName 返回用户友好的名称,而 iOS 在映射表未命中时只能返回类似 "iPhone15,2" 的原始标识符。

提示:marketName 是 OpenHarmony 平台的一个优势------它为三级降级策略提供了一个可靠的中间层,使得 apple_product_name 在鸿蒙平台上的容错能力优于 iOS 平台。

十八、Dart 侧设备信息验证

18.1 验证 API 返回值

dart 复制代码
Future<void> verifyDeviceInfo() async {
  final ohos = OhosProductName();

  // 验证 getMachineId
  final machineId = await ohos.getMachineId();
  assert(machineId.isNotEmpty, 'machineId 不应为空');
  assert(machineId != 'Unknown', 'machineId 不应为 Unknown');
  print('✓ machineId: $machineId');

  // 验证 getProductName
  final productName = await ohos.getProductName();
  assert(productName.isNotEmpty, 'productName 不应为空');
  print('✓ productName: $productName');

  // 验证 lookup
  final lookupResult = await ohos.lookup(machineId);
  print('✓ lookup($machineId): $lookupResult');

  // 验证一致性
  if (lookupResult == productName) {
    print('✓ lookup 结果与 getProductName 一致');
  } else {
    print('⚠ lookup 结果与 getProductName 不一致');
    print('  可能原因:getProductName 使用了 marketName 降级');
  }
}

18.2 一致性说明

getProductName 和 lookup(getMachineId()) 的结果可能不一致:

场景 getProductName lookup(machineId) 原因
映射表命中 "HUAWEI Mate 60 Pro" "HUAWEI Mate 60 Pro" 两者都从映射表获取
映射表未命中 "HUAWEI New Device" null getProductName 降级到 marketName

注意:lookup 方法只查询映射表,不会使用 marketName 降级。如果需要与 getProductName 完全一致的行为,应该直接调用 getProductName 而非 lookup。

十九、常见问题与排查

19.1 问题:productModel 返回空字符串

typescript 复制代码
// 排查步骤
console.log('productModel:', JSON.stringify(deviceInfo.productModel));
console.log('typeof:', typeof deviceInfo.productModel);

可能原因:

  1. 模拟器环境未正确模拟设备信息
  2. 系统版本过低,API 尚未支持
  3. 特殊的沙箱环境限制了属性访问

19.2 问题:marketName 与预期不符

typescript 复制代码
// marketName 的值由设备厂商在固件中设定
// 不同固件版本可能返回不同的值
console.log('marketName:', deviceInfo.marketName);
// 可能返回: "HUAWEI Mate 60 Pro"
// 也可能返回: "Mate 60 Pro"(缺少品牌前缀)
// 也可能返回: ""(空字符串)

19.3 问题:映射表查找失败

typescript 复制代码
// 确认型号标识符的精确值
const model = deviceInfo.productModel;
console.log('查找键:', JSON.stringify(model));
console.log('映射结果:', HUAWEI_DEVICE_MAP[model]);

// 常见原因:型号标识符包含不可见字符或大小写不匹配
// 解决方案:在映射表中添加该型号

19.4 快速诊断工具

dart 复制代码
Future<void> quickDiagnose() async {
  print('=== deviceInfo 诊断 ===');
  try {
    final ohos = OhosProductName();
    final id = await ohos.getMachineId();
    final name = await ohos.getProductName();
    print('machineId: $id');
    print('productName: $name');
    print('映射表命中: ${id != name && !name.contains(id)}');
    print('=== 诊断完成 ===');
  } catch (e) {
    print('诊断失败: $e');
  }
}

提示:遇到问题时,先运行诊断工具获取基础信息,再根据输出结果有针对性地排查。大多数问题都可以通过检查 machineId 和 productName 的返回值来定位。

总结

deviceInfo 是 apple_product_name 库在 OpenHarmony 平台的核心数据来源。插件只使用了 productModel 和 marketName 两个属性,通过三级降级策略确保在任何设备上都能返回有意义的产品名称,同时保持了零权限的轻量集成体验。

下一篇文章将介绍插件注册与生命周期管理。

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


相关资源:

相关推荐
Geek_Vison10 小时前
保险APP已经用了Flutter,还能接入小程序吗?
flutter·小程序·uni-app·mpaas·小程序容器
FungLeo10 小时前
成为全栈·Flutter App 篇·go_router 与四 Tab App Shell:保留导航状态、深链和登录回跳
flutter·移动开发·成为全栈·go_router·路由设计·深链接
m0_7381858211 小时前
Flutter 鸿蒙化实战:screen_protector 适配 OpenHarmony,防截屏/防泄露
flutter·华为·harmonyos·鸿蒙
梦想的颜色16 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭2 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白2 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫3 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘3 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185823 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙