前言
欢迎加入开源鸿蒙跨平台社区: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文件,建议对照源码阅读。
目录
- [deviceInfo 模块导入](#deviceInfo 模块导入)
- [BasicServicesKit 工具包概览](#BasicServicesKit 工具包概览)
- [productModel 属性详解](#productModel 属性详解)
- [marketName 属性详解](#marketName 属性详解)
- [brand 与 manufacturer 属性](#brand 与 manufacturer 属性)
- [deviceType 设备类型属性](#deviceType 设备类型属性)
- 系统版本信息属性
- [deviceInfo 完整属性一览](#deviceInfo 完整属性一览)
- [getMachineId 中的 API 调用](#getMachineId 中的 API 调用)
- [getProductName 三级降级实现](#getProductName 三级降级实现)
- 降级策略的设计哲学
- 权限模型与安全性
- 异常处理与防御性编程
- 性能特征与缓存策略
- 设备类型判断实战
- 获取完整设备信息
- [与 iOS deviceInfo 的对比](#与 iOS deviceInfo 的对比)
- [Dart 侧设备信息验证](#Dart 侧设备信息验证)
- 常见问题与排查
- 总结
一、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 在正常情况下不会抛出异常,但防御性编程考虑以下场景:
- 系统服务异常导致 deviceInfo 模块不可用
- 设备固件缺陷导致属性返回异常
- 未来系统版本更新改变了 API 行为
- 特殊的沙箱或测试环境限制了 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 的短路求值特性:
- 如果
marketName是非空字符串 → 返回marketName - 如果
marketName是空字符串""→ 被视为 falsy,返回model - 如果
marketName是undefined→ 被视为 falsy,返回model - 如果
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_MAP[model] | < 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 为什么设备信息适合缓存
- 不变性:设备型号在硬件生命周期内不会改变
- 确定性:同一设备每次调用返回相同结果
- 无副作用:读取属性不会修改任何状态
提示:设备信息是纯函数式的数据------输入(设备硬件)不变,输出(型号和名称)就不变。这使得它成为缓存的理想候选对象,可以放心地缓存而不用担心数据过期。
十五、设备类型判断实战
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);
可能原因:
- 模拟器环境未正确模拟设备信息
- 系统版本过低,API 尚未支持
- 特殊的沙箱环境限制了属性访问
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 两个属性,通过三级降级策略确保在任何设备上都能返回有意义的产品名称,同时保持了零权限的轻量集成体验。
下一篇文章将介绍插件注册与生命周期管理。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony适配仓库:apple_product_name
- 开源鸿蒙跨平台社区:openharmonycrossplatform.csdn.net
- OpenHarmony 设备信息 API 文档:developer.huawei.com
- Flutter MethodChannel 文档:flutter.dev
- BasicServicesKit 文档:developer.huawei.com