Refactoring UI

This commit is contained in:
2025-08-09 21:11:41 +08:00
parent c42562bc15
commit 4cb51a38cb
7 changed files with 864 additions and 579 deletions

View File

@ -2,34 +2,84 @@
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="HeartRateMonitorAndroid.MainPage" x:Class="HeartRateMonitorAndroid.MainPage"
BackgroundColor="#F8F9FA"> BackgroundColor="#F8F9FA"
Title="心率监测">
<Grid Padding="20" RowSpacing="20" ColumnSpacing="20"> <Grid Padding="20" RowSpacing="20">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- 状态和心率标签 --> <!-- 服务状态指示器 -->
<VerticalStackLayout Grid.Row="0" Spacing="10"> <Frame Grid.Row="0" BorderColor="#E9ECEF" CornerRadius="15" Padding="15"
<Label x:Name="statusLabel" BackgroundColor="White" HasShadow="True">
Text="正在初始化..." <Grid ColumnSpacing="15">
FontSize="18" <Grid.ColumnDefinitions>
TextColor="#6C757D" <ColumnDefinition Width="Auto"/>
HorizontalOptions="Center" /> <ColumnDefinition Width="*"/>
<Label x:Name="heartRateLabel" <ColumnDefinition Width="Auto"/>
Text="心率: -- bpm" </Grid.ColumnDefinitions>
FontSize="36"
FontAttributes="Bold" <!-- 状态指示灯 -->
TextColor="#FF4757" <Ellipse Grid.Column="0"
Margin="0,5,0,5" x:Name="statusIndicator"
HorizontalOptions="Center" /> Fill="#28A745"
</VerticalStackLayout> WidthRequest="16"
HeightRequest="16"
VerticalOptions="Center"/>
<!-- 状态文本 -->
<VerticalStackLayout Grid.Column="1" Spacing="2">
<Label x:Name="serviceStatusLabel"
Text="后台服务运行中"
FontSize="16"
FontAttributes="Bold"
TextColor="#495057"/>
<Label x:Name="connectionStatusLabel"
Text="正在连接心率设备..."
FontSize="14"
TextColor="#6C757D"/>
</VerticalStackLayout>
<!-- 数据上传状态 -->
<Label Grid.Column="2"
x:Name="uploadStatusLabel"
Text="已连接"
FontSize="12"
TextColor="#28A745"
VerticalOptions="Center"
HorizontalOptions="End"/>
</Grid>
</Frame>
<!-- 当前心率显示 -->
<Frame Grid.Row="1" BorderColor="#E9ECEF" CornerRadius="15" Padding="20"
BackgroundColor="White" HasShadow="True">
<VerticalStackLayout HorizontalOptions="Center" Spacing="5">
<Label Text="当前心率"
FontSize="16"
TextColor="#6C757D"
HorizontalOptions="Center"/>
<Label x:Name="heartRateLabel"
Text="-- bpm"
FontSize="48"
FontAttributes="Bold"
TextColor="#FF4757"
HorizontalOptions="Center"/>
<Label x:Name="lastUpdateLabel"
Text="等待数据..."
FontSize="12"
TextColor="#ADB5BD"
HorizontalOptions="Center"/>
</VerticalStackLayout>
</Frame>
<!-- 心率统计信息 --> <!-- 心率统计信息 -->
<Grid Grid.Row="1" ColumnSpacing="15"> <Grid Grid.Row="2" ColumnSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@ -37,75 +87,76 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- 最低心率 --> <!-- 最低心率 -->
<Frame Grid.Column="0" BorderColor="#E9ECEF" BackgroundColor="#E8F5FE" CornerRadius="12" Padding="10" HasShadow="True"> <Frame Grid.Column="0" BorderColor="#E9ECEF" BackgroundColor="#E8F5FE" CornerRadius="12" Padding="15" HasShadow="True">
<VerticalStackLayout HorizontalOptions="Center"> <VerticalStackLayout HorizontalOptions="Center" Spacing="3">
<Label Text="最低心率" FontSize="14" TextColor="#2E86DE" FontAttributes="Bold" HorizontalOptions="Center"/> <Label Text="最低" FontSize="12" TextColor="#2E86DE" FontAttributes="Bold" HorizontalOptions="Center"/>
<Label x:Name="minHeartRateLabel" Text="--" FontSize="24" TextColor="#0056B3" FontAttributes="Bold" HorizontalOptions="Center"/> <Label x:Name="minHeartRateLabel" Text="--" FontSize="20" TextColor="#0056B3" FontAttributes="Bold" HorizontalOptions="Center"/>
<Label Text="bpm" FontSize="12" TextColor="#2E86DE" HorizontalOptions="Center"/> <Label Text="bpm" FontSize="10" TextColor="#2E86DE" HorizontalOptions="Center"/>
</VerticalStackLayout> </VerticalStackLayout>
</Frame> </Frame>
<!-- 平均心率 --> <!-- 平均心率 -->
<Frame Grid.Column="1" BorderColor="#E9ECEF" BackgroundColor="#FFF5F5" CornerRadius="12" Padding="10" HasShadow="True"> <Frame Grid.Column="1" BorderColor="#E9ECEF" BackgroundColor="#FFF5F5" CornerRadius="12" Padding="15" HasShadow="True">
<VerticalStackLayout HorizontalOptions="Center"> <VerticalStackLayout HorizontalOptions="Center" Spacing="3">
<Label Text="平均心率" FontSize="14" TextColor="#FF4757" FontAttributes="Bold" HorizontalOptions="Center"/> <Label Text="平均" FontSize="12" TextColor="#FF4757" FontAttributes="Bold" HorizontalOptions="Center"/>
<Label x:Name="avgHeartRateLabel" Text="--" FontSize="24" TextColor="#E01B30" FontAttributes="Bold" HorizontalOptions="Center"/> <Label x:Name="avgHeartRateLabel" Text="--" FontSize="20" TextColor="#E01B30" FontAttributes="Bold" HorizontalOptions="Center"/>
<Label Text="bpm" FontSize="12" TextColor="#FF4757" HorizontalOptions="Center"/> <Label Text="bpm" FontSize="10" TextColor="#FF4757" HorizontalOptions="Center"/>
</VerticalStackLayout> </VerticalStackLayout>
</Frame> </Frame>
<!-- 最高心率 --> <!-- 最高心率 -->
<Frame Grid.Column="2" BorderColor="#E9ECEF" BackgroundColor="#FFF0E5" CornerRadius="12" Padding="10" HasShadow="True"> <Frame Grid.Column="2" BorderColor="#E9ECEF" BackgroundColor="#FFF0E5" CornerRadius="12" Padding="15" HasShadow="True">
<VerticalStackLayout HorizontalOptions="Center"> <VerticalStackLayout HorizontalOptions="Center" Spacing="3">
<Label Text="最高心率" FontSize="14" TextColor="#FF8832" FontAttributes="Bold" HorizontalOptions="Center"/> <Label Text="最高" FontSize="12" TextColor="#FF8832" FontAttributes="Bold" HorizontalOptions="Center"/>
<Label x:Name="maxHeartRateLabel" Text="--" FontSize="24" TextColor="#E65C00" FontAttributes="Bold" HorizontalOptions="Center"/> <Label x:Name="maxHeartRateLabel" Text="--" FontSize="20" TextColor="#E65C00" FontAttributes="Bold" HorizontalOptions="Center"/>
<Label Text="bpm" FontSize="12" TextColor="#FF8832" HorizontalOptions="Center"/> <Label Text="bpm" FontSize="10" TextColor="#FF8832" HorizontalOptions="Center"/>
</VerticalStackLayout> </VerticalStackLayout>
</Frame> </Frame>
</Grid> </Grid>
<!-- 心率图表 --> <!-- 心率图表 -->
<Frame Grid.Row="2" BorderColor="#E9ECEF" CornerRadius="15" Padding="10" Margin="0,5,0,10" <Frame Grid.Row="3" BorderColor="#E9ECEF" CornerRadius="15" Padding="15"
BackgroundColor="#F8F9FA" HasShadow="True"> BackgroundColor="White" HasShadow="True">
<Grid> <Grid>
<GraphicsView x:Name="heartRateGraphicsView" HeightRequest="250" WidthRequest="300" BackgroundColor="#F8F9FA"/> <GraphicsView x:Name="heartRateGraphicsView" BackgroundColor="White"/>
<Label x:Name="noDataLabel" Text="暂无数据" HorizontalOptions="Center" VerticalOptions="Center" <VerticalStackLayout x:Name="noDataLayout"
IsVisible="True" TextColor="#6C757D" FontSize="16" FontAttributes="Italic"/> HorizontalOptions="Center"
VerticalOptions="Center"
IsVisible="True"
Spacing="10">
<Label Text="📊" FontSize="40" HorizontalOptions="Center"/>
<Label Text="等待心率数据"
HorizontalOptions="Center"
TextColor="#6C757D"
FontSize="16"/>
<Label Text="后台服务将自动连接设备并获取数据"
HorizontalOptions="Center"
TextColor="#ADB5BD"
FontSize="12"
HorizontalTextAlignment="Center"/>
</VerticalStackLayout>
</Grid> </Grid>
</Frame> </Frame>
<!-- 按钮区域 --> <!-- 会话信息 -->
<Grid Grid.Row="3" ColumnSpacing="15" RowSpacing="15"> <Frame Grid.Row="4" BorderColor="#E9ECEF" CornerRadius="15" Padding="15"
<Grid.RowDefinitions> BackgroundColor="White" HasShadow="True">
<RowDefinition Height="Auto"/> <Grid ColumnSpacing="20">
<RowDefinition Height="Auto"/> <Grid.ColumnDefinitions>
</Grid.RowDefinitions> <ColumnDefinition Width="*"/>
<Grid.ColumnDefinitions> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> </Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Text="开始扫描" Clicked="OnScanClicked" <VerticalStackLayout Grid.Column="0" Spacing="3">
HorizontalOptions="FillAndExpand" <Label Text="监测时长" FontSize="12" TextColor="#6C757D" FontAttributes="Bold"/>
BackgroundColor="#2E86DE" <Label x:Name="durationLabel" Text="00:00:00" FontSize="16" TextColor="#495057" FontAttributes="Bold"/>
TextColor="White" </VerticalStackLayout>
FontAttributes="Bold"
CornerRadius="10" <VerticalStackLayout Grid.Column="1" Spacing="3">
HeightRequest="50"/> <Label Text="数据点数" FontSize="12" TextColor="#6C757D" FontAttributes="Bold"/>
<Button Grid.Row="0" Grid.Column="1" x:Name="backgroundButton" Text="后台运行" Clicked="OnBackgroundClicked" <Label x:Name="dataPointsLabel" Text="0" FontSize="16" TextColor="#495057" FontAttributes="Bold"/>
HorizontalOptions="FillAndExpand" </VerticalStackLayout>
BackgroundColor="#FF4757" </Grid>
TextColor="White" </Frame>
FontAttributes="Bold"
CornerRadius="10"
HeightRequest="50"/>
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="webSocketButton" Text="配置数据上传" Clicked="OnWebSocketSettingsClicked"
HorizontalOptions="FillAndExpand"
BackgroundColor="#6C757D"
TextColor="White"
FontAttributes="Bold"
CornerRadius="10"
HeightRequest="50"/>
</Grid>
</Grid> </Grid>
</ContentPage> </ContentPage>

