You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

737 lines
26 KiB

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';
import '../../core/utils/logger.dart';
import 'dart:io' show Platform;
import 'package:permission_handler/permission_handler.dart';
/// 设备类型枚举
enum DeviceType {
unknown, // 未知设备类型
classicAudio, // 经典蓝牙音频设备(耳机等)
ble, // BLE设备
dual // 双模设备(同时支持经典蓝牙和BLE)
}
/// 蓝牙设备服务,用于发现和管理蓝牙设备
class BluetoothService extends GetxService {
static BluetoothService get to => Get.find<BluetoothService>();
// 单例实例
static final BluetoothService _instance = BluetoothService._internal();
factory BluetoothService() => _instance;
BluetoothService._internal();
// 设备状态
final _isAvailable = false.obs;
final _isOn = false.obs;
// 已连接的设备
final _connectedDevices = <BluetoothDevice>[].obs;
final _systemPairedDevices = <BluetoothDevice>[].obs;
// 扫描状态
final _isScanning = false.obs;
// 扫描结果
final _scanResults = <ScanResult>[].obs;
// 当前选中的设备
final _selectedDevice = Rx<BluetoothDevice?>(null);
final _deviceType = Rx<DeviceType>(DeviceType.unknown);
// 连接状态
final _connectionState = Rx<BluetoothConnectionState>(BluetoothConnectionState.disconnected);
// 存储最后连接的设备信息,供其他控制器使用
final _lastConnectedDeviceInfo = Rx<Map<String, dynamic>>({});
// 订阅
StreamSubscription? _stateSubscription;
StreamSubscription? _scanSubscription;
StreamSubscription? _connectionSubscription;
// 连接锁,防止并发操作
bool _isConnecting = false;
// 记录连接尝试次数
int _connectionAttempts = 0;
static const int MAX_CONNECTION_ATTEMPTS = 2;
// 权限状态
final _permissionsGranted = false.obs;
bool get permissionsGranted => _permissionsGranted.value;
// Getters
bool get isAvailable => _isAvailable.value;
bool get isOn => _isOn.value;
bool get isScanning => _isScanning.value;
List<BluetoothDevice> get connectedDevices => _connectedDevices;
List<BluetoothDevice> get systemPairedDevices => _systemPairedDevices;
List<ScanResult> get scanResults => _scanResults;
BluetoothDevice? get selectedDevice => _selectedDevice.value;
DeviceType get deviceType => _deviceType.value;
BluetoothConnectionState get connectionState => _connectionState.value;
Map<String, dynamic> get lastConnectedDeviceInfo => _lastConnectedDeviceInfo.value;
// Observable getters (for reactive programming)
RxBool get isAvailableObs => _isAvailable;
RxBool get isOnObs => _isOn;
RxList<BluetoothDevice> get connectedDevicesObs => _connectedDevices;
RxList<ScanResult> get scanResultsObs => _scanResults;
Rx<BluetoothDevice?> get selectedDeviceObs => _selectedDevice;
Rx<DeviceType> get deviceTypeObs => _deviceType;
Rx<BluetoothConnectionState> get connectionStateObs => _connectionState;
Rx<Map<String, dynamic>> get lastConnectedDeviceInfoObs => _lastConnectedDeviceInfo;
// 电池服务UUID
static const String BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb";
// 电池电量特征UUID
static const String BATTERY_CHARACTERISTIC_UUID = "00002a19-0000-1000-8000-00805f9b34fb";
/// 初始化蓝牙服务
Future<BluetoothService> init() async {
try {
// 确保权限检查在后台线程执行,避免主线程阻塞
Future.microtask(() async {
// 仅在Android上请求权限
if (Platform.isAndroid) {
await _checkAndRequestPermissions();
}
});
// 在后台线程初始化蓝牙状态监听
Future.microtask(() {
// 监听蓝牙状态
_stateSubscription = FlutterBluePlus.adapterState.listen((state) {
_isOn.value = state == BluetoothAdapterState.on;
_isAvailable.value = state != BluetoothAdapterState.unavailable;
if (state == BluetoothAdapterState.on) {
// 蓝牙打开时,获取已连接和已配对设备
_refreshSystemDevices();
} else if (state == BluetoothAdapterState.off) {
// 蓝牙关闭时,清空设备列表
_connectedDevices.clear();
_systemPairedDevices.clear();
_scanResults.clear();
_selectedDevice.value = null;
_lastConnectedDeviceInfo.value = {};
_deviceType.value = DeviceType.unknown;
}
});
});
// 延迟检查蓝牙状态,避免启动时卡顿
Future.delayed(const Duration(milliseconds: 800), () async {
try {
// 使用超时机制
bool isOn = await FlutterBluePlus.isOn
.timeout(const Duration(seconds: 3), onTimeout: () {
Logger.error('检查蓝牙状态超时');
return false;
});
_isOn.value = isOn;
_isAvailable.value = isOn;
if (isOn) {
// 在后台刷新设备状态
_refreshSystemDevices();
}
} catch (e) {
Logger.error('延迟检查蓝牙状态失败: $e');
}
});
return this;
} catch (e) {
Logger.error('初始化蓝牙服务失败: $e');
return this;
}
}
/// 检查并请求蓝牙所需权限(Android 12+)
Future<bool> _checkAndRequestPermissions() async {
try {
if (Platform.isAndroid) {
// 对于Android 12+,需要BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
].request();
bool allGranted = statuses.values.every((status) => status.isGranted);
_permissionsGranted.value = allGranted;
if (!allGranted) {
Logger.warning('蓝牙权限未完全授予: $statuses');
}
return allGranted;
}
// iOS不需要显式请求蓝牙权限
_permissionsGranted.value = true;
return true;
} catch (e) {
Logger.error('检查蓝牙权限失败: $e');
_permissionsGranted.value = false;
return false;
}
}
/// 再次请求权限
Future<bool> requestPermissions() async {
return await _checkAndRequestPermissions();
}
/// 刷新系统设备状态(已连接和已配对设备)
Future<void> _refreshSystemDevices() async {
try {
// 避免重复刷新,使用标志位
bool _isRefreshing = false;
if (_isRefreshing) return;
_isRefreshing = true;
// 获取系统已连接设备
try {
List<BluetoothDevice> devices = await FlutterBluePlus.connectedSystemDevices
.timeout(const Duration(seconds: 3), onTimeout: () {
Logger.error('获取已连接设备超时');
return <BluetoothDevice>[];
});
_connectedDevices.value = devices;
Logger.info('已连接系统设备数量: ${_connectedDevices.length}');
// 如果有当前选中设备,更新其状态
if (_selectedDevice.value != null) {
// 检查选中设备是否仍然在已连接列表中
bool stillConnected = _connectedDevices.any(
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str
);
if (stillConnected) {
_connectionState.value = BluetoothConnectionState.connected;
_updateLastConnectedDeviceInfo();
} else {
_connectionState.value = BluetoothConnectionState.disconnected;
}
}
} catch (e) {
Logger.error('获取已连接设备失败: $e');
}
// 获取已配对设备
try {
List<BluetoothDevice> pairedDevices = await FlutterBluePlus.bondedDevices
.timeout(const Duration(seconds: 3), onTimeout: () {
Logger.error('获取已配对设备超时');
return <BluetoothDevice>[];
});
_systemPairedDevices.value = pairedDevices;
Logger.info('系统已配对设备数量: ${_systemPairedDevices.length}');
} catch (e) {
Logger.error('获取已配对设备失败: $e');
}
// 如果有已连接的设备但没有选中设备,自动选择第一个
if (_connectedDevices.isNotEmpty && _selectedDevice.value == null) {
Logger.info('自动选择第一个已连接设备: ${_connectedDevices[0].platformName}');
_selectedDevice.value = _connectedDevices[0];
_determineDeviceType(_selectedDevice.value!);
_connectionState.value = BluetoothConnectionState.connected;
_updateLastConnectedDeviceInfo();
}
_isRefreshing = false;
} catch (e) {
Logger.error('刷新系统设备状态失败: $e');
}
}
/// 确定设备类型(经典蓝牙/BLE/双模)
void _determineDeviceType(BluetoothDevice device) {
try {
// 默认设为未知类型
_deviceType.value = DeviceType.unknown;
// 设备名称包含耳机/headphone/earphone/earbuds等关键词可能是音频设备
final String deviceName = device.platformName.toLowerCase();
final List<String> audioKeywords = [
'headphone', 'earphone', 'earbuds', 'headset',
'speaker', 'audio', '耳机', '音箱', '音响'
];
bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword));
// 如果在已连接设备列表中,很可能是已经通过系统连接的经典蓝牙
bool inConnectedList = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
if (hasAudioKeyword || inConnectedList) {
_deviceType.value = DeviceType.classicAudio;
Logger.info('设备${device.platformName}被判定为经典蓝牙音频设备');
} else {
// 不确定的情况,先假设是BLE设备,连接后可能会更新
_deviceType.value = DeviceType.ble;
Logger.info('设备${device.platformName}被判定为BLE设备');
}
} catch (e) {
Logger.error('确定设备类型失败: $e');
_deviceType.value = DeviceType.unknown;
}
}
/// 开始扫描附近的蓝牙设备
Future<List<ScanResult>> startScan({Duration? timeout}) async {
if (_isScanning.value) {
await stopScan();
}
// 清空旧的扫描结果
_scanResults.clear();
try {
// 确保拥有扫描权限
if (Platform.isAndroid && !await Permission.bluetoothScan.isGranted) {
Logger.warning('没有蓝牙扫描权限,尝试请求权限');
await _checkAndRequestPermissions();
}
// 设置扫描监听
_scanSubscription?.cancel();
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
_scanResults.value = results;
});
// 设置扫描中状态
_isScanning.value = true;
// 开始扫描
await FlutterBluePlus.startScan(
timeout: timeout ?? const Duration(seconds: 5),
);
// 扫描完成
_isScanning.value = false;
Logger.info('蓝牙扫描完成,发现 ${_scanResults.length} 个设备');
return _scanResults;
} catch (e) {
_isScanning.value = false;
Logger.error('蓝牙扫描失败: $e');
return [];
}
}
/// 停止扫描
Future<void> stopScan() async {
if (!_isScanning.value) return;
try {
await FlutterBluePlus.stopScan();
_isScanning.value = false;
} catch (e) {
Logger.error('停止蓝牙扫描失败: $e');
}
}
/// 连接到指定的蓝牙设备
/// 针对不同类型设备使用不同的连接策略
Future<bool> connectToDevice(BluetoothDevice device) async {
// 防止并发连接操作
if (_isConnecting) {
Logger.warning('已有正在进行的连接操作,请等待完成');
return false;
}
_isConnecting = true;
_connectionAttempts = 0;
try {
Logger.info('尝试连接设备: ${device.platformName} (${device.remoteId.str})');
// 确定设备类型
_determineDeviceType(device);
// 检查设备是否已在系统连接列表中
bool alreadyConnected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
// 如果是当前已选择且已连接的设备,直接返回成功
if (_selectedDevice.value == device && alreadyConnected) {
Logger.info('设备已连接,无需重新连接');
_connectionState.value = BluetoothConnectionState.connected;
_updateLastConnectedDeviceInfo();
_isConnecting = false;
return true;
}
// 如果有其他已选择的设备,先取消监听并清除
if (_selectedDevice.value != null && _selectedDevice.value != device) {
_connectionSubscription?.cancel();
_connectionSubscription = null;
}
// 更新选中的设备
_selectedDevice.value = device;
// 对于经典蓝牙音频设备,不尝试BLE连接,直接检查系统连接状态
if (_deviceType.value == DeviceType.classicAudio) {
Logger.info('经典蓝牙音频设备,检查系统连接状态');
// 刷新系统连接设备列表
await _refreshSystemDevices();
// 再次检查设备是否在已连接列表中
alreadyConnected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
if (alreadyConnected) {
Logger.info('设备已在系统连接列表中,视为已连接');
_connectionState.value = BluetoothConnectionState.connected;
_monitorSystemConnectionState(device);
_updateLastConnectedDeviceInfo();
_isConnecting = false;
return true;
} else {
Logger.info('经典蓝牙设备未在系统连接列表中,需要在系统设置中配对和连接');
_connectionState.value = BluetoothConnectionState.disconnected;
_isConnecting = false;
return false;
}
}
// 对于BLE或双模设备,尝试进行BLE连接
Logger.info('尝试进行BLE连接');
return await _attemptBleConnection(device);
} catch (e) {
Logger.error('连接设备 ${device.platformName} 失败: $e');
_connectionState.value = BluetoothConnectionState.disconnected;
_isConnecting = false;
return false;
}
}
/// 尝试进行BLE连接
Future<bool> _attemptBleConnection(BluetoothDevice device) async {
try {
_connectionState.value = BluetoothConnectionState.connecting;
// 增加连接尝试次数
_connectionAttempts++;
// 不使用autoConnect=true来避免与mtu参数冲突
// 根据错误日志,mtu和autoConnect是不兼容的
bool autoConnect = false; // 始终使用false避免兼容性问题
Logger.info('BLE连接尝试 #$_connectionAttempts,autoConnect=$autoConnect');
// 开始监听连接状态
_monitorConnectionState(device);
// 尝试连接,不同的尝试使用不同的超时时间
if (_connectionAttempts == 1) {
// 第一次尝试:短超时
Logger.info('第一次尝试使用短超时(4秒)');
await device.connect(
autoConnect: autoConnect,
timeout: const Duration(seconds: 4)
);
} else {
// 第二次尝试:长超时
Logger.info('第二次尝试使用长超时(8秒)');
await device.connect(
autoConnect: autoConnect,
timeout: const Duration(seconds: 8)
);
}
// 如果连接成功,更新状态
_connectionState.value = BluetoothConnectionState.connected;
_updateLastConnectedDeviceInfo();
Logger.info('设备 ${device.platformName} 连接成功');
_isConnecting = false;
return true;
} catch (e) {
// 连接失败,检查是否需要重试
Logger.error('BLE连接尝试 #$_connectionAttempts 失败: $e');
if (_connectionAttempts < MAX_CONNECTION_ATTEMPTS) {
Logger.info('尝试使用不同参数重新连接');
// 断开连接并等待一段时间
try {
await device.disconnect();
} catch (disconnectError) {
// 忽略断开连接的错误
Logger.error('断开连接时出错,继续尝试: $disconnectError');
}
// 等待时间更长,确保设备和系统状态重置
await Future.delayed(const Duration(milliseconds: 2000));
// 重试连接
return await _attemptBleConnection(device);
} else {
// 连接失败且达到最大尝试次数
_connectionState.value = BluetoothConnectionState.disconnected;
_isConnecting = false;
return false;
}
}
}
/// 断开与当前设备的连接
Future<bool> disconnectFromDevice() async {
if (_selectedDevice.value == null) {
Logger.info('没有当前选中的设备,无需断开连接');
return true;
}
// 取消设备监听
_connectionSubscription?.cancel();
_connectionSubscription = null;
try {
// 对于经典蓝牙设备,不调用disconnect
if (_deviceType.value == DeviceType.classicAudio) {
Logger.info('经典蓝牙设备,仅清除选择状态,不调用disconnect');
_selectedDevice.value = null;
_connectionState.value = BluetoothConnectionState.disconnected;
return true;
}
// 对于BLE设备,调用disconnect
Logger.info('正在断开设备 ${_selectedDevice.value!.platformName} 的连接');
await _selectedDevice.value!.disconnect();
_selectedDevice.value = null;
_connectionState.value = BluetoothConnectionState.disconnected;
return true;
} catch (e) {
Logger.error('断开蓝牙设备连接失败: $e');
// 即使失败也清除状态
_selectedDevice.value = null;
_connectionState.value = BluetoothConnectionState.disconnected;
return false;
}
}
/// 监控BLE连接状态
void _monitorConnectionState(BluetoothDevice device) {
Logger.info('设置设备 ${device.platformName} 的BLE连接状态监听');
// 取消之前的监听
_connectionSubscription?.cancel();
// 设置新的监听
_connectionSubscription = device.connectionState.listen((state) {
Logger.info('设备 ${device.platformName} BLE连接状态变更为: $state');
_connectionState.value = state;
if (state == BluetoothConnectionState.disconnected) {
Logger.info('设备 ${device.platformName} BLE连接已断开');
// 设备断开连接时,不清除设备信息,以便下次可以重新连接
} else if (state == BluetoothConnectionState.connected) {
Logger.info('设备 ${device.platformName} BLE连接已成功');
// 设备连接成功时,更新最后连接的设备信息
_updateLastConnectedDeviceInfo();
}
});
}
/// 监控系统连接状态(针对经典蓝牙设备)
void _monitorSystemConnectionState(BluetoothDevice device) {
Logger.info('设置设备 ${device.platformName} 的系统连接状态监听');
// 使用定时器定期检查系统连接状态
Timer.periodic(const Duration(seconds: 5), (timer) async {
// 如果设备已变更或服务关闭,停止监控
if (_selectedDevice.value == null ||
_selectedDevice.value!.remoteId.str != device.remoteId.str) {
timer.cancel();
return;
}
try {
await _refreshSystemDevices();
// 检查设备是否仍在系统连接列表中
bool stillConnected = _connectedDevices.any(
(d) => d.remoteId.str == device.remoteId.str
);
if (stillConnected && _connectionState.value != BluetoothConnectionState.connected) {
Logger.info('设备 ${device.platformName} 在系统连接列表中,更新为已连接状态');
_connectionState.value = BluetoothConnectionState.connected;
_updateLastConnectedDeviceInfo();
} else if (!stillConnected && _connectionState.value == BluetoothConnectionState.connected) {
Logger.info('设备 ${device.platformName} 不在系统连接列表中,更新为断开状态');
_connectionState.value = BluetoothConnectionState.disconnected;
}
} catch (e) {
Logger.error('监控系统连接状态失败: $e');
}
});
}
/// 获取已连接设备的信息
Map<String, dynamic> getConnectedDeviceInfo() {
if (_selectedDevice.value == null) {
return {
'connected': false,
'device': null,
'deviceType': _deviceType.value.toString().split('.').last
};
}
final deviceInfo = {
'connected': _connectionState.value == BluetoothConnectionState.connected,
'device': {
'name': _selectedDevice.value!.platformName.isEmpty ?
'未命名设备' : _selectedDevice.value!.platformName,
'id': _selectedDevice.value!.remoteId.str,
'isConnected': _connectionState.value == BluetoothConnectionState.connected,
'rssi': _scanResults
.firstWhereOrNull((r) => r.device.remoteId == _selectedDevice.value!.remoteId)
?.rssi
},
'deviceType': _deviceType.value.toString().split('.').last
};
// 更新最后连接的设备信息
_lastConnectedDeviceInfo.value = deviceInfo;
return deviceInfo;
}
/// 更新最后连接的设备信息
void _updateLastConnectedDeviceInfo() {
if (_selectedDevice.value != null) {
_lastConnectedDeviceInfo.value = getConnectedDeviceInfo();
}
}
/// 一个综合方法,查找且尝试使用系统已连接的设备
/// 针对经典蓝牙耳机优化,避免使用BLE连接
Future<BluetoothDevice?> findConnectedSystemDevice() async {
try {
// 先刷新系统设备列表
await _refreshSystemDevices();
// 1. 检查当前是否有已选择且已连接的设备
if (_selectedDevice.value != null &&
_connectionState.value == BluetoothConnectionState.connected) {
Logger.info('使用当前已连接设备: ${_selectedDevice.value!.platformName}');
return _selectedDevice.value;
}
// 2. 检查系统已连接设备列表
if (_connectedDevices.isNotEmpty) {
// 优先选择可能是音频设备的
BluetoothDevice audioDevice = _connectedDevices.firstWhere(
(device) {
final name = device.platformName.toLowerCase();
return name.contains('headphone') ||
name.contains('earphone') ||
name.contains('earbuds') ||
name.contains('headset') ||
name.contains('speaker') ||
name.contains('耳机') ||
name.contains('音箱');
},
orElse: () => _connectedDevices.first
);
Logger.info('在系统已连接设备中找到设备: ${audioDevice.platformName}');
// 设置为当前设备并更新状态
_selectedDevice.value = audioDevice;
_determineDeviceType(audioDevice);
_connectionState.value = BluetoothConnectionState.connected;
_monitorSystemConnectionState(audioDevice);
_updateLastConnectedDeviceInfo();
return audioDevice;
}
// 3. 如果没有已连接设备,检查已配对设备
return null;
} catch (e) {
Logger.error('查找系统已连接设备失败: $e');
return null;
}
}
/// 获取设备电量
Future<int?> getBatteryLevel() async {
if (_selectedDevice.value == null ||
_connectionState.value != BluetoothConnectionState.connected) {
return null;
}
try {
// 对于BLE设备,尝试读取电池服务
if (_deviceType.value == DeviceType.ble || _deviceType.value == DeviceType.dual) {
// 发现设备服务
List<dynamic> services = await _selectedDevice.value!.discoverServices();
// 查找电池服务
for (var service in services) {
if (service.uuid.toString().toLowerCase() == BATTERY_SERVICE_UUID) {
// 查找电池特征
for (var characteristic in service.characteristics) {
if (characteristic.uuid.toString().toLowerCase() == BATTERY_CHARACTERISTIC_UUID) {
// 读取电池电量
List<int> value = await characteristic.read();
if (value.isNotEmpty) {
return value[0]; // 电量百分比
}
}
}
}
}
}
// 对于经典蓝牙设备,可能需要平台特定的方法
if (_deviceType.value == DeviceType.classicAudio) {
// 这里需要使用平台通道或其他插件获取经典蓝牙设备电量
// 目前根据设备名称特征尝试估计电量
// 部分设备会在名称后附加电量信息,如"XXX耳机(80%)"
if (_selectedDevice.value != null) {
final deviceName = _selectedDevice.value!.platformName;
final percentRegex = RegExp(r'(\d+)%');
final match = percentRegex.firstMatch(deviceName);
if (match != null && match.groupCount >= 1) {
final percentStr = match.group(1);
if (percentStr != null) {
final percent = int.tryParse(percentStr);
if (percent != null && percent >= 0 && percent <= 100) {
return percent;
}
}
}
}
Logger.info('无法从设备名称中提取电量信息');
}
// 如果以上方法都无法获取电量,返回null
return null;
} catch (e) {
Logger.error('获取设备电量失败: $e');
return null;
}
}
@override
void onClose() {
_stateSubscription?.cancel();
_scanSubscription?.cancel();
_connectionSubscription?.cancel();
disconnectFromDevice();
super.onClose();
}
}