当 Windows Azure 碰到了 Windows Phone 7:推送通知服务概述

junwong 发布于 2012/02/27 15:12
阅读 790
收藏 0

本文将介绍 Windows Phone 7 上的一个新功能-Push Notification Service,一个由应用程序供货商与手机用户进行沟通的管道,服务应用程序可以利用这个功能将讯息广播给所有订阅此服务的手机使用者,如同简讯一般。

Push Notification Services 简介

Windows Phone 7(WP7)已于 11 月中旬正式上市,台湾目前可以买到 HTC HD7 以及 HTC Mozart 两款 WP7 手机,除了可以让台湾的使用者能够抢先体验 WP7 之外,台湾的 Windows Phone 开发人员也开始不断的将 WP7 的应用程序提交到 App Hub(WP7 的应用程序中心)让全球的 WP7 使用者可以下载使用。WP7 挟带者全新的手机体验进入手机的市场,不论是使用者的使用经验,或是开发人员的开发经验,都是一种全新的境界。对于开发人员来说,WP7 上的应用程序开发模型和 Windows Mobile 完全不同,就像是在桌上型应用程序由 Windows Forms 移转到 WPF(Windows Presentation Foundation)一样,WP7 的开发也由 Windows Mobile 上的 Smart Device Application(Windows Forms-based)转换为 Silverlight Application,象征未来 Windows 平台上的应用程序开发会全面移转到 WPF/Silverlight,所有发展 Windows 应用程序的开发人员将无法避免这个时代的洪流。

WP7 除了应用程序开发模式的不同,连通讯的功能也起了不小的变化,在以往 Windows Mobile 的时代,若要实作通讯的应用,开发人员必须要自客户端到服务器,都要一手包办,其中最难的地方是服务器的部份,以及如何将讯息传到特定的 Windows Mobile 手机上,Windows Mobile 本身能够识别手机本身的支持很少,要以 Windows Mobile 实作异步讯息的应用的复杂度并不低,不过在现阶段云端运算技术,尤其是 Windows Azure 平台的演进已成熟之际,微软特别在 Windows Azure 上为 WP7 架设了一个讯息的交换中心,称为 Microsoft Push Notification Services(MPNS),它可以支持由云端应用程序或服务应用程序透过它将讯息传递给 WP7 手机,而 WP7 的通道由 WP7 应用程序提交给服务应用程序,然后由服务应用程序将讯息发送给 MPNS,再由 MPNS 将讯息发送给 WP7 手机,整体架构如下图。

透过 MPNS 的讯息通知服务,WP7 上的应用程序可以很容易的由云端应用程序上获得不同的讯息,并实时更新用户接口以告知手机的使用者(例如火车或电影院的购票划位完成通知),而 WP7 上的 Notification Framework 让开发人员在使用 MPNS 开发应用时能更加的便利。

Notification 类型

WP7 对于 MPNS 上的客户端支持,可以分为三种讯息格式:纯讯息、讯息砖块以及讯息列,这三种讯息可支持不同的使用情境,而WP7本身也对这三种讯息格式有不同的用户接口的支持。

纯讯息(RAW)是一种不特别对讯息做格式上处理的数据流,云端应用程序可以向 MPNS 服务发送以字节为基础的数据流,开发人员可以在数据流内写入字节的数据,WP7 操作系统本身并不会针对 RAW 讯息进行用户接口的处理,也就是说 WP7 的应用程序要全权处理 RAW 讯息,这适合在 WP7 与服务间交换数据的功能,或是想要自己控制处理讯息通知的行为与用户接口的需求。

讯息砖块(Tile)是一种会安排在 WP7 首页内的 Quick Launch 区域中的用户接口组件,由一个方型的用户接口组件,它可以由开发人员设定安插在首页的 Quick Launch 区,并且与 MPNS 连接以撷取来自服务应用程序的讯息,并且在收到讯息时更新这个方型的用户接口区,以通知用户有来自服务应用程序的讯息(例如使用不同的图片或突出的用户接口效果来通知用户),当按下该砖块时即会加载应用程序。

讯息列(Toast)则是会出现在 WP7 用户接口内的一种讯息通知方式,它比较像是现在 IE9 或 Google Chrome 的信息列,当 MPNS 有讯息进来时,会显示在 WP7 手机画面的上方或下方(由 WP7 以及应用程序决定)。

