在 MSDN 文檔有關 Windows Phone 推送通知 有關推送的內容包含 Tile、Toast、Raw 這三種通知。這三種通知windows
的方式相似,運用的場合不一樣,這裏再也不贅述,它們的運行原理相似:服務器
其實 有關 VoIP 的文檔 中,有一個叫 VoipHttpIncomingCallTask 的後臺代理:在推送通知通道收到新的傳入呼叫時啓動,網絡
它使 Windows Phone 運行時 程序集了解到它應該建立新的呼叫。app
聽說 VoIP 的推送通知是屬於強推送的,可靠性比經常使用的三種要強一些,有關這幾種推送的可靠性我沒有測試過,不知道是否是ide
由於微軟給 VoIP 的推送通知所使用服務器更可靠一些,純屬猜想。不過像 Skype 這種使用 VoIP 的網絡電話,對於呼叫的響應函數
確實須要比較可靠的消息通知。測試
在這個代理運行的時候,能夠在代理的 OnInvoke(ScheduledTask task) 方法中經過:ui
VoipHttpIncomingCallTask incomingCallTask = task as VoipHttpIncomingCallTask; if (incomingCallTask != null) { // 把推送通知中自定義的 xml 文本轉換成該對象 Notification pushNotification; using (MemoryStream ms = new MemoryStream(incomingCallTask.MessageBody)) { XmlSerializer xs = new XmlSerializer(typeof(Notification)); pushNotification = (Notification)xs.Deserialize(ms);//反序列化消息的 xml } }
中的 incomingCallTask.MessageBody 獲取推送的消息內容,而且這個消息體是能夠自定義的,而後能夠做爲 Toast 或者 Tile 通知顯示給用戶。做爲消息通知,有關 VoIP 的其它的步驟就再也不繼續了。this
MSDN 上介紹的 Toast 和 Tile 通知的步驟:spa
1) 首先,客戶端 app 須要向微軟的推送服務器申請 push channel,微軟服務器返回 channel 後,客戶端把這個 channel 發送到第三方服務器
2) 當第三方服務器須要向用戶推送消息時,第三方服務器只需向微軟的推送服務器發送 http 數據請求,而後微軟的推送服務器再把消息推送到客戶端
VoIP 的呼入推送和上面的步驟相似,不一樣的是客戶端代碼工程須要建立並添加一個 VoipHttpIncomingCallTask 代理:
// 建立一個新的呼叫代理 VoipHttpIncomingCallTask incomingCallTask = new VoipHttpIncomingCallTask(代理名稱, 推送通道); incomingCallTask.Description = "Incoming call task"; ScheduledActionService.Add(incomingCallTask);
當客戶端收到 VoIP 的推送通知時,會調用代理的 OnInvoke(ScheduledTask task) 方法,就能夠在這個方法裏面獲取上面所說的 incomingCallTask.MessageBody 屬性中獲取消息通知了。
第三方服務器端在發送消息的時候,HttpWebRequest 對象發送 http 請求時,請求報文頭:
1) tile 通知:Request.Headers.Add("X-NotificationClass", "1");
2) toast 通知:Request.Headers.Add("X-NotificationClass", "2");
3) raw 通知:Request.Headers.Add("X-NotificationClass", "3");
4) voip 通知:Request.Headers.Add("X-NotificationClass", "4")
而且 VoIP 的消息的報文體能夠是自定義的,例如:
Text = @"<?xml version=""1.0"" encoding=""utf-8""?> <wp:Notification xmlns:wp=""WPNotification""> <TitleField>北京天安門</TitleField> <BodyField>敘利亞可能遭受美國武力打擊</BodyField> </wp:Notification>";
客戶端定義相同的字段,用於反序列化:
public partial class Notification { public string TitleField { get; set; } public string BodyField { get; set; } }
客戶端和服務器端制定相同的可序列化數據類型進行通訊。
這篇文章介紹的工程運行交互大概是:
使用 VoIP 中的 VoipHttpIncomingCallTask 代理進行消息推送時,能夠同時顯示 Toast 或者 Tile 通知,另外一個
好處是,在以前的 Tile通知中,只能更新app的主瓷貼,而次級瓷貼沒辦法顯示通知,使用這個代理時,能夠自定義
更新次級瓷貼的消息。
第一步,新建一個wpf 的 pc 端程序,做爲推送通知的服務器,運行截圖如上面三、中顯示的。
頁面中的 xaml:
<TextBox x:Name="txtPushChannel" HorizontalAlignment="Left" Height="80" Margin="109,22,0,0"
TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="334"/> <TextBlock HorizontalAlignment="Left" Margin="42,22,0,0" TextWrapping="Wrap" Text="Channel:"
VerticalAlignment="Top"/> <TextBox x:Name="txtXml" HorizontalAlignment="Left" Height="107" Margin="109,149,0,0" TextWrapping="Wrap"
Text="TextBox" VerticalAlignment="Top" Width="334"/> <TextBlock HorizontalAlignment="Left" Margin="58,149,0,0" TextWrapping="Wrap" Text="xml:"
VerticalAlignment="Top" RenderTransformOrigin="0.283,0.387"/> <Button Content="Push" HorizontalAlignment="Left" Margin="205,272,0,0" VerticalAlignment="Top"
Width="75" Click="Button_Click"/>
相應的 C#,做用是向微軟的推送服務器發送推送通知,微軟的服務器就會把消息推送到註冊了該 channel 的手機上面:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); txtXml.Text = @"<?xml version=""1.0"" encoding=""utf-8""?> <wp:Notification xmlns:wp=""WPNotification""> <TitleField>北京天安門</TitleField> <BodyField>敘利亞可能遭受美國武力打擊</BodyField> </wp:Notification>"; txtPushChannel.Text = "http://db3.notify.live.net/throttledthirdparty/01.00/AQEcdarEW-cRQIsjVBGEMoHVAgAAAAAD
tA8DAAQUZm52OjI0MjhFRkVEMUVERTE0MzAFBkxFR0FDWQ";//推送通道 } private void Button_Click(object sender, RoutedEventArgs e) { // 向這個 channel URI 發送推送通知,模擬 VoIP 呼叫 try { // 只能使用 HTTP POST 方式向微軟的推送服務器發送通知 HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(txtPushChannel.Text); sendNotificationRequest.Method = "POST"; // 自定義消息 byte[] notificationMessage = Encoding.UTF8.GetBytes(txtXml.Text); // 設置請求報文頭 sendNotificationRequest.ContentLength = notificationMessage.Length; sendNotificationRequest.ContentType = "text/xml"; sendNotificationRequest.Headers["X-NotificationClass"] = "4"; // Class 4 indicates an incoming VoIP call // 發送請求報文體 sendNotificationRequest.BeginGetRequestStream((IAsyncResult arRequest) => { try { using (Stream requestStream = sendNotificationRequest.EndGetRequestStream(arRequest)) { requestStream.Write(notificationMessage, 0, notificationMessage.Length); } // 獲取微軟服務器的響應 sendNotificationRequest.BeginGetResponse((IAsyncResult arResponse) => { try { HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.EndGetResponse(arResponse); string notificationStatus = response.Headers["X-NotificationStatus"]; string subscriptionStatus = response.Headers["X-SubscriptionStatus"]; string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"]; // 顯示微軟服務器返回的發送狀態 this.ShowResult(string.Format("Notification: {0}\r\nSubscription: {1}\r\nDevice: {2}",
notificationStatus, subscriptionStatus, deviceConnectionStatus)); } catch (Exception ex) { this.ShowResult(ex); } }, null); } catch (Exception ex) { this.ShowResult(ex); } }, null); } catch (Exception ex) { this.ShowResult(ex); } } // 顯示彈出框消息 private void ShowResult(object result) { this.Dispatcher.BeginInvoke((Action)(() => { Exception ex = result as Exception; if (ex == null) { MessageBox.Show(string.Format("{0}\r\n{1}", result, DateTime.Now)); } else { MessageBox.Show(string.Format("An error has occurred\r\n{0}", DateTime.Now)); } }), null); } }
第二步,新建一個 WP 客戶端程序,默認名稱 PhoneApp1,運行截圖如上面 一、中所示。
新建一個 VoipHttpIncomingCallTask 後臺代理工程,做用是 接收傳入呼叫通知時啓動該後臺代理,
而後顯示 Toast 通知和 Tile 通知:
該代理的所有代碼:
public class ScheduledAgent : ScheduledTaskAgent { /// <remarks> /// ScheduledAgent 構造函數,初始化 UnhandledException 處理程序 /// </remarks> static ScheduledAgent() { // 訂閱託管的異常處理程序 Deployment.Current.Dispatcher.BeginInvoke(delegate { Application.Current.UnhandledException += UnhandledException; }); } /// 出現未處理的異常時執行的代碼 private static void UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) { if (Debugger.IsAttached) { // 出現未處理的異常;強行進入調試器 Debugger.Break(); } } /// <summary> /// 運行計劃任務的代理 /// </summary> /// <param name="task"> /// 調用的任務 /// </param> /// <remarks> /// 調用按期或資源密集型任務時調用此方法 /// </remarks> protected override void OnInvoke(ScheduledTask task) { //TODO: 添加用於在後臺執行任務的代碼 VoipHttpIncomingCallTask incomingCallTask = task as VoipHttpIncomingCallTask; if (incomingCallTask != null) { // 把推送通知中自定義的 xml 文本轉換成該對象 Notification pushNotification; using (MemoryStream ms = new MemoryStream(incomingCallTask.MessageBody)) { XmlSerializer xs = new XmlSerializer(typeof(Notification)); pushNotification = (Notification)xs.Deserialize(ms);//反序列化消息的 xml } Debug.WriteLine(" Incoming call from caller {0}, number {1}", pushNotification.TitleField, pushNotification.BodyField); // 顯示通知 ChangeMainTile(pushNotification.TitleField, pushNotification.BodyField); } NotifyComplete();//通知操做系統,代理已針對代理的當前調用完成其目標任務。 } // 同時顯示 Tile 通知和 Toast 通知(app的主瓷貼須要訂到開始菜單中) void ChangeMainTile(string strTitle, string strBody) { // Toast 通知 ShellToast to = new ShellToast(); to.Title = strTitle; to.Content = strBody; to.Show(); // 瓷貼通知,第一個瓷貼爲 app 的主瓷貼 ShellTile defaultTile = ShellTile.ActiveTiles.First(); StandardTileData tileData = new StandardTileData() { Title = strTitle, Count = 0, BackTitle = strTitle, BackContent = strBody }; defaultTile.Update(tileData); } }
//自定義消息的字段 [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17613")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "WPNotification")] [System.Xml.Serialization.XmlRootAttribute(Namespace = "WPNotification", IsNullable = false)] public partial class Notification { private string titleField; private string bodyField; //消息標題 [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] public string TitleField { get { return this.titleField; } set { this.titleField = value; } } //消息體 [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] public string BodyField { get { return this.bodyField; } set { this.bodyField = value; } } }
第三步,把第二步中的代理添加引用到 PhoneApp1 工程中:
在 MainPage 中添加一個文本框和一個按鈕:
<TextBox x:Name="txtRegist" HorizontalAlignment="Left" Height="318" Margin="23,125,-23,0"
TextWrapping="Wrap" Text="channel" VerticalAlignment="Top" Width="456"/> <Button Content="Regist" HorizontalAlignment="Left" Margin="113,495,0,0"
VerticalAlignment="Top" Width="265" Click="Button_Click"/>
在 MainPage 的 codebehind 頁面中,添加註冊 push channel 邏輯:
public partial class MainPage : PhoneApplicationPage { // 構造函數 public MainPage() { InitializeComponent(); } // VoIP 的呼入任務的名稱 static string incomingCallTaskName = "PhoneVoIPApp.IncomingCallTask"; // 推送通知的通道名稱 static string pushChannelName = "VoIPChannel"; private void Button_Click(object sender, RoutedEventArgs e) { InitPushChannel(); } private void InitPushChannel() { // 查找之前註冊的推送通知通道 HttpNotificationChannel httpChannel = HttpNotificationChannel.Find(pushChannelName); // 若是之前沒有註冊通道,則建立一個新通道 if (httpChannel == null || httpChannel.ChannelUri == null) { httpChannel = new HttpNotificationChannel(pushChannelName); httpChannel.Open(); } else { // 已經存在的推送通道 pushChannelName = httpChannel.ChannelUri.ToString(); Debug.WriteLine("[App] Existing Push channel URI is {0}", pushChannelName); } // 註冊相應的事件 httpChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(PushChannel_ChannelUriUpdated); httpChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(PushChannel_ErrorOccurred); httpChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(PushChannel_HttpNotificationReceived); } private void PushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e) { Debug.WriteLine("[App] New Push channel URI is {0}", e.ChannelUri); InitHttpNotificationTask(); // 初始化 VoIP 呼入通知的代理 this.Dispatcher.BeginInvoke(delegate { txtRegist.Text = e.ChannelUri + ""; }); // TODO: Let your cloud server know that the push channel to this device is e.ChannelUri. } private void PushChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e) { // TODO: Let your cloud server know that the push channel to this device is no longer valid. } private void PushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e) { // TODO: Process raw push notifications here, if required. } public void InitHttpNotificationTask() { // 獲取已經存在的 VoIP 呼入代理 VoipHttpIncomingCallTask incomingCallTask = ScheduledActionService.Find(incomingCallTaskName) as VoipHttpIncomingCallTask; if (incomingCallTask != null) { if (incomingCallTask.IsScheduled == false) { // 從計劃操做服務中刪除具備指定名稱的 ScheduledAction ScheduledActionService.Remove(incomingCallTaskName); } else { // 計劃任務已經添加,而且計劃狀態爲 true,則直接返回 return; } } // 建立一個新的呼叫代理 incomingCallTask = new VoipHttpIncomingCallTask(incomingCallTaskName, pushChannelName); incomingCallTask.Description = "Incoming call task"; ScheduledActionService.Add(incomingCallTask); } }
代碼工程完成,源碼下載。
這個工程是參考微軟的 VoIP 代碼完成的,具體關於 VoIP 的代碼: ChatterBox VoIP sample app