16 changed files with 385 additions and 413 deletions
@ -1,7 +0,0 @@ |
|||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||
<!-- The INTERNET permission is required for development. Specifically, |
|
||||
the Flutter tool needs it to communicate with the running application |
|
||||
to allow setting breakpoints, to provide hot reload, etc. |
|
||||
--> |
|
||||
<uses-permission android:name="android.permission.INTERNET"/> |
|
||||
</manifest> |
|
||||
@ -1,243 +0,0 @@ |
|||||
import 'dart:async'; |
|
||||
import 'package:flutter/services.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
import 'package:permission_handler/permission_handler.dart'; |
|
||||
import '../../core/utils/logger.dart'; |
|
||||
import 'dart:io'; |
|
||||
|
|
||||
/// 蓝牙设备信息 |
|
||||
class BluetoothDevice { |
|
||||
final String name; |
|
||||
final String address; |
|
||||
final String type; |
|
||||
|
|
||||
BluetoothDevice({ |
|
||||
required this.name, |
|
||||
required this.address, |
|
||||
required this.type |
|
||||
}); |
|
||||
|
|
||||
factory BluetoothDevice.fromMap(Map<dynamic, dynamic> map) { |
|
||||
return BluetoothDevice( |
|
||||
name: map['name'] ?? '未知设备', |
|
||||
address: map['address'] ?? '', |
|
||||
type: map['type'] ?? 'unknown', |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
String toString() => 'BluetoothDevice(name: $name, address: $address, type: $type)'; |
|
||||
} |
|
||||
|
|
||||
/// 经典蓝牙服务 |
|
||||
/// 负责经典蓝牙设备的连接状态管理 |
|
||||
class ClassicBluetoothService extends GetxService { |
|
||||
static ClassicBluetoothService get to => Get.find<ClassicBluetoothService>(); |
|
||||
|
|
||||
// 通道名称 |
|
||||
final _methodChannel = const MethodChannel('com.yunqiinnovation.classic_bluetooth/methods'); |
|
||||
final _eventChannel = const EventChannel('com.yunqiinnovation.classic_bluetooth/events'); |
|
||||
|
|
||||
/// 当前连接的设备 |
|
||||
final currentDevice = Rx<BluetoothDevice?>(null); |
|
||||
|
|
||||
/// 已连接耳机设备列表 |
|
||||
final connectedDevices = <BluetoothDevice>[].obs; |
|
||||
|
|
||||
/// 蓝牙事件监听 |
|
||||
StreamSubscription? _eventSubscription; |
|
||||
|
|
||||
/// 蓝牙开关状态 |
|
||||
final bluetoothState = 'unknown'.obs; |
|
||||
|
|
||||
/// 蓝牙是否可用 |
|
||||
bool get isBluetoothEnabled => bluetoothState.value == 'on'; |
|
||||
|
|
||||
/// 服务是否已初始化 |
|
||||
bool _isInitialized = false; |
|
||||
|
|
||||
/// 初始化蓝牙服务 |
|
||||
Future<bool> initialize() async { |
|
||||
if (_isInitialized) { |
|
||||
Logger.info('蓝牙服务已经初始化,跳过'); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
Logger.info('正在初始化蓝牙服务...'); |
|
||||
|
|
||||
// 检查蓝牙是否启用 |
|
||||
final bool isEnabled = await _methodChannel.invokeMethod<bool>('isBluetoothEnabled') ?? false; |
|
||||
bluetoothState.value = isEnabled ? 'on' : 'off'; |
|
||||
Logger.info('初始蓝牙状态: ${bluetoothState.value}'); |
|
||||
|
|
||||
// 设置事件监听 |
|
||||
_setupEventListener(); |
|
||||
|
|
||||
_isInitialized = true; |
|
||||
Logger.info('蓝牙服务初始化成功'); |
|
||||
|
|
||||
// 如果蓝牙已启用,自动获取已连接设备 |
|
||||
if (isEnabled) { |
|
||||
await refreshConnectedDevices(); |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} catch (e) { |
|
||||
Logger.error('初始化蓝牙服务失败: $e'); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 设置事件监听 |
|
||||
void _setupEventListener() { |
|
||||
_eventSubscription?.cancel(); |
|
||||
_eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) { |
|
||||
if (event is Map) { |
|
||||
Logger.info('收到蓝牙事件: $event'); |
|
||||
|
|
||||
// 根据事件类型处理 |
|
||||
final type = event['type'] as String?; |
|
||||
if (type == 'stateChanged') { |
|
||||
final state = event['state'] as String?; |
|
||||
if (state != null) { |
|
||||
_handleBluetoothStateChange(state); |
|
||||
} |
|
||||
} else if (type == 'deviceConnected') { |
|
||||
final deviceMap = event['device'] as Map?; |
|
||||
if (deviceMap != null) { |
|
||||
_handleDeviceConnected(deviceMap); |
|
||||
} |
|
||||
} else if (type == 'deviceDisconnected') { |
|
||||
final deviceMap = event['device'] as Map?; |
|
||||
if (deviceMap != null) { |
|
||||
_handleDeviceDisconnected(deviceMap); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}, onError: (e) { |
|
||||
Logger.error('蓝牙事件监听错误: $e'); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/// 刷新已连接的蓝牙设备 |
|
||||
Future<bool> refreshConnectedDevices() async { |
|
||||
// 确保服务已初始化 |
|
||||
if (!_isInitialized && !(await initialize())) { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// 如果蓝牙未启用,无法获取设备 |
|
||||
if (!isBluetoothEnabled) { |
|
||||
Logger.info('蓝牙未启用,无法获取设备'); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
// 获取已连接的设备 |
|
||||
final devices = await getConnectedDevices(); |
|
||||
|
|
||||
// 更新设备列表 |
|
||||
connectedDevices.value = devices; |
|
||||
|
|
||||
// 如果列表不为空且当前无选定设备,选择第一个 |
|
||||
if (devices.isNotEmpty && currentDevice.value == null) { |
|
||||
currentDevice.value = devices.first; |
|
||||
} |
|
||||
|
|
||||
return devices.isNotEmpty; |
|
||||
} catch (e) { |
|
||||
Logger.error('刷新已连接设备失败: $e'); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 获取已连接的蓝牙设备 |
|
||||
Future<List<BluetoothDevice>> getConnectedDevices() async { |
|
||||
try { |
|
||||
// 确保服务已初始化 |
|
||||
if (!_isInitialized && !(await initialize())) { |
|
||||
return []; |
|
||||
} |
|
||||
|
|
||||
final result = await _methodChannel.invokeMethod('getConnectedDevices'); |
|
||||
|
|
||||
if (result is List) { |
|
||||
return result.map((device) => BluetoothDevice.fromMap(device)).toList(); |
|
||||
} |
|
||||
|
|
||||
return []; |
|
||||
} catch (e) { |
|
||||
Logger.error('获取已连接设备失败: $e'); |
|
||||
return []; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 检查蓝牙是否已开启 |
|
||||
Future<bool> checkBluetoothEnabled() async { |
|
||||
try { |
|
||||
return await _methodChannel.invokeMethod<bool>('isBluetoothEnabled') ?? false; |
|
||||
} catch (e) { |
|
||||
Logger.error('检查蓝牙状态失败: $e'); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 处理设备连接事件 |
|
||||
void _handleDeviceConnected(Map deviceMap) { |
|
||||
final device = BluetoothDevice.fromMap(deviceMap); |
|
||||
|
|
||||
// 更新设备列表 |
|
||||
if (!connectedDevices.any((d) => d.address == device.address)) { |
|
||||
connectedDevices.add(device); |
|
||||
} |
|
||||
|
|
||||
// 更新当前设备 |
|
||||
currentDevice.value = device; |
|
||||
|
|
||||
Logger.info('设备已连接: ${device.name}'); |
|
||||
} |
|
||||
|
|
||||
/// 处理设备断开事件 |
|
||||
void _handleDeviceDisconnected(Map deviceMap) { |
|
||||
final address = deviceMap['address'] as String?; |
|
||||
|
|
||||
if (address != null) { |
|
||||
// 更新设备列表 |
|
||||
connectedDevices.removeWhere((d) => d.address == address); |
|
||||
|
|
||||
// 如果是当前设备,清除当前设备 |
|
||||
if (currentDevice.value?.address == address) { |
|
||||
currentDevice.value = connectedDevices.isEmpty ? null : connectedDevices.first; |
|
||||
Logger.info('当前设备已断开连接'); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// 处理蓝牙状态变化 |
|
||||
void _handleBluetoothStateChange(String state) { |
|
||||
Logger.info('蓝牙状态变化: $state'); |
|
||||
|
|
||||
// 更新蓝牙状态 |
|
||||
bluetoothState.value = state; |
|
||||
|
|
||||
if (state == 'off') { |
|
||||
// 蓝牙关闭,清空设备信息 |
|
||||
connectedDevices.clear(); |
|
||||
currentDevice.value = null; |
|
||||
} else if (state == 'on') { |
|
||||
// 蓝牙重新开启,延迟一小段时间后刷新设备 |
|
||||
Future.delayed(const Duration(milliseconds: 1000), () { |
|
||||
refreshConnectedDevices(); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
void onClose() { |
|
||||
// 取消事件监听 |
|
||||
_eventSubscription?.cancel(); |
|
||||
super.onClose(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,37 @@ |
|||||
|
plugins { |
||||
|
// Android Library 插件 |
||||
|
id("com.android.library") |
||||
|
// Kotlin Android 插件 |
||||
|
id("org.jetbrains.kotlin.android") |
||||
|
id("com.github.johnrengelman.shadow") version "8.1.1" |
||||
|
} |
||||
|
|
||||
|
android { |
||||
|
// 命名空间,对应你插件的包名(需与代码内包名保持一致) |
||||
|
namespace = "com.yunqiinnovation.classic_bluetooth" |
||||
|
|
||||
|
// 目标 SDK 版本 |
||||
|
compileSdk = 33 |
||||
|
|
||||
|
defaultConfig { |
||||
|
// 最低 SDK 版本 |
||||
|
minSdk = 21 |
||||
|
targetSdk = 33 |
||||
|
} |
||||
|
|
||||
|
// Java 语言级别兼容配置 |
||||
|
compileOptions { |
||||
|
sourceCompatibility = JavaVersion.VERSION_11 |
||||
|
targetCompatibility = JavaVersion.VERSION_11 |
||||
|
} |
||||
|
|
||||
|
// Kotlin 语言级别 |
||||
|
kotlinOptions { |
||||
|
jvmTarget = "11" |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
dependencies { |
||||
|
add("implementation", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10") |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
rootProject.name = "classic_bluetooth" |
||||
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
package="com.yunqiinnovation.classic_bluetooth"> |
||||
|
|
||||
|
<uses-permission android:name="android.permission.BLUETOOTH" /> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> |
||||
|
|
||||
|
<!-- For Android 12+ --> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> |
||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> |
||||
|
</manifest> |
||||
@ -0,0 +1,115 @@ |
|||||
|
import 'dart:async'; |
||||
|
import 'package:flutter/services.dart'; |
||||
|
|
||||
|
/// 经典蓝牙插件 |
||||
|
class ClassicBluetooth { |
||||
|
static const MethodChannel _methodChannel = |
||||
|
MethodChannel('com.yunqiinnovation.classic_bluetooth/methods'); |
||||
|
|
||||
|
static const EventChannel _eventChannel = |
||||
|
EventChannel('com.yunqiinnovation.classic_bluetooth/events'); |
||||
|
|
||||
|
static Stream<BluetoothEvent>? _eventStream; |
||||
|
|
||||
|
/// 获取已连接的蓝牙设备 |
||||
|
static Future<List<BluetoothDevice>> getConnectedDevices() async { |
||||
|
final List<dynamic> devices = await _methodChannel.invokeMethod('getConnectedDevices'); |
||||
|
return devices.map((device) => BluetoothDevice.fromJson(device)).toList(); |
||||
|
} |
||||
|
|
||||
|
/// 检查蓝牙是否已启用 |
||||
|
static Future<bool> isBluetoothEnabled() async { |
||||
|
return await _methodChannel.invokeMethod('isBluetoothEnabled'); |
||||
|
} |
||||
|
|
||||
|
/// 监听蓝牙事件(状态变化、设备连接/断开) |
||||
|
static Stream<BluetoothEvent> get events { |
||||
|
_eventStream ??= _eventChannel |
||||
|
.receiveBroadcastStream() |
||||
|
.map((dynamic event) => BluetoothEvent.fromJson(event)) |
||||
|
.handleError((error) { |
||||
|
print('[ClassicBluetooth] 事件流错误: $error'); |
||||
|
}); |
||||
|
|
||||
|
return _eventStream!; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 蓝牙设备模型 |
||||
|
class BluetoothDevice { |
||||
|
final String name; |
||||
|
final String address; |
||||
|
|
||||
|
BluetoothDevice({ |
||||
|
required this.name, |
||||
|
required this.address, |
||||
|
}); |
||||
|
|
||||
|
factory BluetoothDevice.fromJson(Map<dynamic, dynamic> json) { |
||||
|
return BluetoothDevice( |
||||
|
name: json['name'] as String, |
||||
|
address: json['address'] as String, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Map<String, dynamic> toJson() { |
||||
|
return { |
||||
|
'name': name, |
||||
|
'address': address, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
String toString() => 'BluetoothDevice(name: $name, address: $address)'; |
||||
|
} |
||||
|
|
||||
|
/// 简化的蓝牙事件类 |
||||
|
class BluetoothEvent { |
||||
|
/// 事件类型:stateChanged, deviceConnected, deviceDisconnected |
||||
|
final String type; |
||||
|
|
||||
|
/// 蓝牙状态: on, off, turningOn, turningOff, unknown |
||||
|
final String? state; |
||||
|
|
||||
|
/// 相关的蓝牙设备 |
||||
|
final BluetoothDevice? device; |
||||
|
|
||||
|
/// 事件发生时间 |
||||
|
final DateTime timestamp; |
||||
|
|
||||
|
BluetoothEvent({ |
||||
|
required this.type, |
||||
|
this.state, |
||||
|
this.device, |
||||
|
required this.timestamp, |
||||
|
}); |
||||
|
|
||||
|
factory BluetoothEvent.fromJson(Map<dynamic, dynamic> json) { |
||||
|
final String type = json['type'] as String; |
||||
|
final timestamp = DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int); |
||||
|
|
||||
|
// 处理设备相关事件 |
||||
|
BluetoothDevice? device; |
||||
|
if (json.containsKey('device')) { |
||||
|
device = BluetoothDevice.fromJson(json['device']); |
||||
|
} |
||||
|
|
||||
|
return BluetoothEvent( |
||||
|
type: type, |
||||
|
state: json['state'] as String?, |
||||
|
device: device, |
||||
|
timestamp: timestamp, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
String toString() { |
||||
|
if (type == 'stateChanged') { |
||||
|
return 'BluetoothEvent(type: $type, state: $state)'; |
||||
|
} else if (type == 'deviceConnected' || type == 'deviceDisconnected') { |
||||
|
return 'BluetoothEvent(type: $type, device: $device)'; |
||||
|
} else { |
||||
|
return 'BluetoothEvent(type: $type)'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
name: classic_bluetooth |
||||
|
description: 经典蓝牙设备连接和状态监控插件 |
||||
|
version: 0.0.1 |
||||
|
|
||||
|
environment: |
||||
|
sdk: ">=2.17.0 <3.0.0" |
||||
|
flutter: ">=2.5.0" |
||||
|
|
||||
|
dependencies: |
||||
|
flutter: |
||||
|
sdk: flutter |
||||
|
deepvoice_utils: |
||||
|
path: ../deepvoice_utils |
||||
|
|
||||
|
flutter: |
||||
|
plugin: |
||||
|
platforms: |
||||
|
android: |
||||
|
package: com.yunqiinnovation.classic_bluetooth |
||||
|
pluginClass: ClassicBluetoothPlugin |
||||
Loading…
Reference in new issue