显示在 Quick Launch 的 Toast 讯息 显示在应用程序内的 Toast 讯息

发送的讯息类别,由云端应用程序决定,发送 RAW 讯息时会以二进制格式来传输,而发送 Tile 或 Toast 讯息时,则要求要依照指定的格式来传送才行。

Push Notification 应用程序实作概观

WP7 的 MPNS 服务分为三个部份,分别是客户端、服务器与讯息设定三个部份。

客户端的部份由 WP7 内的 Microsoft.Phone.dll 所封装的 Microsoft.Phone.Notification 命名空间支持,内含下列主要对象:

物件 说明
HttpNotification 包装来自 MPNS 的讯息内容。
HttpNotificationChannel 提供与 MPNS 的双向通讯功能,对 Tile 与 Toast 讯息来说,此类别提供与用户接口系结(Bind)的能力,而对 Raw 讯息来说,此类别提供应用程序可与 MPNS 通讯并下载 Raw 讯息的能力。

而客户端在与云端应用程序要求讯息通知机制前,必须要先向 MPNS 提交通讯需求,以取得专属此手机的通讯 URI,这个 URI 会由 WP7 应用程序提供的服务名称以及通道名称来产生,而 WP7 客户端应用程序要使用 HttpNotificationChannel 来处理向 MPNS 要求 URI 以及接收来自于 MPNS 的讯息数据,下列程序代码即是使用 HttpNotificationChannel 对象与 MPNS 取得联系的范例:

[C#]

public HttpNotificationChannel myChannel;
public void CreatingANotificationChannel()
{
    myChannel = HttpNotificationChannel.Find("MyChannel");
 
    if (myChannel == null)
    {
        // Only one notification channel name is supported per application.
        myChannel = new HttpNotificationChannel("MyChannel","www.contoso.com");
 
        SetUpDelegates();
 
        // After myChannel.Open() is called, the notification 
// channel URI will be sent to the application through the ChannelUriUpdated delegate.
        // If your application requires a timeout for setting up a notification channel, 
// start it after the myChannel.Open() call. 
        myChannel.Open();
    }
    else // Found an existing notification channel.
    {
        SetUpDelegates();
 
        // The URI that the application sends to its web service.
        Debug.WriteLine("Notification channel URI:" + myChannel.ChannelUri.ToString());
 
        if (myChannel.ChannelUri == null)
        {
            // The notification channel URI has not been sent to the client. 
// Wait for the ChannelUriUpdated delegate to fire.
            // If your application requires a timeout for setting up a notification channel, 
// start it here.
        } 
    }
 
    // An application is expected to send its notification channel URI to 
// its corresponding web service each time it launches.
    // The notification channel URI is not guaranteed to be the same as the 
// last time the application ran. 
    if (myChannel.ChannelUri != null)
    {
        // SendURIToService(myChannel.ChannelUri);
    }
}
 
public void SetUpDelegates()
{
      myChannel.ChannelUriUpdated += 
new EventHandler<NotificationChannelUriEventArgs>(myChannel_ChannelUriUpdated);
      myChannel.HttpNotificationReceived += 
new EventHandler<HttpNotificationEventArgs>(myChannel_HttpNotificationReceived);
      myChannel.ShellToastNotificationReceived +=
 new EventHandler<NotificationEventArgs>(myChannel_ShellToastNotificationReceived);
      myChannel.ErrorOccurred += 
new EventHandler<NotificationChannelErrorEventArgs>(myChannel_ErrorOccurred);
}
 
void myChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
    // The URI that the application will send to its corresponding web service.
    Debug.WriteLine("Notification channel URI:" + e.ChannelUri.ToString());
    // SendURIToService(e.ChannelUri);
}
 
void myChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
{
    switch(e.ErrorType)
    {
        case ChannelErrorType.ChannelOpenFailed:
            // ...
            break;
        case ChannelErrorType.MessageBadContent:
            // ...
            break;
        case ChannelErrorType.NotificationRateTooHigh:
            // ...
            break;
        case ChannelErrorType.PayloadFormatError:
            // ...
            break;
        case ChannelErrorType.PowerLevelChanged:
            // ...
            break;
    }
}

