在Windows phone 7即将问世之际,笔者想要跟大家分享一个在开发上很好用的机制:Push notification。以往,我们习惯使用Web Service或WCF service来让本地程式与远端伺服器沟通,随时随地,随心所欲的存取资料,always connect!但这个方式到了新的云端应用上,却出现了新的挑战。 首先,因为系统资源、效能与电力等考量,不支援3rd party application 的Multitasking。这也意味着,除了原本内建的「First Party」程式之外,我们在上面开发的程式,当不在foreground时,便会进入suspended(dehydrated)状态,没有办法持续运作。那么,远端如果有资讯的更新,本地端不就没有办法及时反映了吗?这时Push notification就派上用场啦。只要善用这个机制,即使程式进入suspended状态,远端的讯息还是可以顺利的传送到本地端,被Windows Phone接收与处理,即时的反映给使用者。另一方面,在foreground的程式也不需要持续的Pooling(论询),就可以维持在最新状态。 它是如何做到的呢?请参照下图一: 首先,你的程式要先向MPN Service注册(建立)一个WP与MPN之间的channel (步骤1),注册成功之后,MPN Service会回传一个Uri,你拿着这个Uri交给你的云端服务之后(步骤2),接下来它就可以透过这个Uri对你传送Push Notification﹝其实是先送到MPN Server,再由MPN Service转送给你的WP﹞ (步骤3与4)。  图一: Push Notification 运作模式(引用自The Windows Phone Developer Blog) 除了 整个传送机制之外,依据适用情境的不同,Notification还区分为三种:RAW notification 、Tile Notification 、Toast Notification 。下表是小弟初步整理出来的比较。
| Toast Notification | Tile Notification | RAW Notification | 呈现方式 | 直接弹出并覆盖在目前使用者的视窗上面 | 更新在快捷区(quick launch area)中状态砖(tile) 的状态 | 由程式接收、处理、并自订回应方式。 | 有效期间 | | | 仅限程式在foreground | 通知范围 | OS-wide | OS-wide | Application-wide | 适用情境 | 及时告知 | 状态更新 | 资料更新 | 说了这么多,不来点程式码似乎很没有「感觉」。那么,我们就以Toast Notification为例,来感受一下Push Notification的威力吧! 这边我只列出几个较重要的重点步骤,这个程式码是我从网路上的范例程式改良并精简过的,希望能尽量不让不相干的程式码干扰看倌们的理解。至于完整可以RUN的版本,请下载Demo档回去测试喔,谢谢。WP7的部分,在注册频道时,需要下列的步骤
- //Step1:建立频道
- HttpNotificationChannel httpChannel = null;
-
- string channelName = "ToastNotificationTest";
-
- //先寻找是否已有此频道
- //若有,先将之关闭
- httpChannel = HttpNotificationChannel.Find(channelName);
- if (httpChannel != null)
- {
- httpChannel.Close();
- httpChannel.Dispose();
- }
-
- httpChannel = new HttpNotificationChannel(channelName);
-
- //Step2:订阅该频道的event
-
- //当频道成功开启会触发该事件
- httpChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(httpChannel_ChannelUriUpdated);
- //当例外发生会触发该事件
- httpChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(httpChannel_ErrorOccurred);
- //当收到toast notification会触发该事件
- httpChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(httpChannel_ShellToastNotificationReceived);
-
- //Step4:开启频道
- httpChannel.Open();
-
- //Step5:指定该频道接听Toast notification
- httpChannel.BindToShellToast();
复制代码
另外,还需补上event handler来处理channel的event
- //Step3:撰写对应的event handler
- void httpChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
- {
- //当频道开启成功后,会回传一个ChannelUri
- //我们将这个Uri贴到ToastNotificationSender程式的Uri Textbox中
- Debug.WriteLine(e.ChannelUri);
- }
-
- void httpChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e)
- {
- //发生错误:显示错误讯息
- Dispatcher.BeginInvoke(() =>
- {
- txtMessage.Text = e.Message;
- });
- }
-
- void httpChannel_ShellToastNotificationReceived(object sender, NotificationEventArgs e)
- {
- //收到讯息:将之显示在画面上
- foreach (var key in e.Collection.Keys)
- {
- string msg = e.Collection[key];
-
- Dispatcher.BeginInvoke(() =>
- {
- txtMessage.Text += key + ": " + msg + "\r\n";
- });
- }
-
- }
复制代码
Server端,发送push notification时,含下列步骤 我们用一个WinForm程式模拟发出通知讯息,需注意一点:此处的Uri值是由上面程式在频道开启成功后回传的,可由Debug outpupt视窗中复制过来
- //简易防呆检查
- if (txtUri.Text == string.Empty)
- {
- MessageBox.Show("Uri不能为空");
- return;
- }
- if (txtText1.Text == string.Empty || txtText2.Text == string.Empty)
- {
- MessageBox.Show("Text1或Text2不能为空");
- return;
- }
-
- //准备POST Message
- HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(txtUri.Text);
- sendNotificationRequest.Method = WebRequestMethods.Http.Post;
- sendNotificationRequest.ContentType = "text/xml; charset=utf-8";
-
- //以下为建构出符合Toast Norification格式的Message
- //若为要改为RAW或tile Notification格式
- //可参考 http://msdn.microsoft.com/en-us/library/ff402545(VS.92).aspx
-
- //准备HttpHeader
- sendNotificationRequest.Headers = new WebHeaderCollection();
- sendNotificationRequest.Headers["X-MessageID"] = Guid.NewGuid().ToString();
- sendNotificationRequest.Headers.Add("X-WindowsPhone-Target", "toast");
- sendNotificationRequest.Headers.Add("X-NotificationClass", "2");
-
- //准备XML内容
- //此处我们採用写死的作法,只为了简化不必要的步骤以帮助理解,实务上不建议採用
- //注意:XML格式有可能变动,若有format error的讯息请依循最新版本
- //http://social.msdn.microsoft.com/Forums/en/windowsphone7series/thread/64863ad2-895c-4179-8367-b846ad816fc5
-
- string ToastPushXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
- "<wp:Notification xmlns:wp=\"WPNotification\">" +
- "<wp:Toast>" +
- "<wp:Text1>{0}</wp:Text1>" +
- "<wp:Text2>{1}</wp:Text2>" +
- "</wp:Toast>" +
- "</wp:Notification>";
-
- //将XML序列化
- string str = string.Format(ToastPushXML, txtText1.Text, txtText2.Text);
- byte[] strBytes = new UTF8Encoding().GetBytes(str);
- sendNotificationRequest.ContentLength = strBytes.Length;
-
- using (Stream requestStream = sendNotificationRequest.GetRequestStream())
- {
- requestStream.Write(strBytes, 0, strBytes.Length);
- }
-
- //送出内容并取得HttpResponse (在正式系统上建议改为非同步呼叫)
- HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.GetResponse();
- string notificationStatus = response.Headers["X-NotificationStatus"];
- string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];
- lblMessage.Text = "状态: " + notificationStatus + "连线: " + deviceConnectionStatus;
复制代码
我们来模拟一下各种情境,当我们正在用必应的时候.... 结语: 其实不论RAW、Tile、Toast notification的实作方式都大同小异,大致上只是注册接听的类型与讯息格式不同而已。其中RAW的部分还可以自定讯息格式,由程式来实作parse的部分,如此一来,如果能善用这个机制,可以想像的变化弹性还不小呢!在Windows Phone 7 Developer Training Kit上有一个较完整的范例,他的Server部分完整实作一个注册机制,当有事件要通知时可以找到有谁订阅,并依序发出讯息。步骤大约有一两百步,如果想挑战的朋友欢迎下载来玩玩(好,我先承认,我做不到第40步就放弃了 )PS 1:本文因为仓促成文,又因Beta产品,网路资源有限,有些资料可能会有错的部分,还会陆续更正,也请各位先进能不吝指导,甘温~ PS 2:Demo程式码是我从范例程式改良并精简来的,有加上注解,并去掉很多无关架构的部分,希望能帮助大家快速理解整个运作方式。Demo程式码下载:
|