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

Android后台启动来电前台服务报错:MAUI自定义来电通知合规方案问询

解决MAUI中Android 12+后台触发自定义来电通知的合规方案

针对Android 12+对后台启动前台服务的限制,结合来电场景的高优先级特性,以下是合规的实现方案:

核心思路

Android 12+允许在高优先级推送的处理上下文中启动前台服务,同时来电类型的通知(Notification.CategoryCall)属于系统认可的高优先级场景,可规避后台启动限制。

步骤1:确保推送为高优先级

如果使用FCM推送,发送时需设置priority: high(Android)或content_available: true(iOS),确保应用在后台能被短暂唤醒,获得启动前台服务的权限。

步骤2:修改推送接收器,直接触发前台服务

在推送消息的处理逻辑中(比如FCM的MyFirebaseMessagingService),直接启动来电前台服务,此时处于推送唤醒的活跃上下文,符合系统规则:

[Service(Exported = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class MyFirebaseMessagingService : FirebaseMessagingService
{
    public override void OnMessageReceived(RemoteMessage message)
    {
        if (message.Data.TryGetValue("is_incoming_call", out _))
        {
            string callId = message.Data["call_id"];
            string callTitle = message.Data["call_title"];
            StartCallService(callId, callTitle);
        }
    }

    private void StartCallService(string callId, string callTitle)
    {
        Intent serviceIntent = new Intent(this, typeof(DroidCallService));
        serviceIntent.SetAction("START_SERVICE");
        serviceIntent.PutExtra("call_id", callId);
        serviceIntent.PutExtra("call_title", callTitle);

        if (Build.VERSION.SdkInt >= BuildVersionCodes.S)
        {
            StartForegroundService(serviceIntent);
        }
        else
        {
            StartService(serviceIntent);
        }
    }
}

步骤3:优化前台服务,确保5秒内启动前台

修改DroidCallService,在OnStartCommand中立即构建并显示来电通知,必须在服务启动后5秒内完成StartForeground调用:

[Service(ForegroundServiceType = Android.Content.PM.ForegroundService.TypePhoneCall, Exported = false)]
internal class DroidCallService : Service
{
    private const int NotificationId = 1001;

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        if (intent.Action == "START_SERVICE")
        {
            var callID = intent.GetStringExtra("call_id");
            var name = intent.GetStringExtra("call_title");
            RegisterNotification(callID, name);
        }
        else if (intent.Action == "STOP_SERVICE")
        {
            StopForeground(StopForegroundFlags.Remove);
            StopSelfResult(startId);
        }

        return StartCommandResult.NotSticky;
    }

    void RegisterNotification(string cid, string title)
    {
        // 构建来电者信息,替换为实际资源和逻辑
        var incomingCaller = new Person.Builder()
            .SetName(title)
            .SetIcon(Icon.CreateWithResource(this, Resource.Drawable.ic_call_caller))
            .Build();

        // 拒接动作的PendingIntent
        var rejectIntent = new Intent(this, typeof(CallActionReceiver))
            .SetAction("REJECT_CALL")
            .PutExtra("call_id", cid);
        var rejectPendingIntent = PendingIntent.GetBroadcast(
            this, 
            0, 
            rejectIntent, 
            Build.VERSION.SdkInt >= BuildVersionCodes.S ? PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable : PendingIntentFlags.UpdateCurrent);

        // 接听动作的PendingIntent
        var answerIntent = new Intent(this, typeof(CallActionReceiver))
            .SetAction("ANSWER_CALL")
            .PutExtra("call_id", cid);
        var answerPendingIntent = PendingIntent.GetBroadcast(
            this, 
            1, 
            answerIntent, 
            Build.VERSION.SdkInt >= BuildVersionCodes.S ? PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable : PendingIntentFlags.UpdateCurrent);

        // 点击通知跳转主页面的Intent
        var clickIntent = new Intent(this, typeof(MainActivity));
        clickIntent.AddFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTop);
        clickIntent.PutExtra("field1thatneeded", "abcde");
        var pendingIntent = PendingIntent.GetActivity(
            this, 
            2, 
            clickIntent, 
            Build.VERSION.SdkInt >= BuildVersionCodes.S ? PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable : PendingIntentFlags.UpdateCurrent);

        // 构建来电通知
        var notificationBuilder = new Notification.Builder(this, AppConstants.CallChannelID)
            .SetAutoCancel(false)
            .SetOngoing(true)
            .SetContentTitle("Incoming call")
            .SetContentText(title)
            .SetContentIntent(pendingIntent)
            .SetStyle(Notification.CallStyle.ForIncomingCall(incomingCaller, rejectPendingIntent, answerPendingIntent))
            .SetCategory(Notification.CategoryCall)
            .SetPriority(NotificationPriority.High)
            .SetVisibility(NotificationVisibility.Public);

        var notification = notificationBuilder.Build();

        // 启动前台服务
        if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
        {
            StartForeground(NotificationId, notification, Android.Content.PM.ForegroundService.TypePhoneCall);
        }
        else
        {
            StartForeground(NotificationId, notification);
        }
    }

    public override IBinder OnBind(Intent intent) => null;
}

步骤4:添加广播接收器处理通话动作

创建广播接收器处理接听/拒接按钮的点击事件:

[BroadcastReceiver(Exported = false)]
internal class CallActionReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        if (!intent.HasExtra("call_id")) return;
        
        string callId = intent.GetStringExtra("call_id");
        switch (intent.Action)
        {
            case "REJECT_CALL":
                // 执行拒接逻辑,比如通知服务器、清理资源
                StopCallService(context);
                break;
            case "ANSWER_CALL":
                // 跳转至通话页面
                Intent activityIntent = new Intent(context, typeof(CallActivity));
                activityIntent.AddFlags(ActivityFlags.NewTask);
                activityIntent.PutExtra("call_id", callId);
                context.StartActivity(activityIntent);
                StopCallService(context);
                break;
        }
    }

    private void StopCallService(Context context)
    {
        Intent stopIntent = new Intent(context, typeof(DroidCallService));
        stopIntent.SetAction("STOP_SERVICE");
        context.StartService(stopIntent);
    }
}

步骤5:配置高优先级通知渠道

在应用启动时创建来电专用的高优先级通知渠道,确保通知能正常显示:

public static void CreateCallNotificationChannel(Context context)
{
    if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
    {
        var channel = new NotificationChannel(
            AppConstants.CallChannelID,
            "Incoming Calls",
            NotificationImportance.High)
        {
            Description = "Handles incoming call notifications"
        };
        // 设置来电铃声
        channel.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Ringtone), null);
        var notificationManager = context.GetSystemService(NotificationService) as NotificationManager;
        notificationManager?.CreateNotificationChannel(channel);
    }
}

在MainActivity的OnCreate中调用该方法:

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);
    CreateCallNotificationChannel(this);
}

合规性验证

  • 该方案依赖推送唤醒的活跃上下文,属于Android 12+允许的前台服务启动场景,无需申请特殊权限。
  • 来电通知的CategoryCall标记会让系统识别为紧急通知,提升显示优先级,避免被后台限制拦截。

内容的提问来源于stack exchange,提问作者Serg Tomcat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:47:09