View File

@ -8,318 +8,224 @@ namespace HeartRateMonitorAndroid;
public partial class MainPage : ContentPage public partial class MainPage : ContentPage
{ {
private const string Tag = "HeartRateMonitor"; private const string Tag = "HeartRateMonitor";
private const int GraphUpdateIntervalMs = 1000; // 每秒更新一次图表 private const int UIUpdateIntervalMs = 1000; // 每秒更新一次UI
private const string DefaultWebsocketUrl = "wss://ws.nuanr-mxi.com/ws"; // 默认WebSocket服务器地址
// 图表更新定时器 // UI更新定时器
private IDispatcherTimer _graphUpdateTimer; private IDispatcherTimer _uiUpdateTimer;
// WebSocket相关 // 数据<EFBFBD><EFBFBD>
private WebSocketService.HeartRateWebSocketClient _webSocketClient; private readonly IHeartRateDataService _dataService;
private bool _isWebSocketEnabled = false;
private string _webSocketUrl = DefaultWebsocketUrl;
// 后台模式标志 // 心率数据图表
private bool _isRunningInBackground = false;
// 服务
private readonly BluetoothService _bluetoothService;
// 心率数据模型
private readonly HeartRateSessionData _sessionData = new();
private readonly HeartRateGraphDrawable _heartRateGraph = new(); private readonly HeartRateGraphDrawable _heartRateGraph = new();
// 会话开始时间
private DateTime _sessionStartTime = DateTime.Now;
public MainPage() public MainPage()
{ {
InitializeComponent(); InitializeComponent();
// 初始化蓝牙服务 // 获取数据服务实例
_bluetoothService = new BluetoothService(); _dataService = HeartRateDataService.Instance;
_bluetoothService.StatusUpdated += UpdateStatus;
_bluetoothService.HeartRateUpdated += UpdateHeartRate;
_bluetoothService.DeviceDiscovered += OnHeartRateDeviceDiscovered;
// 初始化图表 // 初始化图表
heartRateGraphicsView.Drawable = _heartRateGraph; heartRateGraphicsView.Drawable = _heartRateGraph;
// 初始化图表更新定时器 // 订阅服务事件
InitializeGraphUpdateTimer(); SubscribeToServiceEvents();
// 检查蓝牙状态 // 初始化UI更新定时器
_bluetoothService.CheckBluetoothState(); InitializeUITimer();
// 启动后台服务
_ = StartBackgroundServiceAsync();
} }
/// <summary> /// <summary>
/// 初始化图表更新定时器 /// 订阅服务事件
/// </summary> /// </summary>
private void InitializeGraphUpdateTimer() private void SubscribeToServiceEvents()
{ {
_graphUpdateTimer = Dispatcher.CreateTimer(); _dataService.HeartRateDataReceived += OnHeartRateDataReceived;
_graphUpdateTimer.Interval = TimeSpan.FromMilliseconds(GraphUpdateIntervalMs); _dataService.ServiceStatusChanged += OnServiceStatusChanged;
_graphUpdateTimer.Tick += async (s, e) => await UpdateGraph(); _dataService.DeviceStatusChanged += OnDeviceStatusChanged;
_graphUpdateTimer.Start();
} }
/// <summary> /// <summary>
/// 更新图表和通知 /// 初始化UI更新定时器
/// </summary> /// </summary>
private async Task UpdateGraph() private void InitializeUITimer()
{ {
// 发送心率数据到服务器 _uiUpdateTimer = Dispatcher.CreateTimer();
await SendHeartRateToServerAsync(_sessionData.LatestHeartRate); _uiUpdateTimer.Interval = TimeSpan.FromMilliseconds(UIUpdateIntervalMs);
_uiUpdateTimer.Tick += async (s, e) => await UpdateUI();
_uiUpdateTimer.Start();
}
/// <summary>
/// 启动后台服务
/// </summary>
private async Task StartBackgroundServiceAsync()
{
try
{
UpdateServiceStatusUI("正在启动后台服务...", false);
await _dataService.StartBackgroundServiceAsync();
Debug.WriteLine($"{Tag}: 后台服务启动请求已发送");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 启动后台服务失败: {ex.Message}");
UpdateServiceStatusUI("服务启动失败", false);
}
}
/// <summary>
/// 更新UI界面
/// </summary>
private async Task UpdateUI()
{
var sessionData = _dataService.GetCurrentSessionData();
// 更新图表 // 更新图表
_heartRateGraph.UpdateData(_sessionData.HeartRateData); if (sessionData.HasNewHeartRateData)
heartRateGraphicsView.Invalidate();
// 重置新数据标记
_sessionData.ResetNewDataFlag();
// 如果在后台运行且有数据,更新通知
if (_isRunningInBackground && _sessionData.HeartRateData.Count > 0)
{ {
var duration = _sessionData.GetSessionDuration(); _heartRateGraph.UpdateData(sessionData.HeartRateData);
NotificationService.ShowHeartRateNotification( heartRateGraphicsView.Invalidate();
_sessionData.LatestHeartRate, sessionData.ResetNewDataFlag();
_sessionData.AverageHeartRate,
_sessionData.MinHeartRate,
_sessionData.MaxHeartRate,
duration);
} }
// 更新会话时长
var duration = DateTime.Now - _sessionStartTime;
durationLabel.Text = $"{duration:hh\\:mm\\:ss}";
// 更新数据点数
dataPointsLabel.Text = sessionData.HeartRateData.Count.ToString();
await Task.CompletedTask;
} }
/// <summary> /// <summary>
/// 更新状态标签 /// 心率数据接收事件处理
/// </summary> /// </summary>
private void UpdateStatus(string status) private void OnHeartRateDataReceived(int heartRate)
{ {
MainThread.BeginInvokeOnMainThread(() => { MainThread.BeginInvokeOnMainThread(() => {
statusLabel.Text = status; try
{
var sessionData = _dataService.GetCurrentSessionData();
// 更新当前心率显示
heartRateLabel.Text = $"{heartRate} bpm";
lastUpdateLabel.Text = $"更新时间: {DateTime.Now:HH:mm:ss}";
// 隐藏无数据提示
if (sessionData.HeartRateData.Count >= 1)
{
noDataLayout.IsVisible = false;
}
// 更新统计信息
minHeartRateLabel.Text = sessionData.MinHeartRate.ToString();
maxHeartRateLabel.Text = sessionData.MaxHeartRate.ToString();
avgHeartRateLabel.Text = sessionData.AverageHeartRate.ToString("0");
//Debug.WriteLine($"{Tag}: UI已更新心率数据: {heartRate} bpm");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 更新心率UI失败: {ex.Message}");
}
}); });
} }
/// <summary> /// <summary>
/// 更新心率数据 /// 服务状态变化事件处理
/// </summary> /// </summary>
private void UpdateHeartRate(int heartRate) private void OnServiceStatusChanged(ServiceStatus status)
{ {
MainThread.BeginInvokeOnMainThread(() => { MainThread.BeginInvokeOnMainThread(() => {
// 更新UI上的心率显示 try
heartRateLabel.Text = $"心率: {heartRate} BPM";
// 添加新的心率数据点
_sessionData.AddHeartRate(heartRate);
// 如果是第一个数据点,隐藏提示标签
if (_sessionData.HeartRateData.Count == 1)
{ {
noDataLabel.IsVisible = false; UpdateServiceStatusUI(status.StatusMessage, status.IsRunning);
UpdateUploadStatusUI(status.IsWebSocketConnected ? "已连接" : "未连接", status.IsWebSocketConnected);
Debug.WriteLine($"{Tag}: 服务状态已更新: {status.StatusMessage}");
} }
catch (Exception ex)
// 更新统计信息显示
minHeartRateLabel.Text = _sessionData.MinHeartRate.ToString();
maxHeartRateLabel.Text = _sessionData.MaxHeartRate.ToString();
avgHeartRateLabel.Text = _sessionData.AverageHeartRate.ToString("0");
});
}
/// <summary>
/// 扫描按钮点击事件
/// </summary>
private async void OnScanClicked(object sender, EventArgs e)
{
await _bluetoothService.StartScanAsync();
}
/// <summary>
/// 心率设备发现事件处理
/// </summary>
private async void OnHeartRateDeviceDiscovered(Plugin.BLE.Abstractions.Contracts.IDevice device)
{
await MainThread.InvokeOnMainThreadAsync(async () => {
statusLabel.Text = $"检测到心率设备: {device.Name ?? ""}";
await _bluetoothService.ConnectToDeviceAsync(device);
// 连接成功后重置心率数据显示
if (_bluetoothService.ConnectedDevice != null)
{ {
_sessionData.ResetData(); Debug.WriteLine($"{Tag}: 更新服务状态UI失败: {ex.Message}");
minHeartRateLabel.Text = "--";
maxHeartRateLabel.Text = "--";
avgHeartRateLabel.Text = "--";
noDataLabel.IsVisible = true;
heartRateGraphicsView.Invalidate();
} }
}); });
} }
/// <summary> /// <summary>
/// 后台运行按钮点击事件 /// 设备状态变化事件处理
/// </summary> /// </summary>
private async void OnBackgroundClicked(object sender, EventArgs e) private void OnDeviceStatusChanged(DeviceConnectionStatus status)
{ {
if (_bluetoothService.ConnectedDevice == null) MainThread.BeginInvokeOnMainThread(() => {
{ try
await DisplayAlert("提示", "请先连接心率设备", "确定");
return;
}
_isRunningInBackground = !_isRunningInBackground;
if (_isRunningInBackground)
{
backgroundButton.Text = "停止后台运行";
// 显示通知
if (_sessionData.HeartRateData.Count > 0)
{ {
TimeSpan duration = _sessionData.GetSessionDuration(); connectionStatusLabel.Text = status.ConnectionMessage;
NotificationService.ShowHeartRateNotification(
_sessionData.LatestHeartRate, // 如果设备重新连接,重置会话开始时间
_sessionData.AverageHeartRate, if (status.IsConnected && !string.IsNullOrEmpty(status.DeviceName))
_sessionData.MinHeartRate, {
_sessionData.MaxHeartRate, _sessionStartTime = DateTime.Now;
duration);
// 重置UI<55><49>
minHeartRateLabel.Text = "--";
maxHeartRateLabel.Text = "--";
avgHeartRateLabel.Text = "--";
noDataLayout.IsVisible = true;
heartRateGraphicsView.Invalidate();
}
Debug.WriteLine($"{Tag}: 设备状态已更新: {status.ConnectionMessage}");
} }
else catch (Exception ex)
{ {
NotificationService.ShowHeartRateNotification(0, 0, 0, 0, TimeSpan.Zero); Debug.WriteLine($"{Tag}: 更新设<E696B0><E8AEBE>状态UI失败: {ex.Message}");
} }
});
// 通知用户应用将在后台运行
await DisplayAlert("后台运行", "应用将在后台继续监测心率。可以通过通知栏查看实时数据。", "确定");
}
else
{
backgroundButton.Text = "后台运行";
// 取消通知
Services.NotificationService.CancelNotification();
}
} }
/// <summary> /// <summary>
/// WebSocket设置按钮点击事件 /// 更新服务状态UI
/// </summary> /// </summary>
private async void OnWebSocketSettingsClicked(object sender, EventArgs e) private void UpdateServiceStatusUI(string status, bool isRunning)
{ {
string result = await DisplayPromptAsync( serviceStatusLabel.Text = status;
"配置数据上传", statusIndicator.Fill = isRunning ? Color.FromArgb("#28A745") : Color.FromArgb("#DC3545");
"请输入WebSocket服务器地址\n格式wss://example.com/ws",
"确定",
"取消",
_webSocketUrl,
maxLength: 100,
keyboard: Keyboard.Url);
if (string.IsNullOrWhiteSpace(result)) return;
if (!result.StartsWith("ws://") && !result.StartsWith("wss://"))
{
// fallback到默认websocket服务器
result = _webSocketUrl;
}
try
{
// 更新按钮状态,显示正在连接
webSocketButton.Text = "正在连接...";
webSocketButton.IsEnabled = false;
// 初始化WebSocket客户端
await InitializeWebSocketClientAsync(result);
if (_isWebSocketEnabled)
{
webSocketButton.Text = "数据上传已启用";
webSocketButton.BackgroundColor = Color.FromArgb("#28A745"); // 绿色
await DisplayAlert("连接成功", "心率数据将会实时上传到服务器", "确定");
// 禁用按钮
webSocketButton.IsEnabled = false;
}
else
{
webSocketButton.Text = "配置数据上传";
webSocketButton.BackgroundColor = Color.FromArgb("#6C757D"); // 灰色
await DisplayAlert("连接失败", "无法连接到指定的WebSocket服务器请检查地址或网络连接", "确定");
}
}
catch (Exception ex)
{
webSocketButton.Text = "配置数据上传";
webSocketButton.BackgroundColor = Color.FromArgb("#6C757D"); // 灰色
await DisplayAlert("错误", $"配置WebSocket时出错: {ex.Message}", "确定");
}
finally
{
webSocketButton.IsEnabled = true;
}
} }
/// <summary> /// <summary>
/// 初始化WebSocket客户端 /// 更新数据上传状态UI
/// </summary> /// </summary>
private async Task InitializeWebSocketClientAsync(string url = null) private void UpdateUploadStatusUI(string status, bool isConnected)
{ {
// 释放现有的WebSocket客户端 uploadStatusLabel.Text = status;
if (_webSocketClient != null) uploadStatusLabel.TextColor = isConnected ? Color.FromArgb("#28A745") : Color.FromArgb("#DC3545");
{
_webSocketClient.Dispose();
_webSocketClient = null;
}
// 使用提供的URL或默认URL
_webSocketUrl = string.IsNullOrEmpty(url) ? DefaultWebsocketUrl : url;
try
{
_webSocketClient = new Services.WebSocketService.HeartRateWebSocketClient(_webSocketUrl);
await _webSocketClient.ConnectAsync();
_isWebSocketEnabled = true;
Debug.WriteLine($"{Tag}: WebSocket客户端已初始化连接到 {_webSocketUrl}");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 初始化WebSocket客户端失败: {ex.Message}");
_isWebSocketEnabled = false;
}
}
/// <summary>
/// 发送心率数据到WebSocket服务器
/// </summary>
private async Task SendHeartRateToServerAsync(int heartRate)
{
if (!_isWebSocketEnabled || _webSocketClient == null) return;
try
{
var data = new WebSocketService.HeartRateData
{
HeartRate = heartRate,
Timestamp = DateTime.Now,
DeviceName = _bluetoothService.ConnectedDevice?.Name ?? "未知设备"
};
await _webSocketClient.SendHeartRateDataAsync(data);
}
catch (Exception ex)
{
Debug.WriteLine($"发送心率数据失败: {ex.Message}");
}
} }
/// <summary> /// <summary>
/// 页面卸载时清理资源 /// 页面卸载时清理资源
/// </summary> /// </summary>
~MainPage() protected override void OnDisappearing()
{ {
if (_webSocketClient != null) base.OnDisappearing();
{
_webSocketClient.Dispose();
_webSocketClient = null;
}
_bluetoothService.Dispose(); // 停止定时器
_uiUpdateTimer?.Stop();
// 取消订阅事件
if (_dataService != null)
{
_dataService.HeartRateDataReceived -= OnHeartRateDataReceived;
_dataService.ServiceStatusChanged -= OnServiceStatusChanged;
_dataService.DeviceStatusChanged -= OnDeviceStatusChanged;
}
} }
} }

