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

如何在Xamarin中设置可穿透DND、重启后生效的闹钟?

嘿,这个需求完全可以在Xamarin中实现,但得针对Android和iOS分别做原生适配——毕竟俩系统的闹钟、DND和后台机制差异不小。下面分平台给你详细讲实现步骤:

一、Android平台实现

Android这边我们要用到系统的AlarmManager,配合广播接收器来实现DND下触发、重启恢复,甚至唤起应用的功能。

1. 配置权限

首先在AndroidManifest.xml中添加必要权限:

<!-- 允许设置精确闹钟(Android 12+) -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- 允许使用精确闹钟(低版本兼容) -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<!-- 接收设备重启广播,用于恢复闹钟 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

2. 创建广播接收器

创建一个BroadcastReceiver来处理闹钟触发和设备重启事件:

[BroadcastReceiver(Enabled = true, Exported = true)]
[IntentFilter(new[] { Intent.ActionBootCompleted, "com.yourapp.ALARM_TRIGGERED" })]
public class AlarmReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        switch (intent.Action)
        {
            case Intent.ActionBootCompleted:
                // 设备重启后恢复之前设置的闹钟
                RestoreAlarm(context);
                break;
            case "com.yourapp.ALARM_TRIGGERED":
                // 闹钟触发:这里可以唤起应用或者播放铃声
                LaunchApp(context);
                PlayAlarmSound(context);
                break;
        }
    }

    private void RestoreAlarm(Context context)
    {
        var prefs = context.GetSharedPreferences("AlarmStorage", FileCreationMode.Private);
        var savedTriggerTime = prefs.GetLong("TriggerTime", 0);
        
        if (savedTriggerTime == 0 || savedTriggerTime < DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
            return;

        var alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);
        var alarmIntent = new Intent(context, typeof(AlarmReceiver))
            .SetAction("com.yourapp.ALARM_TRIGGERED");
        var pendingIntent = PendingIntent.GetBroadcast(
            context, 
            0, 
            alarmIntent, 
            PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);

        // 根据Android版本选择合适的闹钟设置方法
        if (Build.VERSION.SdkInt >= BuildVersionCodes.S)
        {
            alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, savedTriggerTime, pendingIntent);
        }
        else
        {
            alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, savedTriggerTime, pendingIntent);
        }
    }

    private void LaunchApp(Context context)
    {
        // 唤起应用主界面
        var launchIntent = context.PackageManager.GetLaunchIntentForPackage(context.PackageName);
        launchIntent.AddFlags(ActivityFlags.NewTask);
        context.StartActivity(launchIntent);
    }

    private void PlayAlarmSound(Context context)
    {
        // 播放系统默认闹钟铃声
        var ringtoneUri = RingtoneManager.GetDefaultUri(RingtoneType.Alarm);
        var ringtone = RingtoneManager.GetRingtone(context, ringtoneUri);
        ringtone.Play();
    }
}

3. 设置闹钟的核心代码

在你的Activity或者依赖服务中,实现设置持久化闹钟的方法:

public void SetPersistentAlarm(DateTime triggerTime)
{
    var alarmManager = (AlarmManager)GetSystemService(AlarmService);
    var alarmIntent = new Intent(this, typeof(AlarmReceiver))
        .SetAction("com.yourapp.ALARM_TRIGGERED");
    var pendingIntent = PendingIntent.GetBroadcast(
        this, 
        0, 
        alarmIntent, 
        PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);

    var triggerMillis = (long)(triggerTime - new DateTime(1970, 1, 1)).TotalMilliseconds;

    // 选择绕过DND和低功耗模式的闹钟API
    if (Build.VERSION.SdkInt >= BuildVersionCodes.S)
    {
        alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, triggerMillis, pendingIntent);
    }
    else
    {
        alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, triggerMillis, pendingIntent);
    }

    // 将闹钟时间保存到SharedPreferences,用于重启恢复
    var prefs = GetSharedPreferences("AlarmStorage", FileCreationMode.Private);
    var editor = prefs.Edit();
    editor.PutLong("TriggerTime", triggerMillis);
    editor.Apply();
}
二、iOS平台实现

iOS没有直接的闹钟API,我们用本地通知来实现,通过配置关键通知声音绕过DND模式,同时在App启动时恢复通知。

1. 配置权限和Info.plist

