通知侦听器:访问所有通知Notification listener: Access all notifications

通知侦听器提供对用户通知的访问。The notification listener provides access to a user's notifications. Smartwatch 和其他可穿戴设备可以使用通知侦听器将手机通知发送到可穿戴设备。Smartwatches and other wearables can use the notification listener to send the phone's notifications to the wearable device. 在收到通知时,Home 自动化应用程序可以使用通知侦听器来执行特定操作,例如,当你收到呼叫时使灯闪烁。Home automation apps can use notification listener to perform specific actions when notifications are received, such as making the lights blink when you receive a call.

重要

需要周年更新 :目标必须为 SDK 14393,并且必须运行版本 14393 或更高版本才能使用通知侦听器。Requires Anniversary Update : You must target SDK 14393 and be running build 14393 or higher to use Notification Listener.

重要 APIUserNotificationListener 类UserNotificationChangedTrigger 类Important APIs : UserNotificationListener class, UserNotificationChangedTrigger class

通过添加用户通知功能支持侦听器Enable the listener by adding the User Notification capability

要使用通知侦听器,必须在应用清单中添加用户通知侦听器功能。To use the notification listener, you must add the User Notification Listener capability to your app manifest.

  1. 在 Visual Studio 中的解决方案资源管理器中,双击 Package.appxmanifest 文件,以打开清单设计器。In Visual Studio, in the Solution Explorer, double click your Package.appxmanifest file to open the manifest designer.
  2. 打开“功能”选项卡。Open the Capabilities tab.
  3. 检查 用户通知侦听器 功能。Check the User Notification Listener capability.

检查侦听器是否受支持Check whether the listener is supported

如果应用支持较旧版本的 Windows 10,则需要使用 ApiInformation class 检查侦听器是否受支持。If your app supports older versions of Windows 10, you need to use the ApiInformation class to check whether the listener is supported. 如果侦听器不受支持,请避免对侦听器 API 执行任何调用。If the listener isn't supported, avoid executing any calls to the listener APIs.

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

请求访问侦听器Requesting access to the listener

由于侦听器允许访问用户通知,用户必须为引用提供访问其通知的权限。Since the listener allows access to the user's notifications, users must give your app permission to access their notifications. 在应用首次运行期间,应该请求使用通知侦听器的访问权限。During your app's first-run experience, you should request access to use the notification listener. 如果需要,可以在调用 RequestAccessAsync 之前显示一个比较初步的 UI,来说明为何应用需要能够访问用户通知,以便用户了解为什么他们应该允许应用访问。If you want, you can show some preliminary UI that explains why your app needs access to the user's notifications before you call RequestAccessAsync, so that the user understands why they should allow access.

// 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“设置”撤销访问权限。The user can revoke access at any time via Windows Settings. 因此,应用程序应始终在执行使用通知侦听器的代码之前通过 GetAccessStatus 方法检查访问状态。Therefore, your app should always check the access status via the GetAccessStatus method before executing code that uses the notification listener. 如果用户撤销访问权限,API 将在不发出提示的情况下失败,而不引发异常(例如,用于获取所有通知的 API 将只返回空列表)。If the user revokes access, the APIs will silently fail rather than throwing an exception (for example, the API to get all notifications will simply return an empty list).

访问用户通知Access the user's notifications

使用通知侦听器,可以获得用户当前通知的列表。With the notification listener, you can get a list of the user's current notifications. 只需调用 GetNotificationsAsync 方法,并指定你想获取的通知类型(目前受支持的通知类型只有 toast 通知)。Simply call the GetNotificationsAsync method, and specify the type of notifications you want to get (currently, the only type of notifications supported are toast notifications).

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

显示通知Displaying the notifications

每个通知都以 UserNotification 形式表示,它提供有关发出通知的应用的信息、创建通知的时间、通知 ID 以及通知本身。Each notification is represented as a UserNotification, which provides information about the app that the notification is from, the time the notification was created, the notification's ID, and the notification itself.

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

AppInfo 属性提供显示通知所需要的信息。The AppInfo property provides the info you need to display the notification.

备注

我们建议将所有处理单个通知的代码放入 try/catch,以免在捕获单个通知时发生意外异常。We recommend surrounding all your code for processing a single notification in a try/catch, in case an unexpected exception occurs when you are capturing a single notification. 切不可只因为一个特定通知出现问题就不能显示其他通知。You shouldn't completely fail to display other notifications just because of an issue with one specific notification.

// 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 属性中。The content of the notification itself, such as the notification text, is contained in the Notification property. 此属性包含通知的可视部分。This property contains the visual portion of the notification. (如果对在 Windows 上发送通知很熟悉,可以注意到 Notification 对象中的 VisualVisual.Bindings 属性对应于开发人员在弹出通知时发送的内容。)(If you are familiar with sending notifications on Windows, you will notice that the Visual and Visual.Bindings properties in the Notification object correspond to what developers send when popping a notification.)

我们需要寻找 toast 绑定(对于错误证明代码,应检查绑定是否为 null)。We want to look for the toast binding (for error-proof code, you should check that the binding isn't null). 你可以从绑定中获取文本元素。From the binding, you can obtain the text elements. 可以选择显示任意多的文本元素。You can choose to display as many text elements as you would like. (理想情况下应全部显示。)可以选择对文本元素区别处理;例如,将第一个文本视为标题文本,将后续元素视为正文文本。(Ideally, you should display them all.) You can choose to treat the text elements differently; for example, treat the first one as title text, and subsequent elements as body text.

// 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));
}

