通知侦听器:访问所有通知

通知侦听器提供对用户通知的访问权限。 智能手表和其他可穿戴设备可以使用通知侦听器将手机的通知发送到可穿戴设备。 家庭自动化应用可以使用通知侦听器在收到通知时执行特定的操作,如在接到来电时闪灯。

重要

需要周年更新:必须以 SDK 14393 为目标,并运行内部版本 14393 或更高版本才能使用通知侦听器。

重要 APIUserNotificationListener 类UserNotificationChangedTrigger 类

通过添加用户通知功能启用侦听器

若要使用通知侦听器,必须将用户通知侦听器功能添加到应用清单。

  1. 在 Visual Studio 的解决方案资源管理器中,双击Package.appxmanifest文件以打开清单设计器。
  2. 打开“功能”选项卡。
  3. 检查用户通知侦听器功能。

检查侦听器是否受支持

如果你的应用支持较旧版本的 Windows 10,则需要使用 ApiInformation 类 来检查侦听器是否受支持。 如果不支持侦听器,请避免执行对侦听器 API 的任何调用。

if (ApiInformation.IsTypePresent("Windows.UI.Notifications.Management.UserNotificationListener"))
{
    // Listener supported!
}
 
else
{
    // Older version of Windows, no Listener
}

请求访问侦听器

由于侦听器允许访问用户的通知,因此用户必须授予应用访问其通知的权限。 在应用的首次运行体验期间,应请求访问以使用通知侦听器。 如果需要,可以显示一些初步 UI,说明应用在调用 RequestAccessAsync 之前为何需要访问用户的通知,以便用户了解他们应允许访问的原因。

// Get the listener
UserNotificationListener listener = UserNotificationListener.Current;
 
// And request access to the user's notifications (must be called from UI thread)
UserNotificationListenerAccessStatus accessStatus = await listener.RequestAccessAsync();
 
switch (accessStatus)
{
    // This means the user has granted access.
    case UserNotificationListenerAccessStatus.Allowed:
 
        // Yay! Proceed as normal
        break;
 
    // This means the user has denied access.
    // Any further calls to RequestAccessAsync will instantly
    // return Denied. The user must go to the Windows settings
    // and manually allow access.
    case UserNotificationListenerAccessStatus.Denied:
 
        // Show UI explaining that listener features will not
        // work until user allows access.
        break;
 
    // This means the user closed the prompt without
    // selecting either allow or deny. Further calls to
    // RequestAccessAsync will show the dialog again.
    case UserNotificationListenerAccessStatus.Unspecified:
 
        // Show UI that allows the user to bring up the prompt again
        break;
}

用户可以随时通过 Windows 设置撤销访问权限。 因此,应用始终应在执行使用通知侦听器的代码之前通过 GetAccessStatus 方法检查访问权限状态。 如果用户撤销访问权限,API 将无提示失败,而不是引发异常(例如,获取所有通知的 API 将仅返回空列表)。

访问用户的通知

使用通知侦听器,可以获取用户的当前通知列表。 只需调用 GetNotificationsAsync 方法,并指定要获取的通知类型(目前,唯一支持的通知类型是 Toast 通知)。

// Get the toast notifications
IReadOnlyList<UserNotification> notifs = await listener.GetNotificationsAsync(NotificationKinds.Toast);

显示通知

每个通知都表示为 UserNotification,它提供有关通知来自的应用、创建通知的时间、通知 ID 和通知本身的信息。

public sealed class UserNotification
{
    public AppInfo AppInfo { get; }
    public DateTimeOffset CreationTime { get; }
    public uint Id { get; }
    public Notification Notification { get; }
}

AppInfo 属性提供显示通知所需的信息。

注意

我们建议围绕所有代码来处理 try/catch 中的单个通知,以防捕获单个通知时发生意外异常。 由于存在一个特定通知的问题,不应完全无法显示其他通知。

// Select the first notification
UserNotification notif = notifs[0];
 
// Get the app's display name
string appDisplayName = notif.AppInfo.DisplayInfo.DisplayName;
 
// Get the app's logo
BitmapImage appLogo = new BitmapImage();
RandomAccessStreamReference appLogoStream = notif.AppInfo.DisplayInfo.GetLogo(new Size(16, 16));
await appLogo.SetSourceAsync(await appLogoStream.OpenReadAsync());

