feat: add background services and map status management
This commit is contained in:
211
lib/services/background_service.dart
Normal file
211
lib/services/background_service.dart
Normal file
@@ -0,0 +1,211 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:lbjconsole/services/ble_service.dart';
|
||||
|
||||
const String _notificationChannelId = 'lbj_console_channel';
|
||||
const String _notificationChannelName = 'LBJ Console 后台服务';
|
||||
const String _notificationChannelDescription = '保持蓝牙连接稳定';
|
||||
const int _notificationId = 114514;
|
||||
|
||||
class BackgroundService {
|
||||
static final FlutterBackgroundService _service = FlutterBackgroundService();
|
||||
static bool _isInitialized = false;
|
||||
|
||||
static Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
final service = FlutterBackgroundService();
|
||||
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||
_notificationChannelId,
|
||||
_notificationChannelName,
|
||||
description: _notificationChannelDescription,
|
||||
importance: Importance.low,
|
||||
enableLights: false,
|
||||
enableVibration: false,
|
||||
playSound: false,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
await service.configure(
|
||||
androidConfiguration: AndroidConfiguration(
|
||||
onStart: _onStart,
|
||||
autoStart: true,
|
||||
isForegroundMode: true,
|
||||
notificationChannelId: _notificationChannelId,
|
||||
initialNotificationTitle: 'LBJ Console',
|
||||
initialNotificationContent: '蓝牙连接监控中',
|
||||
foregroundServiceNotificationId: _notificationId,
|
||||
),
|
||||
iosConfiguration: IosConfiguration(
|
||||
autoStart: true,
|
||||
onForeground: _onStart,
|
||||
onBackground: _onIosBackground,
|
||||
),
|
||||
);
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
static void _onStart(ServiceInstance service) async {
|
||||
DartPluginRegistrant.ensureInitialized();
|
||||
|
||||
if (service is AndroidServiceInstance) {
|
||||
service.on('setAsForeground').listen((event) {
|
||||
service.setAsForegroundService();
|
||||
});
|
||||
|
||||
service.on('setAsBackground').listen((event) {
|
||||
service.setAsBackgroundService();
|
||||
});
|
||||
}
|
||||
|
||||
service.on('stopService').listen((event) {
|
||||
service.stopSelf();
|
||||
});
|
||||
|
||||
BLEService().initialize();
|
||||
|
||||
if (service is AndroidServiceInstance) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (await service.isForegroundService()) {
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
try {
|
||||
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||
_notificationChannelId,
|
||||
_notificationChannelName,
|
||||
description: _notificationChannelDescription,
|
||||
importance: Importance.low,
|
||||
enableLights: false,
|
||||
enableVibration: false,
|
||||
playSound: false,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
|
||||
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
_notificationId,
|
||||
'LBJ Console',
|
||||
'蓝牙连接监控中',
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_notificationChannelId,
|
||||
_notificationChannelName,
|
||||
channelDescription: _notificationChannelDescription,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
ongoing: true,
|
||||
autoCancel: false,
|
||||
importance: Importance.low,
|
||||
priority: Priority.low,
|
||||
enableLights: false,
|
||||
enableVibration: false,
|
||||
playSound: false,
|
||||
onlyAlertOnce: true,
|
||||
setAsGroupSummary: false,
|
||||
groupKey: 'lbj_console_group',
|
||||
visibility: NotificationVisibility.public,
|
||||
category: AndroidNotificationCategory.service,
|
||||
),
|
||||
),
|
||||
);
|
||||
print('前台服务通知显示成功');
|
||||
} catch (e) {
|
||||
print('前台服务通知显示失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||
if (service is AndroidServiceInstance) {
|
||||
if (await service.isForegroundService()) {
|
||||
try {
|
||||
final bleService = BLEService();
|
||||
final isConnected = bleService.isConnected;
|
||||
final deviceStatus = bleService.deviceStatus;
|
||||
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
_notificationId,
|
||||
'LBJ Console',
|
||||
isConnected ? '蓝牙已连接 - $deviceStatus' : '蓝牙未连接 - 自动重连中',
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
_notificationChannelId,
|
||||
_notificationChannelName,
|
||||
channelDescription: _notificationChannelDescription,
|
||||
icon: '@mipmap/ic_launcher',
|
||||
ongoing: true,
|
||||
autoCancel: false,
|
||||
importance: Importance.low,
|
||||
priority: Priority.low,
|
||||
enableLights: false,
|
||||
enableVibration: false,
|
||||
playSound: false,
|
||||
onlyAlertOnce: true,
|
||||
setAsGroupSummary: false,
|
||||
groupKey: 'lbj_console_group',
|
||||
visibility: NotificationVisibility.public,
|
||||
category: AndroidNotificationCategory.service,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
print('前台服务通知更新失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
static Future<bool> _onIosBackground(ServiceInstance service) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<void> startService() async {
|
||||
await initialize();
|
||||
final service = FlutterBackgroundService();
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
final isRunning = await service.isRunning();
|
||||
if (!isRunning) {
|
||||
service.startService();
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
service.startService();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> stopService() async {
|
||||
final service = FlutterBackgroundService();
|
||||
service.invoke('stopService');
|
||||
}
|
||||
|
||||
static Future<bool> isRunning() async {
|
||||
final service = FlutterBackgroundService();
|
||||
return await service.isRunning();
|
||||
}
|
||||
|
||||
static void setForegroundMode(bool isForeground) {
|
||||
final service = FlutterBackgroundService();
|
||||
if (isForeground) {
|
||||
service.invoke('setAsForeground');
|
||||
} else {
|
||||
service.invoke('setAsBackground');
|
||||
}
|
||||
}
|
||||
}
|
||||
113
lib/services/map_state_service.dart
Normal file
113
lib/services/map_state_service.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'dart:convert';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:lbjconsole/models/map_state.dart';
|
||||
import 'database_service.dart';
|
||||
|
||||
class MapStateService {
|
||||
static final MapStateService instance = MapStateService._internal();
|
||||
factory MapStateService() => instance;
|
||||
MapStateService._internal();
|
||||
|
||||
static const String _tableName = 'record_map_states';
|
||||
|
||||
final Map<String, MapState> _memoryCache = {};
|
||||
|
||||
Future<void> _ensureTableExists() async {
|
||||
final db = await DatabaseService.instance.database;
|
||||
await db.execute('''
|
||||
CREATE TABLE IF NOT EXISTS $_tableName (
|
||||
key TEXT PRIMARY KEY,
|
||||
state TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
String getSingleRecordMapKey(String recordId) {
|
||||
return "${recordId}_record_map";
|
||||
}
|
||||
|
||||
String getMergedRecordMapKey(String groupKey) {
|
||||
return "${groupKey}_group_map";
|
||||
}
|
||||
|
||||
Future<void> saveMapState(String key, MapState state) async {
|
||||
try {
|
||||
_memoryCache[key] = state;
|
||||
|
||||
final db = await DatabaseService.instance.database;
|
||||
await _ensureTableExists();
|
||||
|
||||
await db.insert(
|
||||
_tableName,
|
||||
{
|
||||
'key': key,
|
||||
'state': jsonEncode(state.toJson()),
|
||||
'updated_at': DateTime.now().millisecondsSinceEpoch,
|
||||
},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
} catch (e) {
|
||||
print('保存地图状态失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<MapState?> getMapState(String key) async {
|
||||
if (_memoryCache.containsKey(key)) {
|
||||
return _memoryCache[key];
|
||||
}
|
||||
|
||||
try {
|
||||
final db = await DatabaseService.instance.database;
|
||||
await _ensureTableExists();
|
||||
|
||||
final result = await db.query(
|
||||
_tableName,
|
||||
where: 'key = ?',
|
||||
whereArgs: [key],
|
||||
limit: 1,
|
||||
);
|
||||
|
||||
if (result.isNotEmpty) {
|
||||
final stateJson = jsonDecode(result.first['state'] as String);
|
||||
final state = MapState.fromJson(stateJson);
|
||||
_memoryCache[key] = state;
|
||||
return state;
|
||||
}
|
||||
} catch (e) {
|
||||
print('读取地图状态失败: $e');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> deleteMapState(String key) async {
|
||||
_memoryCache.remove(key);
|
||||
|
||||
try {
|
||||
final db = await DatabaseService.instance.database;
|
||||
await db.delete(
|
||||
_tableName,
|
||||
where: 'key = ?',
|
||||
whereArgs: [key],
|
||||
);
|
||||
} catch (e) {
|
||||
print('删除地图状态失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearAllMapStates() async {
|
||||
_memoryCache.clear();
|
||||
|
||||
try {
|
||||
final db = await DatabaseService.instance.database;
|
||||
await db.delete(_tableName);
|
||||
} catch (e) {
|
||||
print('清空地图状态失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void clearMemoryCache() {
|
||||
_memoryCache.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user