add: fuck you android
This commit is contained in:
@ -6,13 +6,33 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
|
||||
<!-- Android 12+ 新蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
|
||||
<!-- 位置权限(蓝牙扫描需要) -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<!-- 保活相关权限 -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
|
||||
|
||||
<!-- 声明不需要硬件蓝牙支持(可选) -->
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
|
||||
|
||||
</manifest>
|
||||
|
||||
129
Platforms/Android/HeartRateJobService.cs
Normal file
129
Platforms/Android/HeartRateJobService.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using Android.App;
|
||||
using Android.App.Job;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
|
||||
namespace HeartRateMonitorAndroid.Platforms.Android
|
||||
{
|
||||
[Service(Name = "com.nuanrmxi.heartratemonitor.HeartRateJobService",
|
||||
Permission = "android.permission.BIND_JOB_SERVICE",
|
||||
Enabled = true,
|
||||
Exported = false)]
|
||||
public class HeartRateJobService : JobService
|
||||
{
|
||||
private const int JOB_ID = 1002;
|
||||
|
||||
public override bool OnStartJob(JobParameters @params)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("HeartRateJobService: OnStartJob called");
|
||||
|
||||
// 在后台线程中执行任务
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckAndRestartService();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"HeartRateJobService Error: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 完成任务
|
||||
JobFinished(@params, false);
|
||||
}
|
||||
});
|
||||
|
||||
return true; // 返回true表示任务在后台执行
|
||||
}
|
||||
|
||||
public override bool OnStopJob(JobParameters @params)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("HeartRateJobService: OnStopJob called");
|
||||
return false; // 返回false表示不需要重新调度
|
||||
}
|
||||
|
||||
private void CheckAndRestartService()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查前台服务是否正在运行
|
||||
if (!IsServiceRunning(typeof(HeartRateKeepAliveService)))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("HeartRateJobService: 前台服务未运行,正在重启...");
|
||||
|
||||
var intent = new Intent(this, typeof(HeartRateKeepAliveService));
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
StartForegroundService(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartService(intent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("HeartRateJobService: 前台服务正常运行");
|
||||
}
|
||||
|
||||
// 重新调度下一次检查
|
||||
ScheduleNextJob();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"CheckAndRestartService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsServiceRunning(System.Type serviceType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activityManager = GetSystemService(ActivityService) as ActivityManager;
|
||||
var services = activityManager?.GetRunningServices(int.MaxValue);
|
||||
|
||||
if (services != null)
|
||||
{
|
||||
foreach (var service in services)
|
||||
{
|
||||
if (service.Service.ClassName.Equals(serviceType.FullName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"IsServiceRunning Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ScheduleNextJob()
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobScheduler = GetSystemService(JobSchedulerService) as JobScheduler;
|
||||
var jobInfo = new JobInfo.Builder(JOB_ID, new ComponentName(this, Java.Lang.Class.FromType(typeof(HeartRateJobService))))
|
||||
.SetRequiredNetworkType(NetworkType.Any)
|
||||
.SetPersisted(true)
|
||||
.SetMinimumLatency(10 * 60 * 1000) // 最少10分钟后执行
|
||||
.SetOverrideDeadline(15 * 60 * 1000) // 最多15分钟后必须执行
|
||||
.SetRequiresCharging(false)
|
||||
.SetRequiresDeviceIdle(false)
|
||||
.Build();
|
||||
|
||||
var result = jobScheduler?.Schedule(jobInfo);
|
||||
System.Diagnostics.Debug.WriteLine($"HeartRateJobService: 下次任务调度结果: {result}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ScheduleNextJob Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
469
Platforms/Android/HeartRateKeepAliveService.cs
Normal file
469
Platforms/Android/HeartRateKeepAliveService.cs
Normal file
@ -0,0 +1,469 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using AndroidX.Core.App;
|
||||
using Android.Content.PM;
|
||||
using HeartRateMonitorAndroid.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Java.Lang;
|
||||
using Android.Provider;
|
||||
using Android.App.Job;
|
||||
using Resource = Android.Resource;
|
||||
|
||||
namespace HeartRateMonitorAndroid.Platforms.Android
|
||||
{
|
||||
[Service(Name = "com.nuanrmxi.heartratemonitor.HeartRateKeepAliveService",
|
||||
Enabled = true,
|
||||
Exported = false,
|
||||
ForegroundServiceType = ForegroundService.TypeDataSync | ForegroundService.TypeLocation)]
|
||||
public class HeartRateKeepAliveService : Service
|
||||
{
|
||||
private const int NOTIFICATION_ID = 1001;
|
||||
private const string CHANNEL_ID = "HeartRateMonitorChannel";
|
||||
private const string CHANNEL_NAME = "心率监测服务";
|
||||
|
||||
private PowerManager.WakeLock _wakeLock;
|
||||
private System.Timers.Timer _heartRateTimer;
|
||||
private WebSocketService.HeartRateWebSocketClient _webSocketClient;
|
||||
private BluetoothService _bluetoothService;
|
||||
private bool _isServiceRunning = false;
|
||||
|
||||
public override void OnCreate()
|
||||
{
|
||||
base.OnCreate();
|
||||
CreateNotificationChannel();
|
||||
AcquireWakeLock();
|
||||
InitializeServices();
|
||||
}
|
||||
|
||||
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
|
||||
{
|
||||
if (!_isServiceRunning)
|
||||
{
|
||||
StartForegroundService();
|
||||
StartHeartRateMonitoring();
|
||||
ScheduleJobService();
|
||||
_isServiceRunning = true;
|
||||
}
|
||||
|
||||
// 返回START_STICKY确保服务被系统杀死后会重启
|
||||
return StartCommandResult.Sticky;
|
||||
}
|
||||
|
||||
public override IBinder OnBind(Intent intent)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CreateNotificationChannel()
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
var channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationImportance.Low)
|
||||
{
|
||||
Description = "心率监测后台服务通知"
|
||||
};
|
||||
channel.SetShowBadge(false);
|
||||
channel.EnableLights(false);
|
||||
channel.EnableVibration(false);
|
||||
|
||||
var notificationManager = GetSystemService(NotificationService) as NotificationManager;
|
||||
notificationManager?.CreateNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartForegroundService()
|
||||
{
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
|
||||
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent,
|
||||
PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);
|
||||
|
||||
var notification = new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.SetContentTitle("心率监测服务")
|
||||
.SetContentText("正在后台监测心率数据")
|
||||
.SetSmallIcon(Resource.Drawable.abc_dialog_material_background) // 使用系统图标
|
||||
.SetContentIntent(pendingIntent)
|
||||
.SetOngoing(true)
|
||||
.SetPriority(NotificationCompat.PriorityLow)
|
||||
.SetCategory(NotificationCompat.CategoryService)
|
||||
.Build();
|
||||
|
||||
StartForeground(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
private void AcquireWakeLock()
|
||||
{
|
||||
var powerManager = GetSystemService(PowerService) as PowerManager;
|
||||
_wakeLock = powerManager?.NewWakeLock(WakeLockFlags.Partial, "HeartRateMonitor::KeepAlive");
|
||||
_wakeLock?.Acquire();
|
||||
}
|
||||
|
||||
private void InitializeServices()
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 开始初始化服务");
|
||||
|
||||
// 获取服务实例
|
||||
var serviceProvider = MauiApplication.Current?.Services;
|
||||
_bluetoothService = serviceProvider?.GetService<BluetoothService>();
|
||||
|
||||
if (_bluetoothService == null)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 无法获取BluetoothService实例,创建新实例");
|
||||
_bluetoothService = new BluetoothService();
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 成功获取BluetoothService实例");
|
||||
}
|
||||
|
||||
// 初始化WebSocket客户端
|
||||
var serverUrl = GetServerUrl();
|
||||
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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 启动蓝牙重连任务");
|
||||
Task.Run(async () => await ReconnectBluetoothDevice());
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 蓝牙服务不可用,跳过蓝牙重连");
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"InitializeServices Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReconnectBluetoothDevice()
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 尝试重新连接蓝牙设备");
|
||||
|
||||
// 检查蓝牙状态
|
||||
var bluetoothState = _bluetoothService.CheckBluetoothState();
|
||||
if (!bluetoothState.Contains("准备就绪"))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"KeepAliveService: 蓝牙状态不可用: {bluetoothState}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试获取上次连接的设备地址
|
||||
var lastConnectedDeviceAddress = _bluetoothService.GetLastConnectedDeviceAddress();
|
||||
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
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveService: 没有找到上次连接的设备信息,开始新的扫描");
|
||||
|
||||
// 注册临时设备发现事件处理器
|
||||
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)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ReconnectBluetoothDevice Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLastConnectedDeviceAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用MAUI的Preferences API获取设备地址
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Heart rate monitoring error: {ex.Message}");
|
||||
}
|
||||
};
|
||||
_heartRateTimer.Start();
|
||||
}
|
||||
|
||||
private async Task MonitorHeartRate()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保WebSocket连接
|
||||
if (_webSocketClient != null)
|
||||
{
|
||||
await _webSocketClient.ConnectAsync();
|
||||
|
||||
// 从蓝牙设备获取心率数据
|
||||
var heartRate = await GetCurrentHeartRate();
|
||||
if (heartRate > 0)
|
||||
{
|
||||
await _webSocketClient.SendHeartRateAsync(heartRate);
|
||||
UpdateNotification($"最新心率: {heartRate} BPM");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"MonitorHeartRate Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> GetCurrentHeartRate()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只从蓝牙设备获取真实心率数据
|
||||
if (_bluetoothService?.ConnectedDevice != null)
|
||||
{
|
||||
// 从BluetoothService获取最新的心率值
|
||||
return _bluetoothService.LastHeartRate;
|
||||
}
|
||||
|
||||
// 如果没有连接设备,返回0表示无数据
|
||||
return 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNotification(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent,
|
||||
PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);
|
||||
|
||||
var notification = new NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.SetContentTitle("心率监测服务")
|
||||
.SetContentText(message)
|
||||
.SetSmallIcon(Resource.Drawable.abc_dialog_material_background) // 使用系统图标
|
||||
.SetContentIntent(pendingIntent)
|
||||
.SetOngoing(true)
|
||||
.Build();
|
||||
|
||||
var notificationManager = GetSystemService(NotificationService) as NotificationManager;
|
||||
notificationManager?.Notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"UpdateNotification Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleJobService()
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobScheduler = GetSystemService(JobSchedulerService) as JobScheduler;
|
||||
var jobInfo = new JobInfo.Builder(1002, new ComponentName(this, Java.Lang.Class.FromType(typeof(HeartRateJobService))))
|
||||
.SetRequiredNetworkType(NetworkType.Any)
|
||||
.SetPersisted(true)
|
||||
.SetPeriodic(15 * 60 * 1000) // 15分钟
|
||||
.SetRequiresCharging(false)
|
||||
.SetRequiresDeviceIdle(false)
|
||||
.Build();
|
||||
|
||||
jobScheduler?.Schedule(jobInfo);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ScheduleJobService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
_isServiceRunning = false;
|
||||
_heartRateTimer?.Stop();
|
||||
_heartRateTimer?.Dispose();
|
||||
_webSocketClient?.Dispose();
|
||||
_wakeLock?.Release();
|
||||
|
||||
// 服务被销毁时,立即重启
|
||||
RestartService();
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
private void RestartService()
|
||||
{
|
||||
try
|
||||
{
|
||||
var intent = new Intent(this, typeof(HeartRateKeepAliveService));
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
StartForegroundService(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartService(intent);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RestartService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnTaskRemoved(Intent rootIntent)
|
||||
{
|
||||
// 当任务被移除时重启服务
|
||||
RestartService();
|
||||
base.OnTaskRemoved(rootIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Platforms/Android/KeepAliveBroadcastReceiver.cs
Normal file
155
Platforms/Android/KeepAliveBroadcastReceiver.cs
Normal file
@ -0,0 +1,155 @@
|
||||
using Android.App;
|
||||
using Android.App.Job;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
|
||||
namespace HeartRateMonitorAndroid.Platforms.Android
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.nuanrmxi.heartratemonitor.KeepAliveBroadcastReceiver",
|
||||
Enabled = true,
|
||||
Exported = true)]
|
||||
[IntentFilter(new[] {
|
||||
Intent.ActionBootCompleted,
|
||||
Intent.ActionUserPresent,
|
||||
Intent.ActionMyPackageReplaced,
|
||||
Intent.ActionPackageReplaced,
|
||||
"android.net.conn.CONNECTIVITY_CHANGE",
|
||||
Intent.ActionScreenOn,
|
||||
Intent.ActionScreenOff
|
||||
}, Priority = 1000)]
|
||||
public class KeepAliveBroadcastReceiver : BroadcastReceiver
|
||||
{
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var action = intent?.Action;
|
||||
System.Diagnostics.Debug.WriteLine($"KeepAliveBroadcastReceiver: 收到广播 {action}");
|
||||
|
||||
try
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case Intent.ActionBootCompleted:
|
||||
System.Diagnostics.Debug.WriteLine("设备启动完成,启动心率监测服务");
|
||||
StartHeartRateService(context);
|
||||
break;
|
||||
|
||||
case Intent.ActionUserPresent:
|
||||
System.Diagnostics.Debug.WriteLine("用户解锁设备,检查服务状态");
|
||||
CheckAndStartService(context);
|
||||
break;
|
||||
|
||||
case Intent.ActionMyPackageReplaced:
|
||||
case Intent.ActionPackageReplaced:
|
||||
System.Diagnostics.Debug.WriteLine("应用包更新,重启服务");
|
||||
StartHeartRateService(context);
|
||||
break;
|
||||
|
||||
case "android.net.conn.CONNECTIVITY_CHANGE":
|
||||
System.Diagnostics.Debug.WriteLine("网络连接状态改变,检查服务");
|
||||
CheckAndStartService(context);
|
||||
break;
|
||||
|
||||
case Intent.ActionScreenOn:
|
||||
System.Diagnostics.Debug.WriteLine("屏幕亮起,检查服务状态");
|
||||
CheckAndStartService(context);
|
||||
break;
|
||||
|
||||
case Intent.ActionScreenOff:
|
||||
System.Diagnostics.Debug.WriteLine("屏幕关闭,确保服务运行");
|
||||
CheckAndStartService(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"KeepAliveBroadcastReceiver Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartHeartRateService(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var intent = new Intent(context, typeof(HeartRateKeepAliveService));
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
context.StartForegroundService(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.StartService(intent);
|
||||
}
|
||||
|
||||
// 同时启动JobScheduler
|
||||
ScheduleJobService(context);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"StartHeartRateService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAndStartService(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsServiceRunning(context, typeof(HeartRateKeepAliveService)))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("服务未运行,正在启动...");
|
||||
StartHeartRateService(context);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"CheckAndStartService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsServiceRunning(Context context, System.Type serviceType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activityManager = context.GetSystemService(Context.ActivityService) as ActivityManager;
|
||||
var services = activityManager?.GetRunningServices(int.MaxValue);
|
||||
|
||||
if (services != null)
|
||||
{
|
||||
foreach (var service in services)
|
||||
{
|
||||
if (service.Service.ClassName.Contains(serviceType.Name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"IsServiceRunning Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ScheduleJobService(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobScheduler = context.GetSystemService(Context.JobSchedulerService) as JobScheduler;
|
||||
var jobInfo = new JobInfo.Builder(1002, new ComponentName(context, Java.Lang.Class.FromType(typeof(HeartRateJobService))))
|
||||
.SetRequiredNetworkType(NetworkType.Any)
|
||||
.SetPersisted(true)
|
||||
.SetPeriodic(15 * 60 * 1000) // 15分钟
|
||||
.SetRequiresCharging(false)
|
||||
.SetRequiresDeviceIdle(false)
|
||||
.Build();
|
||||
|
||||
jobScheduler?.Schedule(jobInfo);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ScheduleJobService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
325
Platforms/Android/KeepAliveManager.cs
Normal file
325
Platforms/Android/KeepAliveManager.cs
Normal file
@ -0,0 +1,325 @@
|
||||
using Android.App;
|
||||
using Android.App.Job;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using AndroidX.Core.App;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration;
|
||||
using Uri = Android.Net.Uri;
|
||||
|
||||
namespace HeartRateMonitorAndroid.Platforms.Android
|
||||
{
|
||||
/// <summary>
|
||||
/// 保活管理器,负责统一管理所有保活功能
|
||||
/// </summary>
|
||||
public static class KeepAliveManager
|
||||
{
|
||||
private static bool _isInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化保活功能
|
||||
/// </summary>
|
||||
public static void Initialize(Context context)
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 开始初始化保活功能");
|
||||
|
||||
// 1. 启动前台服务
|
||||
StartForegroundService(context);
|
||||
|
||||
// 2. 调度JobScheduler任务
|
||||
ScheduleJobService(context);
|
||||
|
||||
// 3. 注册广播接收器(通过代码方式补充)
|
||||
RegisterBroadcastReceiver(context);
|
||||
|
||||
// 4. 请求系统权限
|
||||
RequestSystemPermissions(context);
|
||||
|
||||
_isInitialized = true;
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 保活功能初始化完成");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"KeepAliveManager Initialize Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动前台服务
|
||||
/// </summary>
|
||||
public static void StartForegroundService(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var intent = new Intent(context, typeof(HeartRateKeepAliveService));
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
context.StartForegroundService(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.StartService(intent);
|
||||
}
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 前台服务已启动");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"StartForegroundService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调度JobScheduler任务
|
||||
/// </summary>
|
||||
public static void ScheduleJobService(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobScheduler = context.GetSystemService(Context.JobSchedulerService) as JobScheduler;
|
||||
var jobInfo = new JobInfo.Builder(1002, new ComponentName(context, Java.Lang.Class.FromType(typeof(HeartRateJobService))))
|
||||
.SetRequiredNetworkType(NetworkType.Any)
|
||||
.SetPersisted(true)
|
||||
.SetPeriodic(15 * 60 * 1000) // 15分钟
|
||||
.SetRequiresCharging(false)
|
||||
.SetRequiresDeviceIdle(false)
|
||||
.Build();
|
||||
|
||||
var result = jobScheduler?.Schedule(jobInfo);
|
||||
System.Diagnostics.Debug.WriteLine($"KeepAliveManager: JobScheduler调度结果: {result}");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ScheduleJobService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态注册广播接收器
|
||||
/// </summary>
|
||||
public static void RegisterBroadcastReceiver(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var receiver = new KeepAliveBroadcastReceiver();
|
||||
var filter = new IntentFilter();
|
||||
|
||||
// 添加需要监听的动作
|
||||
filter.AddAction(Intent.ActionUserPresent);
|
||||
filter.AddAction("android.net.conn.CONNECTIVITY_CHANGE");
|
||||
filter.AddAction(Intent.ActionScreenOn);
|
||||
filter.AddAction(Intent.ActionScreenOff);
|
||||
filter.Priority = 1000;
|
||||
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu)
|
||||
{
|
||||
context.RegisterReceiver(receiver, filter, ReceiverFlags.Exported);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.RegisterReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 动态广播接收器已注册");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RegisterBroadcastReceiver Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求系统权限
|
||||
/// </summary>
|
||||
public static void RequestSystemPermissions(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 请求忽略电池优化
|
||||
RequestIgnoreBatteryOptimization(context);
|
||||
|
||||
// 请求自启动权限(针对不同厂商)
|
||||
RequestAutoStartPermission(context);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RequestSystemPermissions Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求忽略电池优化
|
||||
/// </summary>
|
||||
public static void RequestIgnoreBatteryOptimization(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
var powerManager = context.GetSystemService(Context.PowerService) as PowerManager;
|
||||
if (powerManager != null && !powerManager.IsIgnoringBatteryOptimizations(context.PackageName))
|
||||
{
|
||||
var intent = new Intent(Settings.ActionRequestIgnoreBatteryOptimizations);
|
||||
intent.SetData(Uri.Parse($"package:{context.PackageName}"));
|
||||
intent.SetFlags(ActivityFlags.NewTask);
|
||||
|
||||
if (intent.ResolveActivity(context.PackageManager) != null)
|
||||
{
|
||||
context.StartActivity(intent);
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 请求电池优化豁免");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RequestIgnoreBatteryOptimization Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求自启动权限(针对不同手机厂商)
|
||||
/// </summary>
|
||||
public static void RequestAutoStartPermission(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manufacturer = Build.Manufacturer?.ToLower();
|
||||
Intent intent = null;
|
||||
|
||||
switch (manufacturer)
|
||||
{
|
||||
case "xiaomi":
|
||||
intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
|
||||
intent.SetClassName("com.miui.securitycenter",
|
||||
"com.miui.permcenter.autostart.AutoStartManagementActivity");
|
||||
intent.PutExtra("extra_pkgname", context.PackageName);
|
||||
break;
|
||||
|
||||
case "oppo":
|
||||
intent = new Intent();
|
||||
intent.SetClassName("com.coloros.safecenter",
|
||||
"com.coloros.safecenter.permission.startup.StartupAppListActivity");
|
||||
break;
|
||||
|
||||
case "vivo":
|
||||
intent = new Intent();
|
||||
intent.SetClassName("com.vivo.permissionmanager",
|
||||
"com.vivo.permissionmanager.activity.BgStartUpManagerActivity");
|
||||
break;
|
||||
|
||||
case "honor":
|
||||
case "huawei":
|
||||
intent = new Intent();
|
||||
intent.SetClassName("com.huawei.systemmanager",
|
||||
"com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
|
||||
break;
|
||||
|
||||
case "oneplus":
|
||||
intent = new Intent();
|
||||
intent.SetClassName("com.oneplus.security",
|
||||
"com.oneplus.security.chainlaunch.view.ChainLaunchAppListActivity");
|
||||
break;
|
||||
|
||||
case "letv":
|
||||
intent = new Intent();
|
||||
intent.SetClassName("com.letv.android.letvsafe",
|
||||
"com.letv.android.letvsafe.AutobootManageActivity");
|
||||
break;
|
||||
}
|
||||
|
||||
if (intent != null)
|
||||
{
|
||||
intent.SetFlags(ActivityFlags.NewTask);
|
||||
if (intent.ResolveActivity(context.PackageManager) != null)
|
||||
{
|
||||
context.StartActivity(intent);
|
||||
System.Diagnostics.Debug.WriteLine($"KeepAliveManager: 请求{manufacturer}自启动权限");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RequestAutoStartPermission Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查服务是否运行
|
||||
/// </summary>
|
||||
public static bool IsServiceRunning(Context context, System.Type serviceType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activityManager = context.GetSystemService(Context.ActivityService) as ActivityManager;
|
||||
var services = activityManager?.GetRunningServices(int.MaxValue);
|
||||
|
||||
if (services != null)
|
||||
{
|
||||
foreach (var service in services)
|
||||
{
|
||||
if (service.Service.ClassName.Contains(serviceType.Name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"IsServiceRunning Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重启所有保活服务
|
||||
/// </summary>
|
||||
public static void RestartKeepAliveServices(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 重启保活服务");
|
||||
|
||||
// 重启前台服务
|
||||
StartForegroundService(context);
|
||||
|
||||
// 重新调度Job
|
||||
ScheduleJobService(context);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RestartKeepAliveServices Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止所有保活服务
|
||||
/// </summary>
|
||||
public static void StopKeepAliveServices(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("KeepAliveManager: 停止保活服务");
|
||||
|
||||
// 停止前台服务
|
||||
var intent = new Intent(context, typeof(HeartRateKeepAliveService));
|
||||
context.StopService(intent);
|
||||
|
||||
// 取消Job调度
|
||||
var jobScheduler = context.GetSystemService(Context.JobSchedulerService) as JobScheduler;
|
||||
jobScheduler?.Cancel(1002);
|
||||
|
||||
_isInitialized = false;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"StopKeepAliveServices Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,11 @@
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Content;
|
||||
using Android.Provider;
|
||||
using AndroidX.Core.App;
|
||||
using AndroidX.Core.Content;
|
||||
using HeartRateMonitorAndroid.Platforms.Android;
|
||||
|
||||
namespace HeartRateMonitorAndroid;
|
||||
|
||||
@ -9,4 +14,178 @@ namespace HeartRateMonitorAndroid;
|
||||
ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
|
||||
public class MainActivity : MauiAppCompatActivity
|
||||
{
|
||||
private const int BATTERY_OPTIMIZATION_REQUEST = 1001;
|
||||
private const int NOTIFICATION_PERMISSION_REQUEST = 1002;
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
// 初始化保活功能
|
||||
InitializeKeepAlive();
|
||||
}
|
||||
|
||||
private void InitializeKeepAlive()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 请求忽略电池优化
|
||||
RequestIgnoreBatteryOptimization();
|
||||
|
||||
// 请求通知权限(Android 13+)
|
||||
RequestNotificationPermission();
|
||||
|
||||
// 启动前台服务
|
||||
StartHeartRateKeepAliveService();
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("保活功能初始化完成");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"InitializeKeepAlive Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestIgnoreBatteryOptimization()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
var powerManager = GetSystemService(PowerService) as PowerManager;
|
||||
if (powerManager != null && !powerManager.IsIgnoringBatteryOptimizations(PackageName))
|
||||
{
|
||||
var intent = new Intent(Settings.ActionRequestIgnoreBatteryOptimizations);
|
||||
intent.SetData(Android.Net.Uri.Parse($"package:{PackageName}"));
|
||||
StartActivityForResult(intent, BATTERY_OPTIMIZATION_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RequestIgnoreBatteryOptimization Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestNotificationPermission()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu)
|
||||
{
|
||||
if (ContextCompat.CheckSelfPermission(this, Android.Manifest.Permission.PostNotifications)
|
||||
!= Permission.Granted)
|
||||
{
|
||||
ActivityCompat.RequestPermissions(this,
|
||||
new[] { Android.Manifest.Permission.PostNotifications },
|
||||
NOTIFICATION_PERMISSION_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"RequestNotificationPermission Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartHeartRateKeepAliveService()
|
||||
{
|
||||
try
|
||||
{
|
||||
var intent = new Intent(this, typeof(HeartRateKeepAliveService));
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
StartForegroundService(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartService(intent);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"StartHeartRateKeepAliveService Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
|
||||
// 每次应用恢复时检查服务状态
|
||||
CheckServiceStatus();
|
||||
}
|
||||
|
||||
private void CheckServiceStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsServiceRunning(typeof(HeartRateKeepAliveService)))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("检测到服务未运行,正在重启...");
|
||||
StartHeartRateKeepAliveService();
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"CheckServiceStatus Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsServiceRunning(System.Type serviceType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activityManager = GetSystemService(ActivityService) as ActivityManager;
|
||||
var services = activityManager?.GetRunningServices(int.MaxValue);
|
||||
|
||||
if (services != null)
|
||||
{
|
||||
foreach (var service in services)
|
||||
{
|
||||
if (service.Service.ClassName.Contains(serviceType.Name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"IsServiceRunning Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
switch (requestCode)
|
||||
{
|
||||
case NOTIFICATION_PERMISSION_REQUEST:
|
||||
if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("通知权限已授予");
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("通知权限被拒绝");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
base.OnActivityResult(requestCode, resultCode, data);
|
||||
|
||||
switch (requestCode)
|
||||
{
|
||||
case BATTERY_OPTIMIZATION_REQUEST:
|
||||
System.Diagnostics.Debug.WriteLine($"电池优化请求结果: {resultCode}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorOnPrimary">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C13.1,2 14,2.9 14,4C14,5.1 13.1,6 12,6C10.9,6 10,5.1 10,4C10,2.9 10.9,2 12,2ZM21,9V7L15,13.5C14.8,13.8 14.5,14 14.1,14H9.9C9.5,14 9.2,13.8 9,13.5L3,7V9C3,9.6 3.4,10 4,10H20C20.6,10 21,9.6 21,9ZM12,15C13.1,15 14,15.9 14,17C14,18.1 13.1,19 12,19C10.9,19 10,18.1 10,17C10,15.9 10.9,15 12,15ZM12,20C13.1,20 14,20.9 14,22C14,23.1 13.1,24 12,24C10.9,24 10,23.1 10,22C10,20.9 10.9,20 12,20Z"/>
|
||||
</vector>
|
||||
|
||||
Reference in New Issue
Block a user