Windows 推播通知服務 (WNS) 概觀

Windows 推播通知服務 (WNS) 使第三方開發人員能夠從自己的雲端服務傳送快顯通知、圖標、徽章,和原始更新。 這提供一種機制,用省電又可靠的方法,將最新的更新資訊傳送給使用者。

運作方式

下圖顯示傳送推播通知的完整資料流。 它牽涉到下列步驟:

  1. 您的應用程式會向 WNS 要求推播通知通道。
  2. Windows 會要求 WNS 建立通知通道。 此通道會以統一資源識別碼 (URI) 的形式傳回呼叫裝置。
  3. WNS 會將通知通道 URI 傳回至您的應用程式。
  4. 您的應用程式會將 URI 傳送至您自己的雲端服務。 接著,您會將 URI 儲存在自己的雲端服務上,以便在傳送通知時存取 URI。 URI 是您自己的應用程式與自己的服務之間的介面;您必須負責使用安全有保護的 Web 標準來實作此介面。
  5. 當雲端服務有要傳送的更新時,它會使用通道 URI 通知 WNS。 這是透過安全通訊端層 (SSL) 發出 HTTP POST 要求 (包括通知承載) 來完成的。 此步驟需要進行驗證。
  6. WNS 會收到要求,並將通知路由傳送至適當的裝置。

wns data flow diagram for push notification

註冊您的應用程式並接收雲端服務的認證

您必須如這裡所述,先向市集儀表板註冊應用程式,才能使用 WNS 傳送通知,。

要求通知通道

執行能夠接收推播通知的應用程式時,它必須先透過 CreatePushNotificationChannelForApplicationAsync 要求通知通道。 如需完整的討論和範例程式碼,請參閱如何要求、建立和儲存通知通道。 此 API 會傳回通道 URI,此 URI 會唯一連結至呼叫的應用程式及其圖標,並且可以透過該通道傳送所有通知類型。

應用程式成功建立通道 URI 之後,會將它傳送至其雲端服務,以及任何應該與此 URI 相關聯的應用程式特定中繼資料。

重要注意

  • 我們不保證應用程式的通知通道 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 的通知要求都需要權杖。

概括而言,資訊鏈結如下:

  1. 雲端服務會遵循 OAuth 2.0 通訊協定,透過 HTTPS 將其認證傳送至 WNS。 這會向 WNS 驗證服務。
  2. 如果驗證成功,WNS 會傳回存取權杖。 此存取權杖會用於所有後續通知要求,直到到期為止。

wns diagram for cloud service authentication

在 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 OK” 的回應。 存取權杖會使用「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"
 }

重要注意

  • 此程序支援的 OAuth 2.0 通訊協定遵循 V16 版草稿。
  • OAuth 要求建議 (RFC) 會使用「用戶端」一詞來參考雲端服務。
  • 當 OAuth 草稿完成時,此程序可能會變更。
  • 存取權杖可以重複用於多個通知要求。 這使得雲端服務只需進行一次驗證即可傳送許多通知。 不過,當存取權杖到期時,雲端服務必須再次進行驗證,才能接收新的存取權杖。

傳送通知

使用通道 URI 時,雲端服務可以在使用者有更新時傳送通知。

上述存取權杖可以重複用於多個通知要求;雲端伺服器不需要為每個通知要求新的存取權杖。 如果存取權杖已過期,通知要求會傳回錯誤。 建議您不要嘗試在拒絕存取權杖時多次重新傳送通知。 如果您遇到此錯誤,您將需要要求新的存取權杖重新傳送通知。 如需確切的錯誤碼,請參閱推播通知回應碼

  1. 雲端服務會將 HTTP POST 設為通道 URI。 此要求必須透過 SSL 提出,並包含必要的標頭和通知承載。 授權標頭必須包含取得的用於授權的存取權杖。

    以下顯示一個要求範例。 如需語法詳細資料,請參閱推播通知回應碼

    如需撰寫通知承載的詳細資料,請參閱快速入門:傳送推播通知。 圖標、快顯通知或徽章推播通知的承載,會以符合其各自定義的調適型圖標構舊版圖標架構的 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
    
     <body>
     ....
    
  2. WNS 會回應指出已收到通知,且將在下一個可用機會中傳遞。 不過,WNS 不會提供裝置或應用程式已收到通知的端對端確認。