删除特定的通知Remove a specific notification

如果可穿戴设备或服务允许用户消除通知,则可以删除实际的通知,这样用户以后就不会在其手机或电脑上看到它。If your wearable or service allows the user to dismiss notifications, you can remove the actual notification so the user doesn't see it later on their phone or PC. 只需提供需要删除的通知的通知 ID(从 UserNotification 对象获得):Simply provide the notification ID (obtained from the UserNotification object) of the notification you'd like to remove:

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

清除所有通知Clear all notifications

UserNotificationListener.ClearNotifications方法可清除所有的用户通知。The UserNotificationListener.ClearNotifications method clears all the user's notifications. 请谨慎使用此方法。Use this method with caution. 仅当可穿戴设备或服务显示所有通知时才应清除所有通知。You should only clear all notifications if your wearable or service displays ALL notifications. 如果可穿戴设备或服务仅显示某些通知,则当用户单击“清除通知”按钮时,该用户只希望删除这些特定通知;但是,调用 ClearNotifications 方法实际上会导致所有通知(包括可穿戴设备或服务未显示的通知)被删除。If your wearable or service only displays certain notifications, when the user clicks your "Clear notifications" button, the user is only expecting those specific notifications to be removed; however, calling the ClearNotifications method would actually cause all the notifications, including ones that your wearable or service wasn't displaying, to be removed.

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

已添加/已消除的通知的后台任务Background task for notification added/dismissed

支持应用侦听通知的一种常用方法是设置后台任务,这样,无论应用当前是否在运行,你都知道何时添加或消除了通知。A common way to enable an app to listen to notifications is to set up a background task, so that you can know when a notification was added or dismissed regardless of whether your app is currently running.

由于在周年更新中增加了单进程模型,添加后台任务变得相当简单。Thanks to the single process model added in the Anniversary Update, adding background tasks is fairly simple. 在主应用代码中,在获得对通知侦听器的用户访问权限和运行后台任务的权限后,只需注册一个新后台任务,并使用 Toast 通知类型设置 UserNotificationChangedTriggerIn your main app's code, after you have obtained the user's access to Notification Listener and obtained access to run background tasks, simply register a new background task, and set the UserNotificationChangedTrigger using the Toast notification kind.

// 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 方法(如果尚未覆盖),然后在任务名称中使用切换语句来确定调用了众多后台任务触发器中的哪一个。Then, in your App.xaml.cs, override the OnBackgroundActivated method if you haven't yet, and use a switch statement on the task name to determine which of your many background task triggers was invoked.

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();
}

后台任务只是“即时点击”:它不会提供有关添加或删除了哪个具体通知的任何信息。The background task is simply a "shoulder tap": it doesn't provide any information about which specific notification was added or removed. 触发后台任务时,应在同步可穿戴设备上的通知,以反映平台中的通知。When your background task is triggered, you should sync the notifications on your wearable so that they reflect the notifications in the platform. 这可以确保在后台任务失败时,在后台任务下一次执行时仍然可以恢复可穿戴设备上的通知。This ensures that if your background task fails, notifications on your wearable can still be recovered the next time your background task executes.

SyncNotifications 是您实现的方法;下一节将演示如何进行操作。SyncNotifications is a method you implement; the next section shows how.

确定添加和删除了哪些通知Determining which notifications were added and removed

SyncNotifications 方法中,要确定添加或删除了哪些通知(与可穿戴设备同步),必须计算当前通知集合与平台通知之间的差异。In your SyncNotifications method, to determine which notifications have been added or removed (syncing notifications with your wearable), you have to calculate the delta between your current notification collection, and the notifications in the platform.

// 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);
}

已添加/已消除的通知的前台事件Foreground event for notification added/dismissed

重要

已知问题:在生成 17763/10 月2018更新/版本1809之前的内部版本中,前台事件将导致 CPU 循环和/或工作不起作用。Known issue: In builds before Build 17763 / October 2018 Update / Version 1809, The foreground event will cause a CPU loop and/or didn't work. 如果需要更早版本的支持,请改用后台任务。If you need support on those earlier builds, use the background task instead.

还可以从内存中事件处理程序侦听通知 .。。You can also listen to notifications from an in-memory event handler...

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

如何修复后台任务的延迟问题How to fix delays in the background task

测试应用时,你可能会注意到后台任务有时会延迟,并且不会触发几分钟时间。When testing your app, you might notice that the background task is sometimes delayed and doesn't trigger for several minutes. 若要解决此延迟,请提示用户中转到 "系统设置-> 系统"-> > 电池-按应用程序使用电池 ",在列表中找到你的应用程序,选择它,然后将其设置为" 在后台始终允许 "。To fix the delay, prompt the user to go to the system settings -> System -> Battery -> Battery usage by app, find your app in the list, select it, and set it to be "Always allowed in background." 此后,后台任务应该始终在收到通知后一秒内触发。After this, the background task should always be triggered within around a second of the notification being received.