如何在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封装:
- 在共享项目中定义接口:
public interface IAlarmService { void SetPersistentAlarm(DateTime triggerTime); }
- 在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); } } }
- 在共享代码中调用:
DependencyService.Get<IAlarmService>().SetPersistentAlarm(DateTime.Now.AddMinutes(5));
内容的提问来源于stack exchange,提问作者Vlad
相关产品推荐
相关产品推荐

