Windows 推送通知服务(WNS)使第三方开发人员能够从自己的云服务发送通知、磁贴、徽章和原始更新。 这提供了一种机制,以高效且可靠的方式向用户提供新的更新。

下图显示了用于发送推送通知的完整数据流。 它涉及以下步骤:

  • 您的应用请求 WNS 提供推送通知通道。
  • Windows 要求 WNS 创建通知通道。 此通道以统一资源标识符(URI)的形式返回到调用设备。
  • 通知通道 URI 由 WNS 返回到应用。
  • 应用将 URI 发送到自己的云服务。 然后,将 URI 存储在自己的云服务上,以便在发送通知时访问 URI。 URI 是你自己的应用和你自己的服务之间的接口;你有责任使用安全可靠的 Web 标准实现此接口。
  • 当云服务有要发送的更新时,它会使用通道 URI 通知 WNS。 这是通过通过安全套接字层(SSL)发出 HTTP POST 请求(包括通知有效负载)来完成的。 此步骤需要身份验证。
  • WNS 接收请求并将通知路由到相应的设备。
  • 的数据流关系图

    注册应用并接收云服务的认证信息

    在使用 WNS 发送通知之前,您的应用必须在应用商店仪表板上注册,如 所述,此处

    请求一个消息通知通道

    当能够接收推送通知的应用运行时,它必须首先通过 CreatePushNotificationChannelForApplicationAsync 请求通知通道。 有关完整讨论和示例代码,请参阅 如何请求、创建和保存通知通道 。 此 API 返回的通道 URI 独特地与调用应用程序及其磁贴关联,并可通过该 URI 发送所有类型的通知。

    应用成功创建通道 URI 后,它会将其发送到其云服务,以及应与此 URI 关联的任何特定于应用的元数据。

    Important notes

  • 我们不能保证应用的通知通道 URI 将始终保持不变。 建议应用每次运行时请求一个新通道,并在 URI 更改时更新其服务。 开发人员不应修改通道 URI,应将其视为黑盒字符串。 此时,通道 URI 会在 30 天后过期。 如果你的 Windows 10 应用将在后台定期更新频道,则可以下载适用于 Windows 8.1 的 推送和定期通知示例 ,并重新使用其源代码和/或演示的模式。
  • 云服务和客户端应用之间的接口由开发人员实现。 我们建议应用使用自己的服务完成身份验证过程,并通过安全协议(如 HTTPS)传输数据。
  • 重要的是,云服务始终确保通道 URI 使用域“notify.windows.com”。 服务不应将通知推送到任何其他域上的通道。 如果应用的回调被破解,恶意攻击者可能会提交通道 URI 来伪造 WNS。 如果不检查域,云服务可能会不知情地向此攻击者披露信息。 通道 URI 的子域可能会更改,在验证通道 URI 时不应考虑。
  • 如果云服务尝试将通知传递到过期通道,WNS 将返回 响应代码 410 。 因此,您的服务不应再尝试向该 URI 发送通知。
  • 对云服务进行身份验证

    若要发送通知,云服务必须通过 WNS 进行身份验证。 将应用注册到 Microsoft 应用商店仪表板时,将执行此过程的第一步。 在注册过程中,应用将获得包安全标识符(SID)和密钥。 云服务使用此信息通过 WNS 进行身份验证。

    WNS 身份验证方案是使用 OAuth 2.0 协议中的客户端凭据配置文件实现的。 云服务通过凭借提供的凭据(包 SID 和密钥)在 WNS 上进行身份验证。 作为回报,它会收到一个访问令牌。 此访问令牌允许云服务发送通知。 发送到 WNS 的每个通知请求都需要令牌。

    在高级别上,信息链如下所示:

  • 云服务遵循 OAuth 2.0 协议,将其凭据通过 HTTPS 发送到 WNS。 这会使用 WNS 对服务进行身份验证。
  • 如果身份验证成功,WNS 将返回访问令牌。 此访问令牌在所有后续通知请求中使用,直到它过期。
  • 在 WNS 的身份验证中,云服务通过安全套接字层(SSL)提交 HTTP 请求。 参数以“application/x-www-for-urlencoded”格式提供。 在“client_id”字段中提供程序包 SID,并在“client_secret”字段中提供机密密钥,如以下示例所示。 有关语法详细信息,请参阅 访问令牌请求 参考。

    这只是一个示例,而不是可以在自己的代码中成功使用的剪切和粘贴代码。

     POST /accesstoken.srf HTTP/1.1
     Content-Type: application/x-www-form-urlencoded
     Host: https://login.live.com
     Content-Length: 211
     grant_type=client_credentials&client_id=ms-app%3a%2f%2fS-1-15-2-2972962901-2322836549-3722629029-1345238579-3987825745-2155616079-650196962&client_secret=Vex8L9WOFZuj95euaLrvSH7XyoDhLJc7&scope=notify.windows.com
    

    WNS 对云服务进行身份验证,如果成功,则发送响应“200 正常”。 使用“application/json”媒体类型,在 HTTP 响应正文中包含的参数中返回访问令牌。 服务收到访问令牌后,即可发送通知。

    以下示例显示了成功的身份验证响应,包括访问令牌。 有关语法详细信息,请参阅 推送通知服务请求和响应标头

     HTTP/1.1 200 OK   
     Cache-Control: no-store
     Content-Length: 422
     Content-Type: application/json
         "access_token":"EgAcAQMAAAAALYAAY/c+Huwi3Fv4Ck10UrKNmtxRO6Njk2MgA=", 
         "token_type":"bearer"
    

    Important notes

  • 此过程支持的 OAuth 2.0 协议遵循草稿版本 V16。
  • OAuth 注释请求(RFC)使用术语“client”来引用云服务。
  • 完成 OAuth 草稿后,此过程可能会发生更改。
  • 可以重复使用访问令牌来请求多个通知请求。 这使云服务只需进行身份验证一次即可发送许多通知。 但是,当访问令牌过期时,云服务必须再次进行身份验证才能接收新的访问令牌。
  • 使用通道 URI,每当云服务有用户的更新时,就可以发送通知。

    上述访问令牌可以重复使用多个通知请求;无需云服务器即可为每个通知请求新的访问令牌。 如果访问令牌已过期,通知请求将返回错误。 如果拒绝访问令牌,建议不要多次尝试重新发送通知。 如果遇到此错误,则需要请求新的访问令牌并重新发送通知。 有关确切的错误代码,请参阅 推送通知响应代码

  • 云服务向通道 URI 发出 HTTP POST。 必须通过 SSL 发出此请求,并包含必要的标头和通知有效负载。 授权标头必须包含获取的访问令牌进行授权。

    此处显示了一个示例请求。 有关语法详细信息,请参阅 推送通知响应代码

    有关撰写通知有效负载的详细信息,请参阅 快速入门:发送推送通知。 磁贴、toast 或徽章推送通知的有效载荷以符合各自定义的 XML 内容提供,这些内容遵循 自适应磁贴架构传统磁贴架构。 原始通知的数据负载没有特定的结构。 它严格由应用定义。

     POST https://cloud.notify.windows.com/?token=AQE%bU%2fSjZOCvRjjpILow%3d%3d HTTP/1.1
     Content-Type: text/xml
     X-WNS-Type: wns/tile
     Authorization: Bearer EgAcAQMAAAAALYAAY/c+Huwi3Fv4Ck10UrKNmtxRO6Njk2MgA=
     Host: cloud.notify.windows.com
     Content-Length: 24
    
  • WNS 响应指示已收到通知,并将在下一个可用机会中传递。 但是,WNS 不提供设备或应用程序已接收通知的端到端确认。

    下图演示了数据流:

  • 通知不应包括机密、敏感或个人数据。
  • 若要发送通知,云服务必须先使用 WNS 进行身份验证并接收访问令牌。
  • 访问令牌仅允许云服务向创建令牌的单个应用发送通知。 一个访问令牌不能用于跨多个应用发送通知。 因此,如果云服务支持多个应用,则向每个通道 URI 推送通知时,它必须为应用提供正确的访问令牌。
  • 当设备处于脱机状态时,默认情况下,WNS 将存储每个通道 URI 的每个通知类型(磁贴、锁屏提醒、Toast)之一,并且不会存储原始通知。
  • 在向用户个性化通知内容的情况下,WNS 建议云服务在收到这些更新时立即发送这些更新。 此类场景的示例包括社交媒体动态更新、即时聊天邀请、新消息通知或提醒。 另一种情况是,经常向大量用户发送相同的通用更新;例如天气、股票和新闻更新。 WNS 准则指定这些更新的频率最多应为 30 分钟一次。 最终用户或 WNS 可能会认为更频繁的更新是一个问题。
  • Windows 通知平台与 WNS 保持定期数据连接,使套接字保持活动状态和正常运行。 如果没有应用程序请求或使用通知通道,则不会创建套接字。
  • 磁贴和徽章通知过期

    默认情况下,磁贴和徽章通知会在下载后三天后过期。 通知过期时,内容将从磁贴或队列中删除,并且不再向用户显示。 最好在所有磁贴和锁屏提醒通知上设置过期时间(使用对应用有意义的时间),以便磁贴的内容不会超过相关时间。 显式过期时间对于具有定义生命周期的内容至关重要。 如果云服务停止发送通知,或者用户长时间与网络断开连接,这也可确保删除过时的内容。

    云服务可以通过设置 X-WNS-TTL HTTP 标头来设置每个通知的过期时间(以秒为单位),以便在发送通知后保持有效。 有关详细信息,请参阅 推送通知服务请求和响应标头

    例如,在股市的活跃交易日中,您可以将股票价格更新的到期时间设置为发送间隔的两倍(例如,如果您每半小时发送一次通知,则可以将到期时间设置为接收通知后一小时)。 作为另一个示例,新闻应用可能会确定一天是每日新闻磁贴更新的适当过期时间。

    推送通知和节电模式

    省电模式通过限制设备上的后台活动来延长电池寿命。 Windows 10 允许用户在电池低于指定阈值时自动启用节电器。 当节电模式处于打开状态时,将禁用推送通知的接收以节省能源。 但对此有几个例外。 以下 Windows 10 节电模式设置(在 “设置” 应用中找到)允许你的应用接收推送通知,即使节电模式处于打开状态。

    允许在省电模式下从任何应用推送通知:此设置允许各应用在省电模式开启时接收推送通知。 请注意,此设置仅适用于 Windows 10 桌面版(家庭版、专业版、企业版和教育版)。
  • 始终允许:此设置允许特定应用在后台运行,而节电模式处于打开状态-包括接收推送通知。 此列表由用户手动维护。

    无法检查这两个设置的状态,但你可以检查节电模式的状态。 在 Windows 10 中,使用 EnergySaverStatus 属性检查节电状态。 你的应用还可以使用 EnergySaverStatusChanged 事件来侦听节电模式的更改。

    如果你的应用严重依赖于推送通知,我们建议通知用户,他们可能不会在节电时收到通知,并使其易于调整 节电设置。 在 Windows 10 ms-settings:batterysaver-settings中使用节电模式设置 URI 方案,可以提供指向“设置”应用的便捷链接。

    在通知用户有关节电模式设置时,我们建议提供一种方法来禁止将来显示消息。 例如, dontAskMeAgainBox 以下示例中的复选框在 LocalSettings 中保留用户的首选项。

    下面是如何检查 Windows 10 中是否开启电池节能模式的示例。 本示例通知用户并启动“设置”应用以打开 电池节能设置。 允许用户 dontAskAgainSetting 取消消息(如果不想再次收到通知)。

    using System;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Navigation;
    using Windows.System;
    using Windows.System.Power;
    async public void CheckForEnergySaving()
       //Get reminder preference from LocalSettings
       bool dontAskAgain;
       var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
       object dontAskSetting = localSettings.Values["dontAskAgainSetting"];
       if (dontAskSetting == null)
       {  // Setting does not exist
          dontAskAgain = false;
       {  // Retrieve setting value
          dontAskAgain = Convert.ToBoolean(dontAskSetting);
       // Check if battery saver is on and that it's okay to raise dialog
       if ((PowerManager.EnergySaverStatus == EnergySaverStatus.On)
             && (dontAskAgain == false))
          // Check dialog results
          ContentDialogResult dialogResult = await saveEnergyDialog.ShowAsync();
          if (dialogResult == ContentDialogResult.Primary)
             // Launch battery saver settings (settings are available only when a battery is present)
             await Launcher.LaunchUriAsync(new Uri("ms-settings:batterysaver-settings"));
          // Save reminder preference
          if (dontAskAgainBox.IsChecked == true)
          {  // Don't raise dialog again
             localSettings.Values["dontAskAgainSetting"] = "true";
    
    #include <winrt/Windows.Foundation.h>
    #include <winrt/Windows.Storage.h>
    #include <winrt/Windows.System.h>
    #include <winrt/Windows.System.Power.h>
    #include <winrt/Windows.UI.Xaml.h>
    #include <winrt/Windows.UI.Xaml.Controls.h>
    #include <winrt/Windows.UI.Xaml.Navigation.h>
    using namespace winrt;
    using namespace winrt::Windows::Foundation;
    using namespace winrt::Windows::Storage;
    using namespace winrt::Windows::System;
    using namespace winrt::Windows::System::Power;
    using namespace winrt::Windows::UI::Xaml;
    using namespace winrt::Windows::UI::Xaml::Controls;
    using namespace winrt::Windows::UI::Xaml::Navigation;
    winrt::fire_and_forget CheckForEnergySaving()
        // Get reminder preference from LocalSettings.
        bool dontAskAgain{ false };
        auto localSettings = ApplicationData::Current().LocalSettings();
        IInspectable dontAskSetting = localSettings.Values().Lookup(L"dontAskAgainSetting");
        if (!dontAskSetting)
            // Setting doesn't exist.
            dontAskAgain = false;
            // Retrieve setting value
            dontAskAgain = winrt::unbox_value<bool>(dontAskSetting);
        // Check whether battery saver is on, and whether it's okay to raise dialog.
        if ((PowerManager::EnergySaverStatus() == EnergySaverStatus::On) && (!dontAskAgain))
            // Check dialog results.
            ContentDialogResult dialogResult = co_await saveEnergyDialog().ShowAsync();
            if (dialogResult == ContentDialogResult::Primary)
                // Launch battery saver settings
                // (settings are available only when a battery is present).
                co_await Launcher::LaunchUriAsync(Uri(L"ms-settings:batterysaver-settings"));
            // Save reminder preference.
            if (dontAskAgainBox().IsChecked())
                // Don't raise the dialog again.
                localSettings.Values().Insert(L"dontAskAgainSetting", winrt::box_value(true));
    

    这是此示例中所示的 ContentDialog 的 XAML。

    <ContentDialog x:Name="saveEnergyDialog"
                   PrimaryButtonText="Open battery saver settings"
                   SecondaryButtonText="Ignore"
                   Title="Battery saver is on."> 
       <StackPanel>
          <TextBlock TextWrapping="WrapWholeWords">
             <LineBreak/><Run>Battery saver is on and you may 
              not receive push notifications.</Run><LineBreak/>
             <LineBreak/><Run>You can choose to allow this app to work normally
             while in battery saver, including receiving push notifications.</Run>
             <LineBreak/>
          </TextBlock>
          <CheckBox x:Name="dontAskAgainBox" Content="OK, got it."/>
       </StackPanel>
    </ContentDialog>
    
                  发送本地磁贴通知
                  快速入门:发送推送通知
                  如何通过推送通知更新徽章
                  如何请求、创建和保存通知通道
                  如何为正在运行的应用程序截获通知
                  如何使用 Windows 推送通知服务 (WNS) 进行验证
                  推送通知服务请求和响应标头
                  推送通知的指南和清单
                  Raw notifications