device_manager.dart 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // lib/utils/device_manager.dart
  2. import 'dart:io';
  3. import 'dart:convert';
  4. import 'package:crypto/crypto.dart';
  5. import 'package:device_info_plus/device_info_plus.dart';
  6. import 'package:get/get.dart';
  7. import 'package:path/path.dart' as path;
  8. import 'package:path_provider/path_provider.dart';
  9. import 'package:uuid/uuid.dart';
  10. import 'package:flutter_secure_storage/flutter_secure_storage.dart';
  11. import '../app/data/sp/ix_sp.dart';
  12. import 'log/logger.dart';
  13. import 'permission_manager.dart';
  14. class DeviceManager {
  15. static const String TAG = 'DeviceManager';
  16. static final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
  17. static const String _folderPrefix = '.omon_cache_';
  18. static const String _iosDeviceIdKey = 'omon_device_id';
  19. static const String _iosDeviceFolderKey = 'omon_device_folder';
  20. /// 获取设备唯一标识并确保文件夹存在
  21. static Future<String> getDeviceId() async {
  22. try {
  23. // 1. 先从 SharedPreferences 获取
  24. String? deviceId = IXSP.getDeviceId();
  25. // 2. 如果SP中没有,尝试从存储位置获取
  26. if (deviceId == null || deviceId.isEmpty) {
  27. try {
  28. deviceId = await _getDeviceIdFromStorage();
  29. } catch (e) {
  30. log(TAG, 'Error reading from storage: $e');
  31. }
  32. }
  33. // 3. 如果还是没有,生成新的
  34. if (deviceId == null || deviceId.isEmpty) {
  35. try {
  36. deviceId = await _generateNewDeviceId();
  37. } catch (e) {
  38. log(TAG, 'Error generating device ID: $e');
  39. deviceId = _generateFallbackId();
  40. }
  41. }
  42. // 4. 确保保存到存储位置
  43. try {
  44. await _saveDeviceId(deviceId);
  45. } catch (e) {
  46. log(TAG, 'Error saving device ID: $e');
  47. }
  48. // 5. 尝试创建文件夹(仅Android),但不影响返回结果
  49. try {
  50. await _ensureDeviceFolder(deviceId);
  51. } catch (e) {
  52. log(TAG, 'Error creating folder: $e');
  53. // 文件夹创建失败不影响 ID 的使用
  54. }
  55. return deviceId;
  56. } catch (e) {
  57. log(TAG, 'Critical error in getDeviceId: $e');
  58. // 最后的后备方案:生成随机ID
  59. final fallbackId = _generateFallbackId();
  60. try {
  61. await _saveDeviceId(fallbackId);
  62. } catch (e) {
  63. log(TAG, 'Error saving fallback ID: $e');
  64. }
  65. return fallbackId;
  66. }
  67. }
  68. /// 获取缓存
  69. static String getCacheDeviceId() {
  70. return IXSP.getDeviceId() ?? '';
  71. }
  72. /// 从存储位置获取设备ID
  73. static Future<String?> _getDeviceIdFromStorage() async {
  74. if (Platform.isIOS) {
  75. // iOS 从钥匙串获取
  76. try {
  77. const storage = FlutterSecureStorage();
  78. final deviceId = await storage.read(key: _iosDeviceIdKey);
  79. if (deviceId != null && deviceId.isNotEmpty) {
  80. await IXSP.setDeviceId(deviceId);
  81. return deviceId;
  82. }
  83. } catch (e) {
  84. log(TAG, 'Error reading from iOS keychain: $e');
  85. }
  86. } else {
  87. // Android 从文件夹获取
  88. try {
  89. final downloadsDir = await _getDownloadsDirectory();
  90. if (downloadsDir == null || !await downloadsDir.exists()) {
  91. return null;
  92. }
  93. final entities = await downloadsDir.list().toList();
  94. for (var entity in entities) {
  95. if (entity is Directory) {
  96. final folderName = path.basename(entity.path);
  97. if (folderName.startsWith(_folderPrefix)) {
  98. final uuid = folderName.substring(_folderPrefix.length);
  99. // 验证UUID格式
  100. if (_isValidUuid(uuid)) {
  101. // 找到有效的UUID,保存到SP
  102. await IXSP.setDeviceId(uuid);
  103. return uuid;
  104. }
  105. }
  106. }
  107. }
  108. } catch (e) {
  109. log(TAG, 'Error getting device ID from Android folder: $e');
  110. }
  111. }
  112. return null;
  113. }
  114. /// 保存设备ID到存储位置
  115. static Future<void> _saveDeviceId(String deviceId) async {
  116. // 保存到 SharedPreferences(跨平台)
  117. await IXSP.setDeviceId(deviceId);
  118. if (Platform.isIOS) {
  119. // iOS 保存到钥匙串
  120. try {
  121. const storage = FlutterSecureStorage();
  122. await storage.write(key: _iosDeviceIdKey, value: deviceId);
  123. } catch (e) {
  124. log(TAG, 'Error saving to iOS keychain: $e');
  125. }
  126. }
  127. }
  128. /// 生成新的设备ID
  129. static Future<String> _generateNewDeviceId() async {
  130. if (GetPlatform.isAndroid) {
  131. final AndroidDeviceInfo androidInfo = await _deviceInfo.androidInfo;
  132. final deviceInfo = {
  133. 'androidId': androidInfo.id,
  134. 'brand': androidInfo.brand,
  135. 'model': androidInfo.model,
  136. 'product': androidInfo.product,
  137. 'hardware': androidInfo.hardware,
  138. 'manufacturer': androidInfo.manufacturer,
  139. 'uuid': const Uuid().v4(),
  140. };
  141. final deviceId = _generateHash(jsonEncode(deviceInfo));
  142. await _saveDeviceId(deviceId);
  143. return deviceId;
  144. } else if (GetPlatform.isIOS) {
  145. final IosDeviceInfo iosInfo = await _deviceInfo.iosInfo;
  146. final deviceInfo = {
  147. 'identifierForVendor': iosInfo.identifierForVendor,
  148. 'model': iosInfo.model,
  149. 'systemVersion': iosInfo.systemVersion,
  150. 'uuid': const Uuid().v4(),
  151. };
  152. final deviceId = _generateHash(jsonEncode(deviceInfo));
  153. await _saveDeviceId(deviceId);
  154. return deviceId;
  155. }
  156. return _generateFallbackId();
  157. }
  158. /// 确保设备文件夹存在
  159. static Future<void> _ensureDeviceFolder(String deviceId) async {
  160. // iOS 不需要创建文件夹,数据存储在钥匙串中
  161. if (Platform.isIOS) {
  162. return;
  163. }
  164. try {
  165. final downloadsDir = await _getDownloadsDirectory();
  166. if (downloadsDir == null || !await downloadsDir.exists()) {
  167. throw Exception('Downloads directory not found');
  168. }
  169. final folderName = '$_folderPrefix$deviceId';
  170. final deviceFolder = Directory('${downloadsDir.path}/$folderName');
  171. if (!await deviceFolder.exists()) {
  172. await deviceFolder.create();
  173. // 创建.nomedia文件
  174. final nomedia = File('${deviceFolder.path}/.nomedia');
  175. await nomedia.create();
  176. }
  177. } catch (e) {
  178. log(TAG, 'Error ensuring device folder: $e');
  179. rethrow;
  180. }
  181. }
  182. /// 获取下载目录
  183. static Future<Directory?> _getDownloadsDirectory() async {
  184. if (Platform.isAndroid) {
  185. try {
  186. // 首先尝试获取应用的外部存储目录
  187. // 判断是不是Android10以下
  188. final hasPermission =
  189. await PermissionManager.requestStoragePermission();
  190. if (!hasPermission) {
  191. throw Exception('Storage permission denied');
  192. }
  193. final downloadsDir = Directory('/storage/emulated/0/Download');
  194. return downloadsDir;
  195. } catch (e) {
  196. log(TAG, 'Error getting downloads directory: $e');
  197. final downloadsDir = getExternalStorageDirectory();
  198. return downloadsDir;
  199. }
  200. }
  201. return null;
  202. }
  203. /// 生成哈希值
  204. static String _generateHash(String input) {
  205. final bytes = utf8.encode(input);
  206. final digest = md5.convert(bytes);
  207. return digest.toString();
  208. }
  209. /// 生成后备ID
  210. static String _generateFallbackId() {
  211. final random = DateTime.now().millisecondsSinceEpoch.toString();
  212. return _generateHash(random);
  213. }
  214. /// 验证UUID格式
  215. static bool _isValidUuid(String uuid) {
  216. // 验证是否为32位十六进制字符
  217. return RegExp(r'^[a-f0-9]{32}$').hasMatch(uuid);
  218. }
  219. /// 获取设备文件夹路径
  220. static Future<String> getDeviceFolderPath() async {
  221. if (Platform.isIOS) {
  222. // iOS 返回钥匙串标识符
  223. return 'iOS Keychain: $_iosDeviceIdKey';
  224. }
  225. final deviceId = await getDeviceId();
  226. return '/storage/emulated/0/Download/$_folderPrefix$deviceId';
  227. }
  228. /// 清除所有数据
  229. static Future<void> clearAll() async {
  230. try {
  231. if (Platform.isIOS) {
  232. // iOS 清除钥匙串数据
  233. try {
  234. const storage = FlutterSecureStorage();
  235. await storage.delete(key: _iosDeviceIdKey);
  236. await storage.delete(key: _iosDeviceFolderKey);
  237. } catch (e) {
  238. log(TAG, 'Error clearing iOS keychain: $e');
  239. }
  240. } else {
  241. // Android 删除文件夹
  242. try {
  243. final downloadsDir = await _getDownloadsDirectory();
  244. if (downloadsDir != null && await downloadsDir.exists()) {
  245. final entities = await downloadsDir.list().toList();
  246. for (var entity in entities) {
  247. if (entity is Directory) {
  248. final folderName = path.basename(entity.path);
  249. if (folderName.startsWith(_folderPrefix)) {
  250. await entity.delete(recursive: true);
  251. }
  252. }
  253. }
  254. }
  255. } catch (e) {
  256. log(TAG, 'Error clearing Android folders: $e');
  257. }
  258. }
  259. // 清除 SP 中的 UUID
  260. await IXSP.setDeviceId('');
  261. } catch (e) {
  262. log(TAG, 'Error clearing device data: $e');
  263. }
  264. }
  265. }