首先在Info.plist中添加必要配置:

<key>NSLocalNotificationUsageDescription</key>
<string>需要使用本地通知触发闹钟提醒</string>
<key>NSCriticalAlertUsageDescription</key>
<string>需要在DND模式下播放闹钟提醒</string>
<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

注意:使用关键通知(Critical Alert)需要先在Apple开发者后台给你的应用开启该权限。

2. 请求通知权限并恢复闹钟

在AppDelegate.cs中处理权限请求和重启后的通知恢复:

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    // 请求通知权限(包括关键通知)
    UNUserNotificationCenter.Current.RequestAuthorization(
        UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.Badge,
        (granted, error) =>
        {
            if (granted)
            {
                UIApplication.SharedApplication.RegisterForRemoteNotifications();
            }
        });

    // 设备重启后恢复之前设置的闹钟
    RestoreScheduledAlarm();

    return true;
}

private void RestoreScheduledAlarm()
{
    var savedTime = NSUserDefaults.StandardUserDefaults.DoubleForKey("AlarmTriggerTime");
    if (savedTime == 0)
        return;

    var triggerTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
        .AddSeconds(savedTime);
    
    if (triggerTime > DateTime.UtcNow)
    {
        SetPersistentAlarm(triggerTime);
    }
}

3. 设置本地通知的核心代码

实现设置持久化闹钟的方法:

public void SetPersistentAlarm(DateTime triggerTime)
{
    var content = new UNMutableNotificationContent();
    content.Title = "闹钟提醒";
    content.Body = "你的闹钟时间到了!";
    // 使用关键通知声音,可绕过DND模式
    content.Sound = UNNotificationSound.DefaultCriticalSound;

    // 转换时间为iOS的日期组件
    var triggerDate = NSDate.FromTimeIntervalSince1970(
        (triggerTime - new DateTime(1970, 1, 1)).TotalSeconds);
    var dateComponents = new NSDateComponents
    {
        Year = triggerDate.Year,
        Month = triggerDate.Month,
        Day = triggerDate.Day,
        Hour = triggerDate.Hour,
        Minute = triggerDate.Minute
    };

    var trigger = UNCalendarNotificationTrigger.CreateTrigger(dateComponents, repeats: false);
    var request = UNNotificationRequest.FromIdentifier("PersistentAlarm", content, trigger);

    UNUserNotificationCenter.Current.AddNotificationRequest(request, error =>
    {
        if (error != null)
        {
            Console.WriteLine($"添加闹钟失败: {error.LocalizedDescription}");
            return;
        }

        // 保存触发时间到UserDefaults,用于重启恢复
        NSUserDefaults.StandardUserDefaults.SetDouble(
            (triggerTime - new DateTime(1970, 1, 1)).TotalSeconds, 
            "AlarmTriggerTime");
        NSUserDefaults.StandardUserDefaults.Synchronize();
    });
}

iOS限制:通知触发时无法自动唤起应用,只能通过用户点击通知打开应用——这是苹果的隐私和后台限制,无法绕过。

三、跨平台封装(可选)

为了让Xamarin.Forms的共享代码能统一调用,你可以用DependencyService封装:

  1. 在共享项目中定义接口:
public interface IAlarmService
{
    void SetPersistentAlarm(DateTime triggerTime);
}
  1. 在Android和iOS项目中分别实现该接口,并注册:
// Android项目
[assembly: Dependency(typeof(AndroidAlarmService))]
namespace YourApp.Droid
{
    public class AndroidAlarmService : IAlarmService
    {
        public void SetPersistentAlarm(DateTime triggerTime)
        {
            // 调用前面的Android设置闹钟代码
            var activity = Forms.Context as MainActivity;
            activity.SetPersistentAlarm(triggerTime);
        }
    }
}

// iOS项目
[assembly: Dependency(typeof(iOSAlarmService))]
namespace YourApp.iOS
{
    public class iOSAlarmService : IAlarmService
    {
        public void SetPersistentAlarm(DateTime triggerTime)
        {
            // 调用前面的iOS设置闹钟代码
            SetPersistentAlarm(triggerTime);
        }
    }
}
  1. 在共享代码中调用:
DependencyService.Get<IAlarmService>().SetPersistentAlarm(DateTime.Now.AddMinutes(5));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:13:18