移动应用通知简介:为什么通知这么重要?
说实话,做移动应用开发这些年,我越来越觉得通知系统就是应用和用户之间那根看不见的线。想想看——社交媒体的新消息提醒、电商的促销推送、健康App的运动打卡……这些通知在悄无声息地拉着用户回到应用里来。
通知主要就两大类:本地通知和推送通知。
本地通知由应用本身在设备上触发,不需要联网就能工作,适合做闹钟、提醒事项这类场景。推送通知则通过远程服务器发送,即使应用没在运行也能送达用户——实时消息、营销活动这类需要服务器主动推的场景就得靠它了。
.NET MAUI(Multi-platform App UI)作为微软的跨平台开发框架,给开发者提供了统一的API来构建iOS、Android、Windows和macOS应用。不过呢,通知这块涉及各平台的原生特性,平台差异还挺多的,需要特别注意。
这篇文章会从头到尾带你走一遍在.NET MAUI中实现本地通知和推送通知的完整流程。准备好了吗?那我们开始吧。
本地通知基础:原生方法与插件选择
.NET MAUI实现本地通知有几种方式。你可以直接调平台原生API,也可以借助社区插件来简化开发。目前最流行的方案是Plugin.LocalNotification,它提供了统一的API接口,帮你把底层平台差异给屏蔽掉了(这一点真的很香)。
Plugin.LocalNotification的安装与配置
首先,通过NuGet安装:
dotnet add package Plugin.LocalNotification --version 11.1.0
然后在MauiProgram.cs里注册服务:
using Plugin.LocalNotification;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseLocalNotification() // 注册本地通知服务
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
发送简单的本地通知
用Plugin.LocalNotification发通知真的很简单,看一下这个基本示例就明白了:
using Plugin.LocalNotification;
public async Task SendSimpleNotification()
{
var notification = new NotificationRequest
{
NotificationId = 1001,
Title = "欢迎使用",
Subtitle = "新功能上线",
Description = "查看我们为您准备的全新功能和优惠活动!",
BadgeNumber = 1,
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Now.AddSeconds(5) // 5秒后显示
}
};
await LocalNotificationCenter.Current.Show(notification);
}
立即显示通知
如果不需要延迟,直接省掉Schedule属性就行了:
public async Task SendImmediateNotification()
{
var notification = new NotificationRequest
{
NotificationId = 1002,
Title = "消息提醒",
Description = "您有一条新消息待查看",
ReturningData = "NavigateToMessages" // 用户点击时返回的数据
};
await LocalNotificationCenter.Current.Show(notification);
}
取消和管理通知
通知的管理也很灵活,该取消取消,该清除清除:
// 取消特定ID的通知
await LocalNotificationCenter.Current.Cancel(1001);
// 取消所有待处理的通知
await LocalNotificationCenter.Current.CancelAll();
// 清除所有已显示的通知
await LocalNotificationCenter.Current.Clear();
// 获取所有待处理的通知
var pendingNotifications = await LocalNotificationCenter.Current.GetPendingNotificationList();
// 获取所有已送达的通知
var deliveredNotifications = await LocalNotificationCenter.Current.GetDeliveredNotificationList();
Android通知渠道:精细化通知管理
从Android 8.0(API 26)开始,Google搞了个叫通知渠道(Notification Channels)的东西。简单来说,就是让用户可以对不同类型的通知进行精细化控制——可以单独设置每个渠道的重要性、声音、震动等等。
理解通知渠道的重要性
举个例子,一个新闻App可以创建"突发新闻"、"体育赛事"、"娱乐资讯"等不同渠道,用户能选择只收突发新闻的通知,把其他的都关掉。这个设计思路其实挺人性化的。
重要的是:在Android 8.0及以上版本,如果不创建通知渠道,通知就压根显示不出来。所以这一步千万不能跳过。
创建通知渠道
用Plugin.LocalNotification创建渠道的代码如下:
public void CreateNotificationChannels()
{
if (DeviceInfo.Platform == DevicePlatform.Android)
{
// 创建重要通知渠道
var urgentChannel = new NotificationChannel
{
Id = "urgent_channel",
Name = "重要通知",
Description = "用于显示紧急和重要的通知消息",
Importance = NotificationImportance.High,
Sound = "notification_urgent.mp3"
};
// 创建常规通知渠道
var generalChannel = new NotificationChannel
{
Id = "general_channel",
Name = "常规通知",
Description = "用于显示一般性的通知消息",
Importance = NotificationImportance.Default
};
// 创建静默通知渠道
var silentChannel = new NotificationChannel
{
Id = "silent_channel",
Name = "静默通知",
Description = "不会发出声音的通知",
Importance = NotificationImportance.Low,
Sound = null
};
LocalNotificationCenter.Current.CreateNotificationChannel(urgentChannel);
LocalNotificationCenter.Current.CreateNotificationChannel(generalChannel);
LocalNotificationCenter.Current.CreateNotificationChannel(silentChannel);
}
}
通知重要性级别详解
Android一共定义了五个通知重要性级别,搞清楚它们的区别很有必要:
- None(无):通知直接不显示,用户完全看不到
- Min(最低):会出现在状态栏和通知抽屉里,但没声音也没震动
- Low(低):通知会显示,不发声不振动,锁屏上也不会出现
- Default(默认):发出声音,但不会弹窗
- High(高):既发声又弹窗提醒(Heads-up notification),最醒目的那种
使用指定渠道发送通知
public async Task SendChannelNotification()
{
var notification = new NotificationRequest
{
NotificationId = 2001,
Title = "系统升级通知",
Description = "系统将在今晚23:00进行维护升级,预计持续2小时",
Android = new AndroidOptions
{
ChannelId = "urgent_channel", // 使用重要通知渠道
Priority = AndroidPriority.High,
VisibilityType = AndroidVisibilityType.Public
}
};
await LocalNotificationCenter.Current.Show(notification);
}
自定义通知样式和行为
Android支持的自定义选项真的很丰富,大图标、颜色、进度条、LED灯、震动模式……基本上你想要的都能配:
public async Task SendCustomStyledNotification()
{
var notification = new NotificationRequest
{
NotificationId = 2002,
Title = "订单配送中",
Description = "您的订单正在配送中,预计30分钟内送达",
CategoryType = NotificationCategoryType.Status,
Android = new AndroidOptions
{
ChannelId = "general_channel",
Priority = AndroidPriority.High,
// 设置大图标
IconName = "delivery_icon",
// 设置颜色
Color = new AndroidColor(0, 122, 255),
// 自动取消(用户点击后自动消失)
AutoCancel = true,
// 显示时间戳
ShowTimestamp = true,
// 设置进度条
ProgressBarMax = 100,
ProgressBarProgress = 65,
ProgressBarIndeterminate = false,
// LED灯效果
LedColor = new AndroidColor(0, 255, 0),
// 震动模式(单位:毫秒)
VibrationPattern = new long[] { 0, 500, 200, 500 }
}
};
await LocalNotificationCenter.Current.Show(notification);
}
定时通知:让应用在对的时间提醒用户
定时通知大概是本地通知最常用的场景了。Plugin.LocalNotification在这方面做得不错,支持一次性通知、重复通知以及基于日期时间的复杂调度。
一次性定时通知
public async Task ScheduleOneTimeNotification()
{
var notification = new NotificationRequest
{
NotificationId = 3001,
Title = "会议提醒",
Description = "项目评审会议将在15分钟后开始,请准时参加",
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Now.AddMinutes(15)
}
};
await LocalNotificationCenter.Current.Show(notification);
}
每日重复通知
public async Task ScheduleDailyNotification()
{
var notification = new NotificationRequest
{
NotificationId = 3002,
Title = "每日健康提醒",
Description = "该起来活动一下了!保持健康的生活习惯。",
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Today.AddHours(9), // 每天上午9点
RepeatType = NotificationRepeat.Daily
}
};
await LocalNotificationCenter.Current.Show(notification);
}
每周重复通知
public async Task ScheduleWeeklyNotification()
{
var notification = new NotificationRequest
{
NotificationId = 3003,
Title = "周报提醒",
Description = "本周工作周报截止日期为今天17:00,请及时提交。",
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Today.AddHours(10), // 上午10点
RepeatType = NotificationRepeat.Weekly
}
};
await LocalNotificationCenter.Current.Show(notification);
}
复杂的定时场景
遇到更复杂的需求怎么办?比如用药提醒一天要提醒三次。这时候可以用循环创建多个通知实例:
public async Task ScheduleMultipleMedicationReminders()
{
// 用药提醒:每天早中晚三次
var times = new[] { 8, 13, 20 }; // 8:00, 13:00, 20:00
for (int i = 0; i < times.Length; i++)
{
var notification = new NotificationRequest
{
NotificationId = 4000 + i,
Title = "用药提醒",
Description = $"该服用{GetMealTime(i)}的药物了,请按时服药。",
Schedule = new NotificationRequestSchedule
{
NotifyTime = DateTime.Today.AddHours(times[i]),
RepeatType = NotificationRepeat.Daily
},
Android = new AndroidOptions
{
ChannelId = "urgent_channel",
Priority = AndroidPriority.High
}
};
await LocalNotificationCenter.Current.Show(notification);
}
}
private string GetMealTime(int index)
{
return index switch
{
0 => "早餐后",
1 => "午餐后",
2 => "晚餐后",
_ => "餐后"
};
}
Firebase Cloud Messaging:Android推送通知实现
接下来到推送通知了。Firebase Cloud Messaging(FCM)是Google提供的免费跨平台消息推送方案,支持Android、iOS和Web。老实说,对于大多数项目来说,FCM基本就是标配了。
Firebase项目配置
写代码之前,得先在Firebase控制台把东西配好:
- 访问Firebase Console(https://console.firebase.google.com)
- 创建新项目或选择现有项目
- 添加Android应用,包名要和.NET MAUI项目的ApplicationId一致(这点容易搞错,注意一下)
- 下载
google-services.json文件 - 把
google-services.json放到Android项目的根目录
安装Plugin.FirebasePushNotifications
这里用的是社区维护的Plugin.FirebasePushNotifications v3.x插件:
dotnet add package Plugin.FirebasePushNotifications --version 3.0.0
配置Android项目
编辑Platforms/Android/AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true"
android:icon="@mipmap/appicon"
android:roundIcon="@mipmap/appicon_round"
android:supportsRtl="true">
</application>
<!-- Firebase所需权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Android 13+通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>
然后确保google-services.json的构建操作设置为GoogleServicesJson。在项目文件里加上:
<ItemGroup Condition="$(TargetFramework.Contains('android'))">
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
</ItemGroup>
注册Firebase推送通知服务
在MauiProgram.cs中注册:
using Plugin.FirebasePushNotifications;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseLocalNotification()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
#if ANDROID || IOS
builder.Services.AddSingleton<IFirebasePushNotification>(
FirebasePushNotificationManager.Current);
#endif
return builder.Build();
}
}
初始化Firebase并获取设备令牌
在App.xaml.cs或主页面中做初始化。这部分代码稍微长一点,但每一步都很关键:
public partial class App : Application
{
public App()
{
InitializeComponent();
InitializeFirebase();
MainPage = new AppShell();
}
private void InitializeFirebase()
{
// 订阅令牌刷新事件
CrossFirebasePushNotification.Current.OnTokenRefresh += (s, token) =>
{
System.Diagnostics.Debug.WriteLine($"FCM Token: {token}");
// 将令牌发送到您的服务器
SendTokenToServer(token);
};
// 订阅通知接收事件
CrossFirebasePushNotification.Current.OnNotificationReceived += (s, data) =>
{
System.Diagnostics.Debug.WriteLine("通知已收到");
foreach (var key in data.Data.Keys)
{
System.Diagnostics.Debug.WriteLine($"{key}: {data.Data[key]}");
}
};
// 订阅通知打开事件
CrossFirebasePushNotification.Current.OnNotificationOpened += (s, response) =>
{
System.Diagnostics.Debug.WriteLine("通知被用户打开");
HandleNotificationNavigation(response.Data);
};
// 订阅通知删除事件
CrossFirebasePushNotification.Current.OnNotificationDeleted += (s, data) =>
{
System.Diagnostics.Debug.WriteLine("通知被用户删除");
};
}
private void SendTokenToServer(string token)
{
// 实现将设备令牌发送到您的后端服务器
// 以便服务器可以向此设备发送推送通知
}
private void HandleNotificationNavigation(IDictionary<string, object> data)
{
if (data.ContainsKey("navigationTarget"))
{
var target = data["navigationTarget"].ToString();
Shell.Current.GoToAsync($"//{target}");
}
}
}
订阅主题
FCM还支持主题订阅功能,可以向订阅了某个主题的所有用户发消息。这个在做新闻推送、活动通知的时候特别好用:
public async Task SubscribeToTopics()
{
// 订阅"体育新闻"主题
await CrossFirebasePushNotification.Current.SubscribeAsync("sports_news");
// 订阅"促销活动"主题
await CrossFirebasePushNotification.Current.SubscribeAsync("promotions");
System.Diagnostics.Debug.WriteLine("已订阅主题");
}
public async Task UnsubscribeFromTopics()
{
// 取消订阅
await CrossFirebasePushNotification.Current.UnsubscribeAsync("promotions");
System.Diagnostics.Debug.WriteLine("已取消订阅");
}
从服务器发送FCM消息
服务器端用Firebase Admin SDK或HTTP API来发消息。下面是用HTTP API的C#示例:
public async Task SendFcmNotification(string deviceToken, string title, string body)
{
var serverKey = "YOUR_FIREBASE_SERVER_KEY"; // 从Firebase控制台获取
var senderId = "YOUR_SENDER_ID";
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(
"Authorization", $"key={serverKey}");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(
"Sender", $"id={senderId}");
var payload = new
{
to = deviceToken,
notification = new
{
title = title,
body = body,
sound = "default",
badge = 1
},
data = new
{
navigationTarget = "MessagesPage",
messageId = "12345",
timestamp = DateTime.UtcNow.ToString("o")
},
priority = "high"
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(
"https://fcm.googleapis.com/fcm/send", content);
if (response.IsSuccessStatusCode)
{
Console.WriteLine("通知发送成功");
}
else
{
var error = await response.Content.ReadAsStringAsync();
Console.WriteLine($"通知发送失败: {error}");
}
}
Apple Push Notification Service:iOS推送通知配置
搞完Android,接下来看iOS这边。APNs(苹果推送通知服务)是iOS、iPadOS、macOS和watchOS上推送通知的基础设施。坦白说,配置APNs比FCM要麻烦不少,需要在Apple Developer Portal做好几步设置。
Apple Developer配置步骤
- 登录Apple Developer Portal(https://developer.apple.com)
- 创建App ID,启用Push Notifications能力
- 创建APNs证书(开发和生产环境各一个)或配置APNs Auth Key(推荐用Auth Key,省事)
- 创建Provisioning Profile,包含Push Notifications权限
- 下载并安装证书和配置文件
配置iOS项目
编辑Platforms/iOS/Entitlements.plist,添加推送通知权限:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<!-- 生产环境使用 "production" -->
</dict>
</plist>
编辑Platforms/iOS/Info.plist,加上后台模式:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
iOS推送通知初始化
Plugin.FirebasePushNotifications在iOS上也能用。Firebase会自动帮你跟APNs对接:
// 在Platforms/iOS/AppDelegate.cs中
using Foundation;
using UIKit;
using UserNotifications;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
// 请求通知权限
UNUserNotificationCenter.Current.RequestAuthorization(
UNAuthorizationOptions.Alert |
UNAuthorizationOptions.Badge |
UNAuthorizationOptions.Sound,
(granted, error) =>
{
if (granted)
{
InvokeOnMainThread(() =>
{
UIApplication.SharedApplication.RegisterForRemoteNotifications();
});
}
});
return base.FinishedLaunching(app, options);
}
public override void RegisteredForRemoteNotifications(
UIApplication application, NSData deviceToken)
{
base.RegisteredForRemoteNotifications(application, deviceToken);
}
public override void FailedToRegisterForRemoteNotifications(
UIApplication application, NSError error)
{
System.Diagnostics.Debug.WriteLine($"注册推送失败: {error.LocalizedDescription}");
base.FailedToRegisterForRemoteNotifications(application, error);
}
}
配置Firebase以支持iOS
在Firebase控制台的iOS应用设置中,还需要做这几步:
- 上传APNs认证密钥(.p8文件)或APNs证书(.p12文件)
- 输入密钥ID和团队ID(用Auth Key的话)
- 下载
GoogleService-Info.plist文件 - 把
GoogleService-Info.plist放到Platforms/iOS目录下 - 构建操作设置为BundleResource
Azure Notification Hubs:统一的跨平台推送后端
如果你的项目规模比较大,或者已经在用Azure生态,那Azure Notification Hubs可能是更好的选择。它是微软提供的企业级推送通知服务,支持所有主流平台。
Azure Notification Hubs的优势
为什么要考虑它?几个关键优势:
- 统一管理:一个后端API搞定所有平台的推送
- 标签和模板:灵活的用户分组和消息定制能力
- 大规模推送:支持数百万设备同时推送(这个在To C应用里很重要)
- 详细分析:推送成功率、设备注册等数据一目了然
- 企业级功能:调度推送、A/B测试等高级玩法都有
安装Azure Notification Hubs SDK
dotnet add package Microsoft.Azure.NotificationHubs --version 4.2.0
客户端设备注册
在.NET MAUI应用中把设备注册到Azure Notification Hub:
using Microsoft.Azure.NotificationHubs;
public class NotificationService
{
private const string NotificationHubName = "your-hub-name";
private const string ListenConnectionString = "Endpoint=sb://...";
private NotificationHubClient _hubClient;
public NotificationService()
{
_hubClient = NotificationHubClient.CreateClientFromConnectionString(
ListenConnectionString, NotificationHubName);
}
public async Task RegisterDeviceAsync(string platform, string deviceToken, string[] tags)
{
try
{
if (platform == "iOS")
{
await RegisterForApns(deviceToken, tags);
}
else if (platform == "Android")
{
await RegisterForFcm(deviceToken, tags);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"注册失败: {ex.Message}");
}
}
private async Task RegisterForApns(string deviceToken, string[] tags)
{
var registration = new AppleRegistrationDescription(deviceToken, tags);
await _hubClient.CreateOrUpdateRegistrationAsync(registration);
System.Diagnostics.Debug.WriteLine("iOS设备注册成功");
}
private async Task RegisterForFcm(string deviceToken, string[] tags)
{
var registration = new FcmRegistrationDescription(deviceToken, tags);
await _hubClient.CreateOrUpdateRegistrationAsync(registration);
System.Diagnostics.Debug.WriteLine("Android设备注册成功");
}
}
使用标签进行精准推送
标签系统是Azure Notification Hubs的核心亮点。你可以给设备打上各种标签,然后按标签组合来定向推送:
public async Task RegisterWithTags(string deviceToken)
{
var tags = new[]
{
"userId:12345", // 用户ID
"language:zh-CN", // 语言偏好
"location:beijing", // 地理位置
"interests:sports", // 兴趣标签
"interests:technology", // 可以有多个兴趣
"vip:true" // VIP状态
};
var platform = DeviceInfo.Platform == DevicePlatform.iOS ? "iOS" : "Android";
await RegisterDeviceAsync(platform, deviceToken, tags);
}
服务器端发送通知
从后端向特定标签的设备推送通知:
using Microsoft.Azure.NotificationHubs;
public class PushNotificationServer
{
private NotificationHubClient _hubClient;
private const string FullConnectionString = "Endpoint=sb://...";
private const string HubName = "your-hub-name";
public PushNotificationServer()
{
_hubClient = NotificationHubClient.CreateClientFromConnectionString(
FullConnectionString, HubName);
}
// 发送到特定用户
public async Task SendToUserAsync(string userId, string title, string message)
{
var tagExpression = $"userId:{userId}";
await SendNotificationAsync(tagExpression, title, message);
}
// 复杂的标签表达式:发送给北京的体育爱好者
public async Task SendToBeijingSportsLoversAsync(string title, string message)
{
var tagExpression = "location:beijing && interests:sports";
await SendNotificationAsync(tagExpression, title, message);
}
private async Task SendNotificationAsync(string tagExpression, string title, string message)
{
try
{
// iOS通知载荷
var apnsJson = $"{{\"aps\":{{\"alert\":{{\"title\":\"{title}\",\"body\":\"{message}\"}},\"badge\":1,\"sound\":\"default\"}}}}";
// Android通知载荷
var fcmJson = $"{{\"notification\":{{\"title\":\"{title}\",\"body\":\"{message}\",\"sound\":\"default\"}}}}";
// 同时发送到iOS和Android
await _hubClient.SendAppleNativeNotificationAsync(apnsJson, tagExpression);
await _hubClient.SendFcmNativeNotificationAsync(fcmJson, tagExpression);
System.Diagnostics.Debug.WriteLine("通知已发送");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"发送通知失败: {ex.Message}");
}
}
}
通知权限管理:Android 13+和iOS的注意事项
这几年,移动操作系统对通知权限的管控越来越严了。Android 13(API 33)新增了运行时通知权限,iOS那边一直都有严格的权限请求规范。说白了,你不能一上来就弹权限框——那样用户八成会拒绝。
跨平台权限请求服务
public class NotificationPermissionService
{
public async Task<bool> RequestNotificationPermissionAsync()
{
if (DeviceInfo.Platform == DevicePlatform.iOS)
{
return await RequestiOSPermission();
}
else if (DeviceInfo.Platform == DevicePlatform.Android)
{
return await RequestAndroidPermission();
}
return false;
}
private async Task<bool> RequestAndroidPermission()
{
var status = await Permissions.CheckStatusAsync<Permissions.PostNotifications>();
if (status != PermissionStatus.Granted)
{
status = await Permissions.RequestAsync<Permissions.PostNotifications>();
}
return status == PermissionStatus.Granted;
}
private async Task<bool> RequestiOSPermission()
{
#if IOS
var settings = await UserNotifications.UNUserNotificationCenter.Current
.GetNotificationSettingsAsync();
if (settings.AuthorizationStatus ==
UserNotifications.UNAuthorizationStatus.NotDetermined)
{
var (granted, error) = await UserNotifications.UNUserNotificationCenter.Current
.RequestAuthorizationAsync(
UserNotifications.UNAuthorizationOptions.Alert |
UserNotifications.UNAuthorizationOptions.Badge |
UserNotifications.UNAuthorizationOptions.Sound);
return granted;
}
return settings.AuthorizationStatus ==
UserNotifications.UNAuthorizationStatus.Authorized;
#else
return false;
#endif
}
}
权限请求最佳实践
这里分享几条实战经验:
- 选好时机:别在应用一启动就弹权限框,等用户做了某个需要通知的操作再请求
- 说清楚为什么:请求前先告诉用户开启通知能得到什么好处
- 优雅降级:用户拒绝了?应用照样能用,只是少了通知功能而已
- 提供出口:如果用户后来改主意了,给个入口引导他去系统设置里开启
- 发之前先查:每次发通知前,都检查一下权限状态
富媒体通知:图片、按钮和交互
纯文字的通知看多了容易被忽略。好在现在的移动系统都支持富媒体通知——可以放图片、视频,甚至加交互按钮。我在实际项目中发现,加了图片的通知点击率能提高不少。
Android大图样式通知
public async Task SendBigPictureNotification()
{
var notification = new NotificationRequest
{
NotificationId = 5001,
Title = "新品上架",
Description = "全新iPhone 16系列现已开售,点击查看详情",
Android = new AndroidOptions
{
ChannelId = "promotional_channel",
Priority = AndroidPriority.High,
StyleType = AndroidNotificationStyle.BigPicture,
BigPicture = "https://example.com/images/promo.jpg",
AutoCancel = true
}
};
await LocalNotificationCenter.Current.Show(notification);
}
通知操作按钮
给通知加上操作按钮,用户不用打开App就能直接操作。比如好友请求这种场景,直接在通知上"接受"或"拒绝":
public async Task SendNotificationWithActions()
{
var notification = new NotificationRequest
{
NotificationId = 5004,
Title = "新好友请求",
Description = "李明想要添加您为好友",
CategoryType = NotificationCategoryType.Social,
Android = new AndroidOptions
{
ChannelId = "social_channel",
Priority = AndroidPriority.High,
AutoCancel = true
}
};
notification.Android.Actions = new List<AndroidAction>
{
new AndroidAction { ActionId = 1, Title = "接受", Icon = "accept_icon" },
new AndroidAction { ActionId = 2, Title = "拒绝", Icon = "decline_icon" }
};
await LocalNotificationCenter.Current.Show(notification);
}
// 处理按钮点击事件
public void InitializeNotificationActions()
{
LocalNotificationCenter.Current.OnNotificationActionTapped += (e) =>
{
if (e.ActionId == 1)
{
// 处理"接受"操作
}
else if (e.ActionId == 2)
{
// 处理"拒绝"操作
}
};
}
深度链接:从通知直达应用指定页面
用户点了通知,结果打开的是应用首页——这体验可就差了。深度链接就是解决这个问题的,它能把用户直接带到应用的特定页面。
在通知中使用深度链接
public async Task SendDeepLinkNotification()
{
var notification = new NotificationRequest
{
NotificationId = 6001,
Title = "您的订单已送达",
Description = "请确认收货并评价商品",
ReturningData = JsonSerializer.Serialize(new
{
action = "order_detail",
orderId = "20240209001"
}),
Android = new AndroidOptions
{
ChannelId = "order_channel",
Priority = AndroidPriority.High,
AutoCancel = true
}
};
await LocalNotificationCenter.Current.Show(notification);
}
处理深度链接导航
收到通知后根据数据做路由跳转:
public partial class App : Application
{
public App()
{
InitializeComponent();
SetupNotificationHandlers();
MainPage = new AppShell();
}
private void SetupNotificationHandlers()
{
LocalNotificationCenter.Current.OnNotificationReceived += OnNotificationReceived;
}
private void OnNotificationReceived(NotificationEventArgs e)
{
if (!string.IsNullOrEmpty(e.Request.ReturningData))
{
var data = JsonSerializer.Deserialize<NotificationData>(
e.Request.ReturningData);
MainThread.BeginInvokeOnMainThread(async () =>
{
switch (data.Action)
{
case "order_detail":
await Shell.Current.GoToAsync(
$"//orders/detail?id={data.OrderId}");
break;
case "chat_message":
await Shell.Current.GoToAsync(
$"//chat?userId={data.UserId}");
break;
default:
await Shell.Current.GoToAsync("//home");
break;
}
});
}
}
}
public class NotificationData
{
public string Action { get; set; }
public string OrderId { get; set; }
public string UserId { get; set; }
}
通知设计最佳实践与用户体验优化
技术实现搞定了,但好的通知系统不只是"能发出去"就行——用户体验才是关键。下面这些实践,是我从踩坑中总结出来的。
通知频率控制
没人喜欢被通知轰炸。做个简单的节流机制很有必要:
public class NotificationThrottleService
{
private readonly Dictionary<string, DateTime> _lastNotificationTimes = new();
private readonly TimeSpan _minimumInterval = TimeSpan.FromMinutes(5);
public bool CanSendNotification(string notificationType)
{
if (!_lastNotificationTimes.ContainsKey(notificationType))
return true;
var timeSinceLastNotification = DateTime.Now - _lastNotificationTimes[notificationType];
return timeSinceLastNotification >= _minimumInterval;
}
public void RecordNotificationSent(string notificationType)
{
_lastNotificationTimes[notificationType] = DateTime.Now;
}
}
用户偏好设置
给用户提供细粒度的通知偏好控制——包括免打扰时间段,这个真的很重要:
public class NotificationPreferences
{
public bool EnablePushNotifications { get; set; } = true;
public bool EnablePromotionalNotifications { get; set; } = true;
public bool EnableOrderUpdates { get; set; } = true;
public bool EnableSoundAndVibration { get; set; } = true;
// 免打扰时间段
public TimeSpan QuietHoursStart { get; set; } = new TimeSpan(22, 0, 0);
public TimeSpan QuietHoursEnd { get; set; } = new TimeSpan(8, 0, 0);
public bool IsInQuietHours()
{
var now = DateTime.Now.TimeOfDay;
if (QuietHoursStart < QuietHoursEnd)
return now >= QuietHoursStart && now <= QuietHoursEnd;
else
return now >= QuietHoursStart || now <= QuietHoursEnd;
}
public void SavePreferences()
{
var json = JsonSerializer.Serialize(this);
Preferences.Default.Set("notification_preferences", json);
}
public static NotificationPreferences LoadPreferences()
{
var json = Preferences.Default.Get("notification_preferences", string.Empty);
if (string.IsNullOrEmpty(json))
return new NotificationPreferences();
return JsonSerializer.Deserialize<NotificationPreferences>(json)
?? new NotificationPreferences();
}
}
智能通知合并
当有多条类似的通知时,合并成一条显示,别让用户的通知栏变成灾难现场:
public class NotificationGroupingService
{
public async Task SendGroupedNotifications(List<Message> messages)
{
if (messages.Count == 1)
{
var notification = new NotificationRequest
{
NotificationId = 7000,
Title = messages[0].SenderName,
Description = messages[0].Content,
Group = "messages_group"
};
await LocalNotificationCenter.Current.Show(notification);
}
else
{
var notification = new NotificationRequest
{
NotificationId = 7000,
Title = "新消息",
Description = $"您有{messages.Count}条新消息",
Group = "messages_group",
IsGroupSummary = true,
Android = new AndroidOptions
{
ChannelId = "messages_channel",
StyleType = AndroidNotificationStyle.Inbox,
InboxList = messages.Select(
m => $"{m.SenderName}: {m.Content}").ToList()
}
};
await LocalNotificationCenter.Current.Show(notification);
}
}
}
总结
到这里,我们基本上把.NET MAUI中通知系统的各个方面都过了一遍——从最基础的本地通知,到FCM推送、APNs配置,再到Azure Notification Hubs的企业级方案。内容确实不少,但每一块在实际开发中都会用到。
快速回顾一下核心要点:
- 本地通知用Plugin.LocalNotification就够了,定时提醒、闹钟这些场景轻松搞定
- Android通知渠道在8.0以上是必须配的,不配通知就出不来
- FCM是Android和iOS推送通知的标准方案,大部分项目用它就行
- Azure Notification Hubs更适合企业级大规模推送场景
- 权限管理别急着弹框,选好时机才能提高授权率
- 富媒体和深度链接能明显提升用户体验和点击转化
- 频率控制和用户偏好是留住用户的关键,别把通知做成骚扰
最后说一句——通知是把双刃剑。用好了是提升留存的利器,用差了就是卸载的推手。希望这篇指南能帮到你,祝开发顺利!