View File

@ -0,0 +1,56 @@
using Android.Content;
using HeartRateMonitorAndroid.Services;
using Application = Android.App.Application;
namespace HeartRateMonitorAndroid.Platforms.Android
{
/// <summary>
/// Android平台的后台服务启动器实现
/// </summary>
public class AndroidBackgroundServiceStarter : IBackgroundServiceStarter
{
public async Task StartServiceAsync()
{
try
{
var context = Platform.CurrentActivity?.ApplicationContext ?? Application.Context;
var intent = new Intent(context, typeof(HeartRateKeepAliveService));
context.StartForegroundService(intent);
await Task.CompletedTask;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"AndroidBackgroundServiceStarter: 启动服务失败: {ex.Message}");
throw;
}
}
public async Task StopServiceAsync()
{
try
{
var context = Platform.CurrentActivity?.ApplicationContext ?? Application.Context;
var intent = new Intent(context, typeof(HeartRateKeepAliveService));
context.StopService(intent);
await Task.CompletedTask;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"AndroidBackgroundServiceStarter: 停止服务失败: {ex.Message}");
throw;
}
}
}
/// <summary>
/// Android平台的服务助手实现
/// </summary>
public class AndroidServiceHelper : IServiceHelper
{
public IBackgroundServiceStarter BackgroundServiceStarter { get; }
public AndroidServiceHelper()
{
BackgroundServiceStarter = new AndroidBackgroundServiceStarter();
}
}
}

