You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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);
            }
        }
    }
}
解决方案与修改建议

一、修复前台服务核心问题

  1. 统一通知通道,修正前台服务启动逻辑
    代码中存在多个通知通道,且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);
        }
    }
    
  2. 移除不必要的后台轮询
    用Task.Run加Thread.Sleep的轮询会浪费资源,改为仅在权限检查环节(如启动、权限回调)触发提醒,避免后台循环。

二、适配Android后台限制

  1. 申请忽略电池优化权限
    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" />
    
  2. 静态注册广播接收器
    动态注册的接收器在服务销毁后失效,改为在AndroidManifest.xml中静态注册MissedCallReceiver:

    <receiver android:name=".Platforms.Android.MissedCallReceiver">
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
    </receiver>
    

    可在接收器中添加重启服务的逻辑,确保服务被回收后能恢复。

  3. 设置正确的服务重启策略
    返回StartCommandResult.RedeliverIntent,让系统在服务被杀死后尝试重启并传递未完成的Intent:

    return StartCommandResult.RedeliverIntent;
    

三、完善权限管理

  1. 处理通知访问权限回调
    补充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();
            }
        }
    }
    
  2. 补充必要权限
    在AndroidManifest.xml中添加呼出电话处理权限(如果需要):

    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    

四、其他最佳实践

  • 网络操作单独线程执行:API调用等耗时操作放在后台线程,避免阻塞服务主线程
  • 适配厂商后台策略:小米、华为等厂商有额外后台限制,需引导用户开启"自启动权限"和"后台运行权限"
  • 用WorkManager处理周期性任务:如果需要定期同步数据,优先使用WorkManager替代自定义轮询

内容的提问来源于stack exchange,提问作者Thofa Technologies

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:52:33