当 HttpNotificationChannel 与 MPNS 建立联机(使用 Open() 方法)时,即可透过 HttpNotificationChannel.ChannelUri 取得此手机唯一的 URI,这组 URI 会作为云端应用程序将讯息发送给手机的依据。

在服务器端的部份,云端应用程序由于要取得来自 WP7 客户端提交的 MPNS 讯息管道 URI,故在云端程序中必须要有一个允许由 WP7 应用程序登录 URI 的入口,而当云端程序取得 URI 后,即可利用此 URI 来发送讯息。登录 URI 的作法可以有很多种,例如使用 HTTP 的方式或是使用 WCF 的 REST 服务来发展,它只要能够接取 WP7 应用程序发送的 URI 即可,例如下列程序即是使用 WCF 的 REST 服务来实作的登录服务:

[C#]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
[ServiceContract]
public interface IRegistrationService
{
    [OperationContract, WebGet]
    void Register(string uri);
 
    [OperationContract, WebGet]
    void Unregister(string uri);
}
 
namespace WP7Azure.Service
{
    public class RegistrationService : IRegistrationService
    {
        public static event EventHandler<SubscriptionEventArgs> Subscribed;
 
        private static List<Uri> subscribers = new List<Uri>();
        private static object obj = new object();
 
        public void Register(string uri)
        {
            Uri channelUri = new Uri(uri, UriKind.Absolute);
            Subscribe(channelUri);
        }
 
        public void Unregister(string uri)
        {
            Uri channelUri = new Uri(uri, UriKind.Absolute);
            Unsubscribe(channelUri);
        }
 
        #region Subscription/Unsubscribing logic
        private void Subscribe(Uri channelUri)
        {
            lock (obj)
            {
                if (!subscribers.Exists((u) => u == channelUri))
                {
                    subscribers.Add(channelUri);
                }
            }
            OnSubscribed(channelUri, true);
        }
 
        public static void Unsubscribe(Uri channelUri)
        {
            lock (obj)
            {
                subscribers.Remove(channelUri);
            }
            OnSubscribed(channelUri, false);
        }
        #endregion
 
        #region Helper private functionality
        private static void OnSubscribed(Uri channelUri, bool isActive)
        {
            EventHandler<SubscriptionEventArgs> handler = Subscribed;
            if (handler != null)
            {
                handler(null,
                  new SubscriptionEventArgs(channelUri, isActive));
            }
        }
        #endregion
 
        #region Internal SubscriptionEventArgs class definition
        public class SubscriptionEventArgs : EventArgs
        {
            public SubscriptionEventArgs(Uri channelUri, bool isActive)
            {
                this.ChannelUri = channelUri;
                this.IsActive = isActive;
            }
 
            public Uri ChannelUri { get; private set; }
            public bool IsActive { get; private set; }
        }
        #endregion
 
        #region Helper public functionality
        public static List<Uri> GetSubscribers()
        {
            return subscribers;
        }
        #endregion
    }
}

当云端应用程序获取 WP7 的 URI 后,就可以视需求来提交讯息给 WP7 客户端,MPNS 只接受 HTTP 通讯协议的讯息,我们前面有提及讯息有 Raw、Tile 和 Toast 三种格式,这可以透过设定 HTTP 标头的值来设定(如表):

HTTP 标头 规格 说明
MessageID

"X-MessageID"":"1*MessageIDValue

MessageIDValue = STRING (uuid)

//For example:

X-MessageID:<UUID>

设定讯息的唯一标识符,这会在响应时附加在讯息标头内,且这是必要值(HTTP POST 不会自动加入这个标头),如果 MPNS 发现要求没有此标头,会拒绝讯息的传送。
NotificationClass

"X-NotificationClass"":"1*NotificationClassValue

NotificationClassValue = DIGIT

//For example:

X-NotificationClass:1

此处可设定 MPNS 传送讯息给 WP7 客户端程序的批次周期,会因讯息的不同而有所不同。
Notification Type

"X-WindowsPhone-Target"":"1*NotificationTypeValue

NotificationTypeValue = STRING

//For example:

X-WindowsPhone-Target:toast

通知讯息的类型,可设定 raw、tile 与 toast 三种值。
CallbackURI

"X-CallbackURI"":"1*CallbackURIValue

CallbackURIValue = STRING (URI)

//For example:

X-CallbackURI: <URI>

当 MPNS 无法传输讯息给 WP7 客户端时,会呼叫这个 URI 以回复讯息给云端应用程序。

表格中所提的 Notification Class 的批次周期,会依讯息类型的不同决定:

物件 说明
Raw

3: 立即发送讯息。

13: 在 450 秒内发送。

23: 在 900 秒内发送。

Tile

1: 立即发送讯息。

11: 在 450 秒内发送。

21: 在 900 秒内发送。

Toast

2: 立即发送讯息。

12: 在 450 秒内发送。

22: 在 900 秒内发送。

接下来就是决定发送讯息的数据了:

讯息类型 格式
Raw 直接将数据转换成二进制数据的字节数组,写入 HTTP Request Stream 即可。
Tile

由 XML 字段来决定,它的格式是:

<wp:Notification xmlns:wp="WPNotification">

<wp:Tile>"

<wp:BackgroundImage><background image path></wp:BackgroundImage>

<wp:Count><count></wp:Count>

<wp:Title><title></wp:Title>

</wp:Tile>

</wp:Notification>

各字段相对于 WP7 消息框的位置如下:

其中,background image 有一个限制,就是必须小于 80KB,且在 15 秒内下载完毕。

Toast

由 XML 字段来决定,它的格式是:

<wp:Notification xmlns:wp="WPNotification">

<wp:Toast>

<wp:Text1><title></wp:Text1>

<wp:Text2><sub-title></wp:Text2>

</wp:Toast>

</wp:Notification>

各字段相对于 WP7 讯息列的位置如下:

准备完讯息内容后,即可正式提交讯息给 MPNS 服务,MPNS 要求使用 HTTP 通讯,因此使用 HttpWebRequest 就可以达成这个任务:

[C#]

//Create and initialize the request object
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(channelUri);
request.Method = WebRequestMethods.Http.Post;
request.ContentType = "text/xml; charset=utf-8";
request.ContentLength = payload.Length;
request.Headers[MESSAGE_ID_HEADER] = Guid.NewGuid().ToString();
request.Headers[NOTIFICATION_CLASS_HEADER] = ((int)notificationType).ToString();
 
byte[] payload = null;
 
// handling your message payload data.
// ...
 
if (notificationType == NotificationType.Toast)
    request.Headers[WINDOWSPHONE_TARGET_HEADER] = "toast";
else if (notificationType == NotificationType.Token)
    request.Headers[WINDOWSPHONE_TARGET_HEADER] = "token";
 
request.BeginGetRequestStream((ar) =>
{
    //Once async call returns get the Stream object
    Stream requestStream = request.EndGetRequestStream(ar);
 
    //and start to write the payload to the stream asynchronously
    requestStream.BeginWrite(payload, 0, payload.Length, (iar) =>
    {
        //When the writing is done, close the stream
        requestStream.EndWrite(iar);
        requestStream.Close();
 
        //and switch to receiving the response from MPNS
        request.BeginGetResponse((iarr) =>
        {
            using (WebResponse response = request.EndGetResponse(iarr))
            {
                //Notify the caller with the MPNS results
                OnNotified(notificationType, (HttpWebResponse)response, callback);
            }
        },
        null);
    },
    null);
},
null);

[结语]

本文简单的介绍了 Windows Phone 7 上可与云端应用程序交换数据的服务-Microsoft Phone Notification Services,包含 WP7 本身以及对云端应用程序的开发支持,开发人员可以视应用程序与讯息交换的需求来决定在 WP7 上使用的讯息通知方式,以支持实际的商业应用以及提醒的需求。

范例程序代码

在 Windows Phone 7 Hands-on Lab 中,有一个 Using Push Notification 的手动实验范例,对 MPNS 有兴趣的读者,可以参考这个实验范例来练习。

Lab: http://msdn.microsoft.com/en-us/wp7trainingcourse_usingpushnotificationslab.aspx

原文链接:http://msdn.microsoft.com/zh-cn/windowsphone/gg502446.aspx

加载中
返回顶部
顶部