View File

@ -21,13 +21,19 @@ namespace HeartRateMonitorAndroid.Platforms.Android
private const int NOTIFICATION_ID = 1001; private const int NOTIFICATION_ID = 1001;
private const string CHANNEL_ID = "HeartRateMonitorChannel"; private const string CHANNEL_ID = "HeartRateMonitorChannel";
private const string CHANNEL_NAME = "心率监测服务"; private const string CHANNEL_NAME = "心率监测服务";
private const int REPORT_INTERVAL_MS = 1000; // 1秒汇报间隔
private PowerManager.WakeLock _wakeLock; private PowerManager.WakeLock _wakeLock;
private System.Timers.Timer _heartRateTimer;
private WebSocketService.HeartRateWebSocketClient _webSocketClient; private WebSocketService.HeartRateWebSocketClient _webSocketClient;
private BluetoothService _bluetoothService; private BluetoothService _bluetoothService;
private HeartRateDataService _dataService;
private bool _isServiceRunning = false; private bool _isServiceRunning = false;
// 定时汇报相关
private System.Threading.Timer _reportTimer;
private int _latestHeartRate = 0;
private readonly object _heartRateLock = new object();
public override void OnCreate() public override void OnCreate()
{ {
base.OnCreate(); base.OnCreate();
@ -41,8 +47,6 @@ namespace HeartRateMonitorAndroid.Platforms.Android
if (!_isServiceRunning) if (!_isServiceRunning)
{ {
StartForegroundService(); StartForegroundService();
StartHeartRateMonitoring();
ScheduleJobService();
_isServiceRunning = true; _isServiceRunning = true;
} }
@ -83,7 +87,7 @@ namespace HeartRateMonitorAndroid.Platforms.Android
var notification = new NotificationCompat.Builder(this, CHANNEL_ID) var notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.SetContentTitle("心率监测服务") .SetContentTitle("心率监测服务")
.SetContentText("正在后台监测心率数据") .SetContentText("正在后台监测心率数据")
.SetSmallIcon(Resource.Drawable.abc_dialog_material_background) // 使用系统图标 .SetSmallIcon(Resource.Drawable.abc_dialog_material_background)
.SetContentIntent(pendingIntent) .SetContentIntent(pendingIntent)
.SetOngoing(true) .SetOngoing(true)
.SetPriority(NotificationCompat.PriorityLow) .SetPriority(NotificationCompat.PriorityLow)
@ -106,276 +110,242 @@ namespace HeartRateMonitorAndroid.Platforms.Android
{ {
System.Diagnostics.Debug.WriteLine("KeepAliveService: 开始初始化服务"); System.Diagnostics.Debug.WriteLine("KeepAliveService: 开始初始化服务");
// 获取服务实例 // 获取数据服务实例
var serviceProvider = MauiApplication.Current?.Services; _dataService = HeartRateDataService.Instance;
_bluetoothService = serviceProvider?.GetService<BluetoothService>(); _dataService.UpdateServiceStatus(false, "正在初始化服务...", false);
if (_bluetoothService == null) // 初始化蓝牙服务
{ _bluetoothService = new BluetoothService();
System.Diagnostics.Debug.WriteLine("KeepAliveService: 无法获取BluetoothService实例创建新实例"); _bluetoothService.StatusUpdated += OnBluetoothStatusUpdated;
_bluetoothService = new BluetoothService(); _bluetoothService.HeartRateUpdated += OnHeartRateDataReceived;
} _bluetoothService.DeviceDiscovered += OnDeviceDiscovered;
else
{ System.Diagnostics.Debug.WriteLine("KeepAliveService: 蓝牙服务已初始化");
System.Diagnostics.Debug.WriteLine("KeepAliveService: 成功获取BluetoothService实例");
}
// 初始化WebSocket客户端 // 初始化WebSocket客户端
var serverUrl = GetServerUrl(); Task.Run(async () => await InitializeWebSocketAsync());
if (!string.IsNullOrEmpty(serverUrl))
{
_webSocketClient = new WebSocketService.HeartRateWebSocketClient(serverUrl);
System.Diagnostics.Debug.WriteLine($"KeepAliveService: WebSocket客户端已初始化服务器: {serverUrl}");
}
else
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 无法获取服务器URL");
}
// 如果蓝牙服务可用,尝试重新连接之前连接的设备 // 启动蓝牙连接
if (_bluetoothService != null) Task.Run(async () => await StartBluetoothMonitoringAsync());
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 启动蓝牙重连任务");
Task.Run(async () => await ReconnectBluetoothDevice());
}
else
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 蓝牙服务不可用,跳过蓝牙重连");
}
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"InitializeServices Error: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"InitializeServices Error: {ex.Message}");
_dataService?.UpdateServiceStatus(false, "服务初始化失败", false);
} }
} }
private async Task ReconnectBluetoothDevice() private async Task InitializeWebSocketAsync()
{ {
try try
{ {
System.Diagnostics.Debug.WriteLine("KeepAliveService: 尝试重新连接蓝牙设备"); var serverUrl = GetServerUrl();
if (!string.IsNullOrEmpty(serverUrl))
// 检查蓝牙状态
var bluetoothState = _bluetoothService.CheckBluetoothState();
if (!bluetoothState.Contains("准备就绪"))
{ {
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 蓝牙状态不可用: {bluetoothState}"); _webSocketClient = new WebSocketService.HeartRateWebSocketClient(serverUrl);
return; await _webSocketClient.ConnectAsync();
}
// 尝试获取上次连接的设备地址 _dataService?.UpdateServiceStatus(true, "后台服务运行中", true, serverUrl);
var lastConnectedDeviceAddress = _bluetoothService.GetLastConnectedDeviceAddress(); System.Diagnostics.Debug.WriteLine($"KeepAliveService: WebSocket连接成功: {serverUrl}");
if (!string.IsNullOrEmpty(lastConnectedDeviceAddress))
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 尝试连接上次的设备: {lastConnectedDeviceAddress}");
// 首先尝试直接连接到已知设备
var directConnectSuccess = await _bluetoothService.ConnectToDeviceByAddressAsync(lastConnectedDeviceAddress);
if (!directConnectSuccess)
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 直接连接失败,开始扫描寻找设备");
// 注册临时设备发现事件处理器
bool deviceFound = false;
Action<Plugin.BLE.Abstractions.Contracts.IDevice> tempDeviceHandler = (device) =>
{
if (device.Id.ToString() == lastConnectedDeviceAddress)
{
deviceFound = true;
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 在扫描中找到目标设备: {device.Name}");
Task.Run(async () => await _bluetoothService.ConnectToDeviceAsync(device));
}
};
_bluetoothService.DeviceDiscovered += tempDeviceHandler;
try
{
// 开始扫描
await _bluetoothService.StartScanAsync();
// 等待10秒寻找目标设备
for (int i = 0; i < 100 && !deviceFound; i++)
{
await Task.Delay(100);
}
// 停止扫描
await _bluetoothService.StopScanAsync();
if (deviceFound)
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 成功找到并连接目标设备");
}
else
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 未找到目标设备");
}
}
finally
{
_bluetoothService.DeviceDiscovered -= tempDeviceHandler;
}
}
else
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 直接连接成功");
}
} }
else else
{ {
System.Diagnostics.Debug.WriteLine("KeepAliveService: 没有找到上次连接的设备信息,开始新的扫描"); _dataService?.UpdateServiceStatus(true, "后台服务运行中", false);
System.Diagnostics.Debug.WriteLine("KeepAliveService: 无法获取服务器URLWebSocket未连接");
// 注册临时设备发现事件处理器
bool anyDeviceFound = false;
Action<Plugin.BLE.Abstractions.Contracts.IDevice> tempDeviceHandler = (device) =>
{
if (!anyDeviceFound)
{
anyDeviceFound = true;
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 发现心率设备: {device.Name}");
Task.Run(async () => await _bluetoothService.ConnectToDeviceAsync(device));
}
};
_bluetoothService.DeviceDiscovered += tempDeviceHandler;
try
{
// 如果没有上次连接的设备信息,开始扫描
await _bluetoothService.StartScanAsync();
// 扫描15秒后停止
await Task.Delay(15000);
await _bluetoothService.StopScanAsync();
if (anyDeviceFound)
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 找到并连接了新设备");
}
else
{
System.Diagnostics.Debug.WriteLine("KeepAliveService: 未找到任何心率设备");
}
}
finally
{
_bluetoothService.DeviceDiscovered -= tempDeviceHandler;
}
} }
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"ReconnectBluetoothDevice Error: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"KeepAliveService: WebSocket连接失败: {ex.Message}");
_dataService?.UpdateServiceStatus(true, "后台服务运行中", false);
} }
} }
private string GetLastConnectedDeviceAddress() private async Task StartBluetoothMonitoringAsync()
{ {
try try
{ {
// 使用MAUI的Preferences API获取设备地址 System.Diagnostics.Debug.WriteLine("KeepAliveService: 开始蓝牙监测");
return Microsoft.Maui.Storage.Preferences.Get("LastConnectedDevice", null);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetLastConnectedDeviceAddress Error: {ex.Message}");
return null;
}
}
private void SaveLastConnectedDeviceAddress(string deviceAddress) // 检查蓝牙状态
{ _bluetoothService.CheckBluetoothState();
try
{
var sharedPreferences = GetSharedPreferences("HeartRateMonitor", FileCreationMode.Private);
var editor = sharedPreferences.Edit();
editor.PutString("LastConnectedDevice", deviceAddress);
editor.Apply();
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 保存设备地址: {deviceAddress}");
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"SaveLastConnectedDeviceAddress Error: {ex.Message}");
}
}
private string GetServerUrl() if (_bluetoothService.IsBluetoothAvailable)
{
try
{
// 从Resources/Raw/token.txt读取服务器URL
using var stream = FileSystem.OpenAppPackageFileAsync("server.txt").Result;
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
return contents;
}
catch
{
return "wss:///ws.nuanr-mxi.com/ws"; // 默认URL
}
}
private void StartHeartRateMonitoring()
{
_heartRateTimer = new System.Timers.Timer(1000); // 30秒间隔
_heartRateTimer.Elapsed += async (sender, e) =>
{
try
{ {
await MonitorHeartRate(); _dataService?.UpdateDeviceStatus(false, "", "正在扫描心率设备...");
// 开始扫描心率设备
await _bluetoothService.StartScanAsync();
System.Diagnostics.Debug.WriteLine("KeepAliveService: 蓝牙扫描已启动");
} }
catch (System.Exception ex) else
{ {
System.Diagnostics.Debug.WriteLine($"Heart rate monitoring error: {ex.Message}"); _dataService?.UpdateDeviceStatus(false, "", "蓝牙不可用");
System.Diagnostics.Debug.WriteLine("KeepAliveService: 蓝牙不可用");
} }
}; }
_heartRateTimer.Start(); catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 蓝牙监测启动失败: {ex.Message}");
_dataService?.UpdateDeviceStatus(false, "", "蓝牙监测启动失败");
}
} }
private async Task MonitorHeartRate() private void OnBluetoothStatusUpdated(string status)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 蓝牙状态更新: {status}");
_dataService?.UpdateDeviceStatus(false, "", status);
}
private void OnHeartRateDataReceived(int heartRate)
{ {
try try
{ {
// 确保WebSocket连接 //System.Diagnostics.Debug.WriteLine($"KeepAliveService: 收到心率数据: {heartRate} bpm");
// 只更新最新心率值,不立即发送数据
lock (_heartRateLock)
{
_latestHeartRate = heartRate;
}
//System.Diagnostics.Debug.WriteLine($"KeepAliveService: 心率数据已更新: {heartRate} bpm");
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 处理心率数据失败: {ex.Message}");
}
}
private async void OnDeviceDiscovered(Plugin.BLE.Abstractions.Contracts.IDevice device)
{
try
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 发现设备: {device.Name ?? ""}");
_dataService?.UpdateDeviceStatus(false, device.Name ?? "未知设备", $"发现设备: {device.Name ?? ""}");
// 尝试连接设备
await _bluetoothService.ConnectToDeviceAsync(device);
if (_bluetoothService.ConnectedDevice != null)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 设备连接成功: {device.Name}");
_dataService?.UpdateDeviceStatus(true, device.Name ?? "未知设备", $"已连接: {device.Name ?? ""}");
_dataService?.ResetSessionData(); // 重置会话数据
// 启动定时汇报
StartPeriodicReporting();
}
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 设备连接失败: {ex.Message}");
_dataService?.UpdateDeviceStatus(false, "", "设备连接失败");
}
}
/// <summary>
/// 启动定时汇报(每秒一次)
/// </summary>
private void StartPeriodicReporting()
{
try
{
// 停止现有定时器
StopPeriodicReporting();
// 启动新的定时器
_reportTimer = new System.Threading.Timer(OnPeriodicReport, null,
TimeSpan.FromMilliseconds(REPORT_INTERVAL_MS),
TimeSpan.FromMilliseconds(REPORT_INTERVAL_MS));
System.Diagnostics.Debug.WriteLine("KeepAliveService: 定时汇报已启动间隔1秒");
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 启动定时汇报失败: {ex.Message}");
}
}
/// <summary>
/// 停止定时汇报
/// </summary>
private void StopPeriodicReporting()
{
try
{
_reportTimer?.Dispose();
_reportTimer = null;
System.Diagnostics.Debug.WriteLine("KeepAliveService: 定时汇报已停止");
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 停止定时汇报失败: {ex.Message}");
}
}
/// <summary>
/// 定时汇报回调方法
/// </summary>
private async void OnPeriodicReport(object state)
{
try
{
if (!_isServiceRunning) return;
int currentHeartRate;
lock (_heartRateLock)
{
currentHeartRate = _latestHeartRate;
}
// 无论心率数据是否更新,都进行汇报
//System.Diagnostics.Debug.WriteLine($"KeepAliveService: 定时汇报 - 心率: {currentHeartRate} bpm");
// 更新数据服务触发UI更新
if (currentHeartRate > 0)
{
_dataService?.UpdateHeartRateData(currentHeartRate);
}
// 发送到WebSocket服务器
if (_webSocketClient != null) if (_webSocketClient != null)
{ {
await _webSocketClient.ConnectAsync(); await SendHeartRateToServerAsync(currentHeartRate);
// 从蓝牙设备获取心率数据
var heartRate = await GetCurrentHeartRate();
if (heartRate > 0)
{
await _webSocketClient.SendHeartRateAsync(heartRate);
UpdateNotification($"最新心率: {heartRate} BPM");
}
} }
// 更新通知
var message = currentHeartRate > 0 ? $"最新心率: {currentHeartRate} BPM" : "等待心率数据...";
UpdateNotification(message);
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"MonitorHeartRate Error: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"KeepAliveService: 定时汇报失败: {ex.Message}");
} }
} }
private async Task<int> GetCurrentHeartRate() private async Task SendHeartRateToServerAsync(int heartRate)
{ {
try try
{ {
// 只从蓝牙设备获取真实心率数据 if (_webSocketClient == null) return;
if (_bluetoothService?.ConnectedDevice != null)
{
// 从BluetoothService获取最新的心率值
return _bluetoothService.LastHeartRate;
}
// 如果没有连接设备返回0表示无数据 var data = new WebSocketService.HeartRateData
return 0; {
HeartRate = heartRate,
Timestamp = DateTime.Now,
DeviceName = _bluetoothService?.ConnectedDevice?.Name ?? "未知设备"
};
await _webSocketClient.SendHeartRateDataAsync(data);
//System.Diagnostics.Debug.WriteLine($"KeepAliveService: 心率数据已发送到服务器: {heartRate} bpm");
} }
catch catch (System.Exception ex)
{ {
return 0; System.Diagnostics.Debug.WriteLine($"KeepAliveService: 发送心率数据失败: {ex.Message}");
_dataService?.UpdateServiceStatus(true, "后台服务运行中", false);
} }
} }
@ -390,7 +360,7 @@ namespace HeartRateMonitorAndroid.Platforms.Android
var notification = new NotificationCompat.Builder(this, CHANNEL_ID) var notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.SetContentTitle("心率监测服务") .SetContentTitle("心率监测服务")
.SetContentText(message) .SetContentText(message)
.SetSmallIcon(Resource.Drawable.abc_dialog_material_background) // 使用系统图标 .SetSmallIcon(Resource.Drawable.abc_dialog_material_background)
.SetContentIntent(pendingIntent) .SetContentIntent(pendingIntent)
.SetOngoing(true) .SetOngoing(true)
.Build(); .Build();
@ -404,65 +374,52 @@ namespace HeartRateMonitorAndroid.Platforms.Android
} }
} }
private void ScheduleJobService() private string GetServerUrl()
{ {
try try
{ {
var jobScheduler = GetSystemService(JobSchedulerService) as JobScheduler; // 从资源文件读取服<E58F96><E69C8D><EFBFBD>器地址
var jobInfo = new JobInfo.Builder(1002, new ComponentName(this, Java.Lang.Class.FromType(typeof(HeartRateJobService)))) using var stream = Assets.Open("server.txt");
.SetRequiredNetworkType(NetworkType.Any) using var reader = new System.IO.StreamReader(stream);
.SetPersisted(true) return reader.ReadToEnd().Trim();
.SetPeriodic(15 * 60 * 1000) // 15分钟
.SetRequiresCharging(false)
.SetRequiresDeviceIdle(false)
.Build();
jobScheduler?.Schedule(jobInfo);
} }
catch (System.Exception ex) catch
{ {
System.Diagnostics.Debug.WriteLine($"ScheduleJobService Error: {ex.Message}"); return "wss://ws.nuanr-mxi.com/ws"; // 默认服务器地址
} }
} }
public override void OnDestroy() public override void OnDestroy()
{
_isServiceRunning = false;
_heartRateTimer?.Stop();
_heartRateTimer?.Dispose();
_webSocketClient?.Dispose();
_wakeLock?.Release();
// 服务被销毁时,立即重启
RestartService();
base.OnDestroy();
}
private void RestartService()
{ {
try try
{ {
var intent = new Intent(this, typeof(HeartRateKeepAliveService)); _isServiceRunning = false;
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{ // 停止定时汇报
StartForegroundService(intent); StopPeriodicReporting();
}
else // 清理资源
{ _bluetoothService?.Dispose();
StartService(intent); _webSocketClient?.Dispose();
} _wakeLock?.Release();
_dataService?.UpdateServiceStatus(false, "服务已停止", false);
System.Diagnostics.Debug.WriteLine("KeepAliveService: 服务已停止");
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"RestartService Error: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"KeepAliveService: 服务停止时发生错误: {ex.Message}");
} }
base.OnDestroy();
} }
public override void OnTaskRemoved(Intent rootIntent) public override void OnTaskRemoved(Intent rootIntent)
{ {
// 当任务被移除时重启服务 // 当任务被移除时重启服务
RestartService(); var intent = new Intent(this, typeof(HeartRateKeepAliveService));
StartForegroundService(intent);
base.OnTaskRemoved(rootIntent); base.OnTaskRemoved(rootIntent);
} }
} }

