在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的部分,在註冊頻道時,需要下列的步驟
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockEnd.gif)
另外,還需補上event handler來處理channel的event
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockEnd.gif)
Server端,發送push notification時,含下列步驟
我們用一個WinForm程式模擬發出通知訊息,需注意一點:此處的Uri值是由上面程式在頻道開啟成功後回傳的,可由Debug outpupt視窗中複製過來
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedSubBlockStart.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/InBlock.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/ExpandedBlockEnd.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
![](http://www.dotblogs.com.tw/Providers/BlogEntryEditor/FCKeditor/editor/dialog/InsertCode/codeimages/None.gif)
恩,我會記得帶傘 ...
糟糕,上面扛了50個VM...
先吃完才有力氣修雲端一號...
買十次總有一次可以「恭喜會員大賺」... =_=
這個應用是我覺得最實用的一個...
(真的是隨便想就好幾個應用呢 XD )
結語:
其實不論RAW、Tile、Toast notification的實作方式都大同小異,大致上只是註冊接聽的類型與訊息格式不同而已。其中RAW的部分還可以自定訊息格式,由程式來實作parse的部分,如此一來,如果能善用這個機制,可以想像的變化彈性還不小呢!在Windows Phone 7 Developer Training Kit上有一個較完整的範例,他的Server部分完整實作一個註冊機制,當有事件要通知時可以找到有誰訂閱,並依序發出訊息。步驟大約有一兩百步,如果想挑戰的朋友歡迎下載來玩玩 (好,我先承認,我做不到第40步就放棄了 )
P.S 1:本文因為倉促成文,又因Beta產品,網路資源有限,有些資料可能會有錯的部分,還會陸續更正,也請各位先進能不吝指導,甘溫~
P.S 2:Demo程式碼是我從範例程式改良並精簡來的,有加上註解,並去掉很多無關架構的部分,希望能幫助大家快速理解整個運作方式。
Demo程式碼下載:
ToastNotificationDemo_100909.zip
參考文獻:
Using Push Notifications
Windows Phone 7 Developer Training Kit
Implementing Push Notifications in Windows Phone 7 - UPDATED (April CTP)
Using Push Notification from Your Windows Phone Application
Windows Phone 7 Push Notifications
Push Notifications for Tiles with Windows Phone 7