
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 一、组件概述与应用场景
📋 1.1 mobile_device_identifier 简介
在移动应用开发中,获取设备唯一标识符是一个非常常见的需求。无论是用户身份识别、设备绑定、数据统计还是安全验证,都需要一个稳定可靠的设备标识符。在 Flutter for OpenHarmony 应用开发中,mobile_device_identifier 插件正是为此而生的解决方案。
核心特性:
| 特性 | 说明 |
|---|---|
| 🔑 唯一性 | 生成的设备 ID 在全球范围内唯一 |
| 💾 持久性 | 应用卸载重装后 ID 保持不变 |
| 🌐 跨平台支持 | 支持 Android、iOS、OpenHarmony 等多个平台 |
| 🔓 无需权限 | 在大多数平台上不需要特殊权限即可获取 |
| ⚡ 简单易用 | 一行代码即可获取设备 ID |
| 🛡️ 安全可靠 | 使用系统级 API 生成,保证安全性 |
💡 1.2 实际应用场景
用户身份识别:在某些应用场景中,需要识别用户所使用的设备。例如,免费试用应用可能限制每个设备只能试用一次。
设备绑定:对于安全敏感的应用,如银行应用、企业应用等,可能需要将用户账号与特定设备绑定。
数据统计与分析:在应用数据分析中,设备 ID 用于统计活跃用户数、留存率等指标。
推送通知:推送通知服务需要设备标识来定位目标设备。
防作弊与安全:在游戏、投票、抽奖等场景中,需要防止用户通过多次安装应用来作弊。
个性化设置同步:某些应用允许用户在设置中保存偏好设置,并在重新安装后恢复。
🏗️ 1.3 系统架构设计
┌─────────────────────────────────────────────────────────┐
│ UI 展示层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 设备信息展示 │ │ 设备绑定管理 │ │ 设备统计面板 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 业务逻辑层 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ DeviceIdentifierService 设备服务 │ │
│ │ • getDeviceId() • bindDevice() • unbind() │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 平台适配层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Android ID │ │ IDFV (iOS) │ │ OAID (OH) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
📦 二、项目配置与依赖安装
🔧 2.1 添加依赖配置
打开项目根目录下的 pubspec.yaml 文件,添加以下配置:
yaml
dependencies:
flutter:
sdk: flutter
# mobile_device_identifier - 设备唯一标识插件
mobile_device_identifier:
git:
url: "https://gitcode.com/openharmony-sig/fluttertpc_mobile_device_identifier.git"
dev_dependencies:
# mobile_device_identifier 鸿蒙平台支持
mobile_device_identifier_ohos:
git:
url: "https://gitcode.com/openharmony-sig/fluttertpc_mobile_device_identifier.git"
path: ./ohos
配置说明:
- 使用 git 方式引用开源鸿蒙适配的仓库
mobile_device_identifier_ohos:鸿蒙平台的原生实现- 本项目基于
mobile_device_identifier@0.0.2开发 - 适配 Flutter 3.27.5-ohos-1.0.4
⚠️ 重要提示:对于 OpenHarmony 平台,必须使用 git 方式引用适配版本,不能直接使用 pub.dev 的版本号。
📥 2.2 下载依赖
配置完成后,在项目根目录执行以下命令:
bash
flutter pub get
🔐 2.3 权限配置
在 OpenHarmony 平台上,获取设备 ID 需要配置相关权限。
ohos/entry/src/main/module.json5:
json
{
"module": {
"requestPermissions": [
{"name": "ohos.permission.INTERNET"},
{
"name": "ohos.permission.APP_TRACKING_CONSENT",
"reason": "$string:reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
ohos/entry/src/main/resources/base/element/string.json:
json
{
"string": [
{
"name": "reason",
"value": "应用信息读取"
}
]
}
⚠️ 重要提示 :
ohos.permission.APP_TRACKING_CONSENT权限属于system_basic级别。需要参考官方文档修改应用等级。
📱 2.4 平台差异说明
| 平台 | 主要标识符 | 特点 |
|---|---|---|
| Android | Android ID / IMEI | Android ID 在恢复出厂后重置 |
| iOS | identifierForVendor | 同一开发商的应用 ID 相同 |
| OpenHarmony | OAID | 用户可重置,匿名性强 |
🔧 三、核心功能详解
🎯 3.1 获取设备 ID
dart
import 'package:flutter/services.dart';
import 'package:mobile_device_identifier_ohos/mobile_device_identifier_ohos.dart';
final _mobileDeviceIdentifierPlugin = MobileDeviceIdentifier();
Future<String?> getDeviceId() async {
try {
String? deviceId = await _mobileDeviceIdentifierPlugin.getDeviceId();
return deviceId;
} on PlatformException catch (e) {
debugPrint('获取设备 ID 失败: ${e.message}');
return null;
}
}
🔐 3.2 设备绑定功能
dart
class DeviceBindingService {
static const String _keyDeviceId = 'bound_device_id';
final SharedPreferences _prefs;
DeviceBindingService(this._prefs);
Future<bool> isCurrentDeviceBound() async {
final boundId = _prefs.getString(_keyDeviceId);
if (boundId == null) return false;
final currentId = await MobileDeviceIdentifier().getDeviceId();
return boundId == currentId;
}
Future<bool> bindCurrentDevice() async {
final currentId = await MobileDeviceIdentifier().getDeviceId();
if (currentId == null) return false;
await _prefs.setString(_keyDeviceId, currentId);
return true;
}
Future<void> unbindDevice() async {
await _prefs.remove(_keyDeviceId);
}
}
📊 3.3 设备数量限制
dart
class DeviceLimitService {
final int maxDevices;
DeviceLimitService({this.maxDevices = 3});
Future<bool> canAddNewDevice(String userId) async {
final devices = await _getRegisteredDevices(userId);
return devices.length < maxDevices;
}
Future<bool> registerCurrentDevice(String userId) async {
final deviceId = await MobileDeviceIdentifier().getDeviceId();
if (deviceId == null) return false;
final devices = await _getRegisteredDevices(userId);
if (devices.contains(deviceId)) return true;
if (devices.length >= maxDevices) return false;
devices.add(deviceId);
await _saveRegisteredDevices(userId, devices);
return true;
}
Future<List<String>> _getRegisteredDevices(String userId) async {
return [];
}
Future<void> _saveRegisteredDevices(String userId, List<String> devices) async {}
}
📝 四、完整示例代码
下面是一个完整的智能设备标识管理系统示例:
dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobile_device_identifier_ohos/mobile_device_identifier_ohos.dart';
void main() {
runApp(const DeviceIdentifierApp());
}
class DeviceIdentifierApp extends StatelessWidget {
const DeviceIdentifierApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '智能设备标识管理系统',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const MainPage(),
);
}
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
final List<Widget> _pages = [
const DeviceInfoPage(),
const DeviceBindingPage(),
const DeviceStatsPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() => _currentIndex = index);
},
destinations: const [
NavigationDestination(icon: Icon(Icons.devices), label: '设备信息'),
NavigationDestination(icon: Icon(Icons.link), label: '设备绑定'),
NavigationDestination(icon: Icon(Icons.analytics), label: '设备统计'),
],
),
);
}
}
// ============ 设备信息页面 ============
class DeviceInfoPage extends StatefulWidget {
const DeviceInfoPage({super.key});
@override
State<DeviceInfoPage> createState() => _DeviceInfoPageState();
}
class _DeviceInfoPageState extends State<DeviceInfoPage> {
String? _deviceId;
bool _isLoading = true;
String? _error;
@override
void initState() {
super.initState();
_loadDeviceId();
}
Future<void> _loadDeviceId() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final plugin = MobileDeviceIdentifier();
final id = await plugin.getDeviceId();
setState(() {
_deviceId = id;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
Future<void> _copyToClipboard() async {
if (_deviceId == null) return;
await Clipboard.setData(ClipboardData(text: _deviceId!));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('设备 ID 已复制到剪贴板'),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设备信息'),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadDeviceId,
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _buildContent(),
);
}
Widget _buildContent() {
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
_buildDeviceCard(),
const SizedBox(height: 24),
_buildInfoSection(),
const SizedBox(height: 24),
_buildActionsSection(),
],
),
);
}
Widget _buildDeviceCard() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.indigo.shade400, Colors.indigo.shade600],
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
const Icon(Icons.devices, size: 48, color: Colors.white),
const SizedBox(height: 16),
const Text(
'设备唯一标识',
style: TextStyle(color: Colors.white70, fontSize: 14),
),
const SizedBox(height: 8),
SelectableText(
_deviceId ?? '获取失败',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _deviceId != null ? _copyToClipboard : null,
icon: const Icon(Icons.copy, size: 18),
label: const Text('复制 ID'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.indigo,
),
),
],
),
);
}
Widget _buildInfoSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'关于设备标识',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildInfoItem('唯一性', '每个设备拥有全球唯一的标识符'),
_buildInfoItem('持久性', '应用卸载重装后标识符保持不变'),
_buildInfoItem('安全性', '标识符不包含个人隐私信息'),
_buildInfoItem('用途', '用于设备识别、数据同步等功能'),
],
),
),
);
}
Widget _buildInfoItem(String title, String description) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 6,
height: 6,
margin: const EdgeInsets.only(top: 6, right: 12),
decoration: BoxDecoration(
color: Colors.indigo.shade400,
shape: BoxShape.circle,
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
Text(
description,
style: TextStyle(color: Colors.grey.shade600, fontSize: 13),
),
],
),
),
],
),
);
}
Widget _buildActionsSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'快捷操作',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
ListTile(
leading: const Icon(Icons.copy),
title: const Text('复制设备 ID'),
subtitle: const Text('将设备 ID 复制到剪贴板'),
trailing: const Icon(Icons.chevron_right),
onTap: _copyToClipboard,
),
ListTile(
leading: const Icon(Icons.share),
title: const Text('分享设备信息'),
subtitle: const Text('通过其他应用分享设备 ID'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('分享功能开发中')),
);
},
),
],
),
),
);
}
}
// ============ 设备绑定页面 ============
class DeviceBindingPage extends StatefulWidget {
const DeviceBindingPage({super.key});
@override
State<DeviceBindingPage> createState() => _DeviceBindingPageState();
}
class _DeviceBindingPageState extends State<DeviceBindingPage> {
String? _deviceId;
bool _isBound = false;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
final plugin = MobileDeviceIdentifier();
final id = await plugin.getDeviceId();
setState(() {
_deviceId = id;
_isLoading = false;
});
}
void _toggleBinding() {
setState(() => _isBound = !_isBound);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_isBound ? '设备已绑定' : '设备已解绑'),
backgroundColor: _isBound ? Colors.green : Colors.orange,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设备绑定'),
centerTitle: true,
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _buildContent(),
);
}
Widget _buildContent() {
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
_buildBindingStatusCard(),
const SizedBox(height: 24),
_buildBindingInfoCard(),
],
),
);
}
Widget _buildBindingStatusCard() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: _isBound
? [Colors.green.shade400, Colors.green.shade600]
: [Colors.grey.shade400, Colors.grey.shade600],
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Icon(
_isBound ? Icons.link : Icons.link_off,
size: 48,
color: Colors.white,
),
const SizedBox(height: 16),
Text(
_isBound ? '设备已绑定' : '设备未绑定',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
_deviceId ?? '',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _toggleBinding,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: _isBound ? Colors.green : Colors.grey,
),
child: Text(_isBound ? '解除绑定' : '绑定设备'),
),
],
),
);
}
Widget _buildBindingInfoCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'设备绑定说明',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildInfoItem('安全保护', '绑定后,您的账号只能在此设备上使用'),
_buildInfoItem('数据安全', '设备绑定可防止他人盗用您的账号'),
_buildInfoItem('灵活管理', '您可以随时解除绑定并绑定新设备'),
],
),
),
);
}
Widget _buildInfoItem(String title, String description) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Icon(Icons.check_circle, color: Colors.green.shade400, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
Text(
description,
style: TextStyle(color: Colors.grey.shade600, fontSize: 13),
),
],
),
),
],
),
);
}
}
// ============ 设备统计页面 ============
class DeviceStatsPage extends StatelessWidget {
const DeviceStatsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设备统计'),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
_buildStatsCard(
'总设备数',
'1,234',
Icons.devices,
Colors.blue,
),
const SizedBox(height: 16),
_buildStatsCard(
'活跃设备',
'856',
Icons.device_hub,
Colors.green,
),
const SizedBox(height: 16),
_buildStatsCard(
'新增设备(今日)',
'42',
Icons.add_circle,
Colors.orange,
),
const SizedBox(height: 24),
_buildRecentDevicesCard(),
],
),
),
);
}
Widget _buildStatsCard(String title, String value, IconData icon, Color color) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
),
);
}
Widget _buildRecentDevicesCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'最近注册设备',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) {
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
backgroundColor: Colors.indigo.withOpacity(0.1),
child: const Icon(Icons.devices, color: Colors.indigo),
),
title: Text('设备 ${index + 1}'),
subtitle: Text('ID: XXXXXXXX${index + 1}'),
trailing: Text(
'${index + 1} 小时前',
style: TextStyle(color: Colors.grey.shade500, fontSize: 12),
),
);
},
),
],
),
),
);
}
}
🏆 五、最佳实践与注意事项
⚠️ 5.1 隐私合规
用户知情:在获取和使用设备 ID 时,应告知用户并获得同意。
最小化使用:只在必要的功能中使用设备 ID,避免过度收集。
数据保护:妥善存储设备 ID,防止泄露。
🔐 5.2 安全性考虑
不可预测:设备 ID 不应包含可预测的模式。
不可伪造:使用系统级 API 生成,确保真实性。
定期更新:关注平台政策变化,及时调整实现方式。
📱 5.3 常见问题处理
获取失败:添加错误处理,提供降级方案。
ID 为空:检查权限配置,确保平台支持。
ID 变化:某些情况下 ID 可能变化,需要处理这种情况。
📌 六、总结
本文通过一个完整的智能设备标识管理系统案例,深入讲解了 mobile_device_identifier 插件的使用方法与最佳实践:
基础获取:掌握获取设备 ID 的基本方法。
设备绑定:实现设备绑定功能,增强账号安全。
设备统计:构建设备统计面板,了解用户设备情况。
隐私合规:遵循隐私保护最佳实践。
掌握这些技巧,你就能构建出专业级的设备标识管理功能,满足各种业务场景需求。