View File

@ -1,5 +1,6 @@
using Android.App; using Android.App;
using Android.Runtime; using Android.Runtime;
using HeartRateMonitorAndroid.Services;
namespace HeartRateMonitorAndroid; namespace HeartRateMonitorAndroid;
@ -12,4 +13,12 @@ public class MainApplication : MauiApplication
} }
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
public override void OnCreate()
{
base.OnCreate();
// 设置Android平台特定的服务助手
ServiceHelper.Current = new Platforms.Android.AndroidServiceHelper();
}
} }

View File

@ -0,0 +1,232 @@
using HeartRateMonitorAndroid.Models;
using System.Diagnostics;
namespace HeartRateMonitorAndroid.Services
{
/// <summary>
/// 心率数据服务实现,作为前端与后台服务的桥梁
/// </summary>
public class HeartRateDataService : IHeartRateDataService
{
private const string Tag = "HeartRateDataService";
// 静态实例,用于在后台服务和前端之间共享数据
private static HeartRateDataService _instance;
private static readonly object _lock = new object();
// 事件
public event Action<int> HeartRateDataReceived;
public event Action<ServiceStatus> ServiceStatusChanged;
public event Action<DeviceConnectionStatus> DeviceStatusChanged;
// 数据存储
private readonly HeartRateSessionData _sessionData = new();
private ServiceStatus _serviceStatus = new();
private DeviceConnectionStatus _deviceStatus = new();
private HeartRateDataService()
{
// 私有构造函数,确保单例模式
}
/// <summary>
/// 获取单例实例
/// </summary>
public static HeartRateDataService Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new HeartRateDataService();
}
}
}
return _instance;
}
}
/// <summary>
/// 获取当前心率数据
/// </summary>
public HeartRateSessionData GetCurrentSessionData()
{
return _sessionData;
}
/// <summary>
/// 获取服务状态
/// </summary>
public ServiceStatus GetServiceStatus()
{
return _serviceStatus;
}
/// <summary>
/// 获取设备连接状态
/// </summary>
public DeviceConnectionStatus GetDeviceStatus()
{
return _deviceStatus;
}
/// <summary>
/// 启动后台服务(平台特定实现将在调用时提供)
/// </summary>
public async Task StartBackgroundServiceAsync()
{
try
{
// 通过依赖注入获取平台特定的服务启动器
var serviceStarter = ServiceHelper.Current?.BackgroundServiceStarter;
if (serviceStarter != null)
{
await serviceStarter.StartServiceAsync();
Debug.WriteLine($"{Tag}: 后台服务启动请求已发送");
}
else
{
Debug.WriteLine($"{Tag}: 无法获取平台特定的服务启动器");
throw new PlatformNotSupportedException("当前平台不支持后台服务");
}
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 启动后台服务失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 停止后台服务
/// </summary>
public async Task StopBackgroundServiceAsync()
{
try
{
var serviceStarter = ServiceHelper.Current?.BackgroundServiceStarter;
if (serviceStarter != null)
{
await serviceStarter.StopServiceAsync();
Debug.WriteLine($"{Tag}: 后台服务停止请求已发送");
}
else
{
Debug.WriteLine($"{Tag}: 无法获取平台特定的服务启动器");
}
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 停止后台服务失败: {ex.Message}");
throw;
}
}
// 以下方法供后台服务调用,用于更新数据和状态
/// <summary>
/// 更新心率数据(由后台服务调用)
/// </summary>
public void UpdateHeartRateData(int heartRate)
{
try
{
_sessionData.AddHeartRate(heartRate);
HeartRateDataReceived?.Invoke(heartRate);
//Debug.WriteLine($"{Tag}: 心率数据已更新: {heartRate} bpm");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 更新心率数据失败: {ex.Message}");
}
}
/// <summary>
/// 更新服务状态(由后台服务调用)
/// </summary>
public void UpdateServiceStatus(bool isRunning, string statusMessage, bool isWebSocketConnected, string webSocketUrl = "")
{
try
{
_serviceStatus.IsRunning = isRunning;
_serviceStatus.StatusMessage = statusMessage;
_serviceStatus.IsWebSocketConnected = isWebSocketConnected;
_serviceStatus.WebSocketUrl = webSocketUrl;
_serviceStatus.LastUpdateTime = DateTime.Now;
ServiceStatusChanged?.Invoke(_serviceStatus);
Debug.WriteLine($"{Tag}: 服务状态已更新: {statusMessage}");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 更新服务状态失败: {ex.Message}");
}
}
/// <summary>
/// 更新设备连接状态(由后台服务调用)
/// </summary>
public void UpdateDeviceStatus(bool isConnected, string deviceName, string connectionMessage)
{
try
{
_deviceStatus.IsConnected = isConnected;
_deviceStatus.DeviceName = deviceName;
_deviceStatus.ConnectionMessage = connectionMessage;
_deviceStatus.LastConnectionTime = DateTime.Now;
DeviceStatusChanged?.Invoke(_deviceStatus);
Debug.WriteLine($"{Tag}: 设备状态已更新: {connectionMessage}");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 更新设备状态失败: {ex.Message}");
}
}
/// <summary>
/// 重置会话数据(由后台服务调用)
/// </summary>
public void ResetSessionData()
{
try
{
_sessionData.ResetData();
Debug.WriteLine($"{Tag}: 会话数据已重置");
}
catch (Exception ex)
{
Debug.WriteLine($"{Tag}: 重置会话数据失败: {ex.Message}");
}
}
}
/// <summary>
/// 平台特定服务启动器接口
/// </summary>
public interface IBackgroundServiceStarter
{
Task StartServiceAsync();
Task StopServiceAsync();
}
/// <summary>
/// 服务助手类,用于获取平台特定的服务
/// </summary>
public static class ServiceHelper
{
public static IServiceHelper Current { get; set; }
}
/// <summary>
/// 服务助手接口
/// </summary>
public interface IServiceHelper
{
IBackgroundServiceStarter BackgroundServiceStarter { get; }
}
}

