.NET MAUI Android前台服务后台运行数分钟后停止,API调用丢失
问题:.NET MAUI后台服务无法持续监听未接来电
我用.NET MAUI开发应用,需要持续监听未接来电并把号码发送到API。已经实现前台服务,但设备进入空闲或应用未打开时,服务运行几分钟就停止,导致API调用丢失。试过带持久通知的前台服务,还是没法稳定运行。以下是我的实现代码:
MainActivity.cs
using Android; using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using Android.Provider; // For Settings.Secure using AndroidX.Core.App; using AndroidX.Core.Content; using Android.Widget; namespace MissedCallSMSService.Platforms.Android { [Activity(Label = "MissedCallSMSService", MainLauncher = true)] public class MainActivity : Activity { const int RequestCallLogPermissionId = 0; const int RequestNotificationAccessId = 1; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // Start the MissedCallService StartMissedCallService(); // Check and request permissions CheckAndRequestPermissions(); } private void StartMissedCallService() { Intent serviceIntent = new Intent(this, typeof(MissedCallService)); if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { StartForegroundService(serviceIntent); } else { StartService(serviceIntent); } } void CheckAndRequestPermissions() { if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadPhoneState) != Permission.Granted || ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadCallLog) != Permission.Granted) { ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.ReadPhoneState, Manifest.Permission.ReadCallLog }, RequestCallLogPermissionId); } else { CheckAndRequestNotificationPermission(); } } void CheckAndRequestNotificationPermission() { if (!IsNotificationServiceEnabled()) { // Guide the user to the notification access settings Intent intent = new Intent(Settings.ActionNotificationListenerSettings); StartActivityForResult(intent, RequestNotificationAccessId); } } public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == RequestCallLogPermissionId) { if (grantResults.Length > 0 && grantResults[0] == Permission.Granted) { // Permissions granted CheckAndRequestNotificationPermission(); } else { // Permissions denied Toast.MakeText(this, "Permissions denied. The app requires call log access to function properly.", ToastLength.Long).Show(); } } } private bool IsNotificationServiceEnabled() { string pkgName = PackageName; string flat = Settings.Secure.GetString(ContentResolver, "enabled_notification_listeners"); if (flat != null && flat.Contains(pkgName)) { return true; } return false; } } }
MissedCallService.cs
using Android; using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using Android.Util; using AndroidX.Core.App; using AndroidX.Core.Content; using System.Threading; using System.Threading.Tasks; namespace MissedCallSMSService.Platforms.Android { [Service] public class MissedCallService : Service { private MissedCallReceiver mReceiver; private const int NotificationId = 1; private string NOTIFICATION_CHANNEL_ID = "1000"; private int NOTIFICATION_ID = 1; private string NOTIFICATION_CHANNEL_NAME = "notification"; public override IBinder OnBind(Intent intent) { return null; } public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { // Run the service in the foreground to ensure it continues running Notification notification = new NotificationCompat.Builder(this, "missed_call_channel") .SetContentTitle("Missed Call Service") .SetContentText("Listening for missed calls") .SetSmallIcon(Resource.Drawable.maui_splash_image) .Build(); // StartForeground(NotificationId, notification); StartForegroundService(); // Start listening for missed calls ListenForMissedCalls(); return StartCommandResult.Sticky; } private void StartForegroundService() { var notifcationManager = GetSystemService(Context.NotificationService) as NotificationManager; if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { CreateNotificationChannel(notifcationManager); } var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); notification.SetAutoCancel(false); notification.SetOngoing(true); notification.SetSmallIcon(Resource.Mipmap.appicon); notification.SetContentTitle("ForegroundService"); notification.SetContentText("Foreground Service is running"); StartForeground(NOTIFICATION_ID, notification.Build()); } private void CreateNotificationChannel(NotificationManager notificationManager) { var channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationImportance.High); notificationManager.CreateNotificationChannel(channel); } private void ListenForMissedCalls() { mReceiver = new MissedCallReceiver(); var filter = new IntentFilter(); filter.AddAction("android.intent.action.PHONE_STATE"); RegisterReceiver(mReceiver, filter); // Check periodically if permissions are granted and notify if not Task.Run(() => { while (true) { Thread.Sleep(5000); // Sleep for 5 seconds before checking again if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadPhoneState) != Permission.Granted || ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadCallLog) != Permission.Granted) { NotifyUserToGrantPermissions(); } } }); } private void NotifyUserToGrantPermissions() { NotificationManager notificationManager = (NotificationManager)GetSystemService(NotificationService); if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { var channel = new NotificationChannel("missed_call_channel", "Missed Call Channel", NotificationImportance.High) { Description = "Notifications for missed call permissions" }; notificationManager.CreateNotificationChannel(channel); } var notificationIntent = new Intent(this, typeof(MainActivity)); notificationIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask); var pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent, PendingIntentFlags.UpdateCurrent); var builder = new NotificationCompat.Builder(this, "missed_call_channel") .SetContentTitle("Missed Call Service") .SetContentText("Please grant phone state and call log permissions.") .SetSmallIcon(Resource.Drawable.maui_splash_image) .SetContentIntent(pendingIntent) .SetAutoCancel(true); notificationManager.Notify(NotificationId, builder.Build()); } public override void OnDestroy() { base.OnDestroy(); if (mReceiver != null) { UnregisterReceiver(mReceiver); } } } }
解决方案与修改建议
一、修复前台服务核心问题
统一通知通道,修正前台服务启动逻辑
代码中存在多个通知通道,且OnStartCommand中注释了正确的StartForeground调用,导致服务稳定性受影响。修改如下:public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { // 确保通知通道存在 CreateNotificationChannel(); // 构建前台通知(设为不可取消) var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .SetContentTitle("未接来电监听服务") .SetContentText("正在后台运行") .SetSmallIcon(Resource.Mipmap.appicon) .SetOngoing(true) .Build(); // 必须在服务启动后5秒内调用StartForeground StartForeground(NOTIFICATION_ID, notification); ListenForMissedCalls(); return StartCommandResult.RedeliverIntent; } private void CreateNotificationChannel() { if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { var channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "未接来电监听通道", NotificationImportance.Low) { Description = "显示后台服务运行状态" }; var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager; notificationManager.CreateNotificationChannel(channel); } }移除不必要的后台轮询
用Task.Run加Thread.Sleep的轮询会浪费资源,改为仅在权限检查环节(如启动、权限回调)触发提醒,避免后台循环。
二、适配Android后台限制
申请忽略电池优化权限
Android Doze模式会限制空闲设备的后台服务,需引导用户将应用加入白名单:// 在MainActivity的权限检查中添加 void CheckBatteryOptimization() { var powerManager = GetSystemService(Context.PowerService) as PowerManager; if (Build.VERSION.SdkInt >= BuildVersionCodes.M && !powerManager.IsIgnoringBatteryOptimizations(PackageName)) { Intent intent = new Intent(Settings.ActionRequestIgnoreBatteryOptimizations); intent.SetData(Android.Net.Uri.Parse($"package:{PackageName}")); StartActivity(intent); } }同时在
AndroidManifest.xml中添加权限:<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />静态注册广播接收器
动态注册的接收器在服务销毁后失效,改为在AndroidManifest.xml中静态注册MissedCallReceiver:<receiver android:name=".Platforms.Android.MissedCallReceiver"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> </receiver>可在接收器中添加重启服务的逻辑,确保服务被回收后能恢复。
设置正确的服务重启策略
返回StartCommandResult.RedeliverIntent,让系统在服务被杀死后尝试重启并传递未完成的Intent:return StartCommandResult.RedeliverIntent;
三、完善权限管理
处理通知访问权限回调
补充OnActivityResult处理通知权限的授权结果:protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == RequestNotificationAccessId) { if (!IsNotificationServiceEnabled()) { Toast.MakeText(this, "未授予通知访问权限,无法正常监听", ToastLength.Long).Show(); } } }补充必要权限
在AndroidManifest.xml中添加呼出电话处理权限(如果需要):<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
四、其他最佳实践
- 网络操作单独线程执行:API调用等耗时操作放在后台线程,避免阻塞服务主线程
- 适配厂商后台策略:小米、华为等厂商有额外后台限制,需引导用户开启"自启动权限"和"后台运行权限"
- 用WorkManager处理周期性任务:如果需要定期同步数据,优先使用WorkManager替代自定义轮询
内容的提问来源于stack exchange,提问作者Thofa Technologies
相关产品推荐
相关产品推荐

