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