View File

@ -0,0 +1,74 @@
using HeartRateMonitorAndroid.Models;
namespace HeartRateMonitorAndroid.Services
{
/// <summary>
/// 心率数据服务接口,用于前端与后台服务通信
/// </summary>
public interface IHeartRateDataService
{
/// <summary>
/// 心率数据更新事件
/// </summary>
event Action<int> HeartRateDataReceived;
/// <summary>
/// 服务状态更新事件
/// </summary>
event Action<ServiceStatus> ServiceStatusChanged;
/// <summary>
/// 设备连接状态更新事件
/// </summary>
event Action<DeviceConnectionStatus> DeviceStatusChanged;
/// <summary>
/// 获取当前心率数据
/// </summary>
HeartRateSessionData GetCurrentSessionData();
/// <summary>
/// 获取服务状态
/// </summary>
ServiceStatus GetServiceStatus();
/// <summary>
/// 获取设备连接状态
/// </summary>
DeviceConnectionStatus GetDeviceStatus();
/// <summary>
/// 启动后台服务
/// </summary>
Task StartBackgroundServiceAsync();
/// <summary>
/// 停止后台服务
/// </summary>
Task StopBackgroundServiceAsync();
}
/// <summary>
/// 服务状态
/// </summary>
public class ServiceStatus
{
public bool IsRunning { get; set; }
public string StatusMessage { get; set; } = "";
public bool IsWebSocketConnected { get; set; }
public string WebSocketUrl { get; set; } = "";
public DateTime LastUpdateTime { get; set; }
}
/// <summary>
/// 设备连接状态
/// </summary>
public class DeviceConnectionStatus
{
public bool IsConnected { get; set; }
public string DeviceName { get; set; } = "";
public string ConnectionMessage { get; set; } = "";
public DateTime LastConnectionTime { get; set; }
public int SignalStrength { get; set; } // 信号强度,如果支持的话
}
}