在 MSDN 文档有关 Windows Phone 推送通知 有关推送的内容包含 Tile、Toast、Raw 这三种通知。这三种通知
的方式类似,运用的场合不同,这里不再赘述,它们的运行原理类似:
其实 有关 VoIP 的文档 中,有一个叫 VoipHttpIncomingCallTask 的后台代理:在推送通知通道收到新的传入呼叫时启动,
它使 Windows Phone 运行时 程序集了解到它应该创建新的呼叫。
据说 VoIP 的推送通知是属于强推送的,可靠性比常用的三种要强一些,有关这几种推送的可靠性我没有测试过,不知道是不是
因为微软给 VoIP 的推送通知所使用服务器更可靠一些,纯属猜测。不过像 Skype 这种使用 VoIP 的网络电话,对于呼叫的响应
确实需要比较可靠的消息通知。
这篇文章介绍的工程运行交互大概是:
使用 VoIP 中的 VoipHttpIncomingCallTask 代理进行消息推送时,可以同时显示 Toast 或者 Tile 通知,另一个
好处是,在之前的 Tile通知中,只能更新app的主瓷贴,而次级瓷贴没办法显示通知,使用这个代理时,可以自定义
更新次级瓷贴的消息。
第一步,新建一个wpf 的 pc 端程序,作为推送通知的服务器,运行截图如上面3、中显示的。
页面中的 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,运行截图如上面 1、中所示。
新建一个 VoipHttpIncomingCallTask 后台代理工程,作用是 接收传入呼叫通知时启动该后台代理,
然后显示 Toast 通知和 Tile 通知:
该代理的全部代码:
logs_code_hide('8b15b49b-48cf-422a-8084-7b108bce70dd',event)" src="/Upload/Images/2013090916/2B1B950FA3DF188F.gif" alt="" />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); } }View Code
//自定义消息的字段 [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; } } }View Code
第三步,把第二步中的代理添加引用到 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