此圖說明了資料流程:

wns diagram for sending a notification

重要注意

  • WNS 不保證通知的可靠性或延遲。
  • 通知不得包含機密、敏感性或個人資料。
  • 若要傳送通知,雲端服務必須先向 WNS 進行驗證,並接收存取權杖。
  • 存取權杖只允許雲端服務將通知傳送至建立權杖的單一應用程式。 一個存取權杖不能用來跨多個應用程式傳送通知。 因此,如果您的雲端服務支援多個應用程式,則必須在將通知推播至每個通道 URI 時,為應用程式提供正確的存取權杖。
  • 當裝置離線時,根據預設,WNS 將為每個通道 URI 儲存每種通知類型 (圖標、徽章、快顯通知) 的其中一個,而且不會儲存原始通知。
  • 在通知內容針對使用者進行個人化設定的情況下,WNS 建議雲端服務在收到更新後立即傳送這些更新。 此案例的範例包括社交媒體摘要更新、立即通訊邀請、新訊息通知或警示。 或者,您可以有一些案例,即同一個通用更新經常傳送給您的大部分使用者的一個大子集;例如,天氣、股票和新聞更新。 WNS 指導方針指定這些更新的頻率應該最多每 30 分鐘一次。 終端使用者或 WNS 可能認為更頻繁的例行更新是濫用行為。
  • Windows 通知平台會維護與 WNS 的定期資料連接,讓通訊端保持良好運作。 如果沒有應用程式要求或使用通知通道,則不會建立通訊端。

圖標和徽章通知的到期日

根據預設,圖標和徽章通知會在下載三天後到期。 當通知到期時,內容會從圖標或佇列中移除,且不再顯示給使用者。 最佳做法是對所有圖標和徽章通知設定到期時間 (使用適合您應用程式的時間),以確保圖標的內容在不再相關時不會持續存在。 明確到期時間對於已定義生命周期的內容而言很重要。 這也確保了如果您的雲端服務停止傳送通知,或使用者長時間中斷網路連接,則可以移除過時的內容。

您的雲端服務可以透過設定 X-WNS-TTL HTTP 標頭來指定通知發送後保持有效的時間 (以秒為單位),從而為每個通知設定過期時間。 有關詳細資訊,請參閱推播通知服務要求和回應標頭

例如,在股市活躍交易日期間,您可以將股票價格更新的到期日設定為發送間隔的兩倍 (例如,如果您每半小時發送一次通知,則為收到後一小時)。 另一個範例是,新聞應用程式可能會確定一天是每日新聞圖標更新的適當到期時間。

推播通知和省電模式

省電模式可藉由限制裝置上的背景活動,來延長電池使用時間。 Windows 10 可讓使用者設定省電模式,在電池低於指定閾值時自動開啟。 當省電模式開啟時,會停用推播通知的接收以節省能源。 但也有少數例外。 下列 Windows 10 省電模式設定 (請參閱設定應用程式),可讓您的應用程式即使在省電模式開啟時也會收到推播通知。

  • 在省電模式中允許來自任何應用程式的推播通知:此設定可讓所有應用程式在省電模式開啟時接收推播通知。 請注意,此設定僅適用於 Windows 10 桌面版 (家用版、專業版、企業版和教育版)。
  • 一律允許:此設定可讓特定應用程式在省電模式開啟時在背景執行,包括接收推播通知。 此清單是由使用者手動維護。

無法檢查這兩個設定的狀態,但您可以檢查省電模式的狀態。 在 Windows 10 中,使用 EnergySaverStatus 屬性來檢查省電模式狀態。 您的應用程式也可以使用 EnergySaverStatusChanged 事件,來接聽省電模式的變更。

如果您的應用程式嚴重依賴推播通知,建議您通知使用者,他們可能不會在省電模式開啟時收到通知,並方便他們調整省電模式設定。 使用 Windows 10 中的省電模式設定 URI 配置,ms-settings:batterysaver-settings您可以提供指向 [設定] 應用程式的便利連結。

提示

通知使用者有關省電模式設定時,建議您提供在未來隱藏該訊息的方法。 例如,下列範例中的 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;
   }
   else
   {  // 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;
    }
    else
    {
        // 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>