通知本身(如通知文本)的内容包含在 Notification 属性中。 此属性包含通知的可视部分。 (如果你熟悉在 Windows 上发送通知,你会注意到通知对象中的视觉对象和 Visual.Bindings 属性对应于弹出通知时开发人员发送的内容。

我们希望查找 Toast 绑定(对于防错误代码,应检查绑定是否不为 null)。 从绑定中,可以获取文本元素。 可以选择显示任意数量的文本元素。 (理想情况下,应全部显示它们。可以选择以不同的方式处理文本元素;例如,将第一个元素视为标题文本,并将后续元素视为正文文本。

// Get the toast binding, if present
NotificationBinding toastBinding = notif.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);
 
if (toastBinding != null)
{
    // And then get the text elements from the toast binding
    IReadOnlyList<AdaptiveNotificationText> textElements = toastBinding.GetTextElements();
 
    // Treat the first text element as the title text
    string titleText = textElements.FirstOrDefault()?.Text;
 
    // We'll treat all subsequent text elements as body text,
    // joining them together via newlines.
    string bodyText = string.Join("\n", textElements.Skip(1).Select(t => t.Text));
}

删除特定通知

如果可穿戴设备或服务允许用户消除通知,则可以删除实际通知,以便用户稍后在手机或电脑上看不到通知。 只需提供要删除的通知的 通知 ID(从 UserNotification 对象获取):

// Remove the notification
listener.RemoveNotification(notifId);

清除所有通知

UserNotificationListener.ClearNotifications 方法清除所有用户的通知。 请谨慎使用此方法。 仅当可穿戴设备或服务显示 ALL 通知时,才应清除所有通知。 如果可穿戴设备或服务仅显示某些通知,当用户单击“清除通知”按钮时,用户只希望删除这些特定通知;但是,调用 ClearNotifications 方法实际上会导致所有通知(包括可穿戴设备或服务未显示通知)被删除。

// Clear all notifications. Use with caution.
listener.ClearNotifications();

已添加/消除通知的后台任务

使应用能够侦听通知的一种常见方法是设置后台任务,以便无论应用当前是否正在运行,都可以知道何时添加或关闭通知。

由于周年更新中添加的单个进程模型,添加后台任务非常简单。 在主应用代码中,通过分别调用 UserNotificationListener.Current.RequestAccessAsyncBackgroundExecutionManager.RequestAccessAsync 获得对通知侦听器的用户访问权限和运行后台任务的权限后,只需注册一个新后台任务,并使用 Toast 通知类型设置 UserNotificationChangedTrigger

// TODO: Request/check Listener access via UserNotificationListener.Current.RequestAccessAsync
 
// TODO: Request/check background task access via BackgroundExecutionManager.RequestAccessAsync
 
// If background task isn't registered yet
if (!BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals("UserNotificationChanged")))
{
    // Specify the background task
    var builder = new BackgroundTaskBuilder()
    {
        Name = "UserNotificationChanged"
    };
 
    // Set the trigger for Listener, listening to Toast Notifications
    builder.SetTrigger(new UserNotificationChangedTrigger(NotificationKinds.Toast));
 
    // Register the task
    builder.Register();
}

然后,在App.xaml.cs中, 重写 OnBackgroundActivated 方法(如果尚未这样做),并使用任务名称上的 switch 语句来确定调用了哪些后台任务触发器。

protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    var deferral = args.TaskInstance.GetDeferral();
 
    switch (args.TaskInstance.Task.Name)
    {
        case "UserNotificationChanged":
            // Call your own method to process the new/removed notifications
            // The next section of documentation discusses this code
            await MyWearableHelpers.SyncNotifications();
            break;
    }
 
    deferral.Complete();
}

后台任务只是一个“肩部点击”:它不提供有关添加或删除哪些特定通知的任何信息。 触发后台任务时,应同步可穿戴设备上的通知,以便它们反映平台中的通知。 这可确保如果后台任务失败,在下次执行后台任务时仍可以恢复可穿戴设备上的通知。

SyncNotifications 是一种你实现的方法;下一部分介绍如何实现。

确定添加和删除了哪些通知

SyncNotifications在方法中,若要确定已添加或删除哪些通知(将通知与可穿戴设备同步),必须计算当前通知集合与平台中的通知之间的增量。

// Get all the current notifications from the platform
IReadOnlyList<UserNotification> userNotifications = await listener.GetNotificationsAsync(NotificationKinds.Toast);
 
// Obtain the notifications that our wearable currently has displayed
IList<uint> wearableNotificationIds = GetNotificationsOnWearable();
 
// Copy the currently displayed into a list of notification ID's to be removed
var toBeRemoved = new List<uint>(wearableNotificationIds);
 
// For each notification in the platform
foreach (UserNotification userNotification in userNotifications)
{
    // If we've already displayed this notification
    if (wearableNotificationIds.Contains(userNotification.Id))
    {
        // We want to KEEP it displayed, so take it out of the list
        // of notifications to remove.
        toBeRemoved.Remove(userNotification.Id);
    }
 
    // Otherwise it's a new notification
    else
    {
        // Display it on the Wearable
        SendNotificationToWearable(userNotification);
    }
}
 
// Now our toBeRemoved list only contains notification ID's that no longer exist in the platform.
// So we will remove all those notifications from the wearable.
foreach (uint id in toBeRemoved)
{
    RemoveNotificationFromWearable(id);
}

已添加/消除通知的前景事件

重要

已知问题:在内部版本 17763/2018 年 10 月更新/版本 1809 之前的内部版本中,前台事件会导致 CPU 循环和/或不工作。 如果需要那些更早的内部版本的支持,请改用后台任务。

还可以从内存中事件处理程序侦听通知...

// Subscribe to foreground event
listener.NotificationChanged += Listener_NotificationChanged;
 
private void Listener_NotificationChanged(UserNotificationListener sender, UserNotificationChangedEventArgs args)
{
    // Your code for handling the notification
}

如何修复后台任务中的延迟

测试应用时,后台任务有时可能会延迟,数分钟不能触发。 若要解决延迟问题,请提示用户转到“系统设置”->“系统”->“电池”->“应用的电池使用情况”,在列表中找到并选择该应用,将其设置为“始终允许在后台运行”。此后,后台任务应该始终在收到通知后大约一秒内触发。