所谓推送通知,用老爷爷都能听懂的话说,就是:

1、我的服务器将通知内容发送到微软的通知服务器,再由通知服务器帮我转发消息。

2、那么,微软的推送服务器是如何知道我的服务器要发消息给哪台手机呢?手机客户端应用程序在创建推送通道时,微软的通知服务器会为手机分配一个URL,我的服务器只要知道这个URL就可以向指定的手机发送消息。所以,手机客户端必须通过网络把获取到的手机URL发给我的服务器,方法很多,如使用Socket、HTTP提交、Web服务、WCF等都可以。

要测试推送通知,可以通过WP 8.1的模拟器的模拟通知功能来实现,但是,这个模拟通知功能目前不太稳定,为啥不稳定呢?我发现可能与Hyper-V的虚拟交换机有关,如果人品好的时候,就可以顺利完成模拟通知;如果哪天人品值波动,就有可能出现错误。

话又说回来,模拟终究是模拟,假的!如果不进行真实的推送测试,说不定你的应用给用户使用时发生意外情况,那就非同一般的痛苦了。对于手机用于接收通知的URL,在调试的时候,可以通过DeBug类输出,然后我们像厦大的教授写论文一样,直接按Ctrl + C,再在服务端Ctrl+V一下就行了。

调试归调试,在实际运作中,应用运行在用户的手机上,你不可能拿每个用户的爱机来debug一下,然后再取得URL的,显然这种做法只能在神话故事才可行,在人间是行不通的。因而我们在开发WP应用时,一定要注意通过网络把手机的通道URL发送到我们的服务器,有了URL才能向某手机发送消息。这就好像你得知道妹子的手机号码,才能向妹子发短信一样,当然,现在人们都用微信来找妹子了。

为了能学会如何使用WP的推送通知,我们首先得有一个服务器,当然不是叫大家去买一台服务器,我们自己动手,写一个测试服务器就行了。

要实现推送,我们必须拥有开发者帐号,至于如何获得,呵呵,方法多着。你可以付款去购买一个,这样的开发者帐号限制较少,可以提交桌面应用;如果你是学生当然首选学生帐号;如果你既不是学生也不想花钱,也是可以的,微软做了一个WP App Studio,是web版的开发工具,不过大家莫笑,这个是给不会编程的人或小孩子玩的,说不得专业。但是,注凹App Studio是免费的,这是重点。至于如何注册,我相信各位都玩过微博、QQ、人人网等东东了,不用我介绍了,无非是填资料,下一步,下一步,完成。如果你不打算在应用商店发布应用,而只是想玩一下,或学习一下,付款信息可以不填,不管它。

创建应用

有了开发者帐号后,我们要在商店中先创建一个应用,应用信息随便填就可以了。如下图,我创建了一个名为“示例应用”的牛X应用程序。

点击应用,进入应用描述页,点击“详细信息”,查看应用的详细信息。

向下滚动页面,找到“Windows推送通知(WNS)”,然后点击超链接进入。

之后会看到这一系列东东。

程序包SID:发送通知的服务器(即我的服务器)在申请Access Token时需要这个,做过微博API的调用的朋友肯定不会陌生,我们在调用微博API前必须得到授权,并换取一个AccessToken,然后我们用这个Token来调用API,相当于一个门卡,通过它可以进入旅店房间。SID除了在申请token时使用,还要把它写到WP应用的清单文件中,即“包名”,两者必须匹配才能允许发送推送消息。

应用程序标识:是应用程序清单文件(实质是XML)中的Identity节点,一定要与“仪表盘”中显示的一致。

客户端ID:完成推送暂时用不到它。

客户端密钥:我的服务器要申请Access Token时需要用到,即我们调用微博API时用到的Secret key,这个一般不要对外公开,开发者自己知道就可以了,不然别人也可以冒充你来发送通知了。

在以上各字段的数据中,要实现推送通知,我们要用到的有:SID、应用程序标识、客户端密钥这三个东东。

开发者身份验证

和微博API调用相似,我们要先验证自己的身份,获得一个access token,才能向指定的手机发送通知,以下是向WNS服务器发送的HTTPS请求的示例。

  1. POST /accesstoken.srf HTTP/1.1
  2. Content-Type: application/x-www-form-urlencoded
  3. Host: https://login.live.com
  4. Content-Length: 211
  5.  
  6. grant_type=client_credentials&client_id={SID}&client_secret={客户端密钥}&scope=notify.windows.com

从中,我们看到该请求有以下几个特点:
1、使用HTTPS方案,地址为https://login.live.com/accesstoken.srf

2、提交方式为POST;

3、内容格式为application/x-www-form-urlencoded;

4、POST的内容包括四个值:grant_type的值固定为client_credentials,这个不用讲,照抄就行;

注意client_id不是“客户端标识”,应该填上SID,不要填错了。client_secret填上“客户端密钥”。

最后那个scope字段也是固定值,照抄就行了,值为notify.windows.com。

发送POST验证成功后,会返回以下内容:

  1. HTTP/1.1 200 OK
  2. Cache-Control: no-store
  3. Content-Length: 422
  4. Content-Type: application/json
  5.  
  6. {
  7. "access_token":"EgAcAQMAAAAALYAAY/c+Huwi3Fv4Ck10UrKNmtxRO6Njk2MgA=",
  8. "token_type":"bearer"
  9. }

返回的JSON中,access_token的值就是申请的access token的值了,我们就是用这个来传送通知的。

发送通知

在拿到token后,我们就可以用它来发送通知了。如:

  1. POST https://db3.notify.windows.com/?token=AgUAAADCQmTg7OMlCg%2fK0K8rBPcBqHuy%2b1rTSNPMuIzF6BtvpRdT7DM4j%2fs%2bNNm8z5l1QKZMtyjByKW5uXqb9V7hIAeA3i8FoKR%2f49ZnGgyUkAhzix%2fuSuasL3jalk7562F4Bpw%3d HTTP/1.1
  2. Authorization: Bearer EgAaAQMAAAAEgAAACoAAPzCGedIbQb9vRfPF2Lxy3K//QZB79mLTgK
  3. X-WNS-RequestForStatus: true
  4. X-WNS-Type: wns/toast
  5. Content-Type: text/xml
  6. Host: db3.notify.windows.com
  7. Content-Length:
  8.  
  9. <toast launch="">
  10. <visual lang="en-US">
  11. <binding template="ToastImageAndText01">
  12. <image id="" src="World" />
  13. <text id="">Hello</text>
  14. </binding>
  15. </visual>
  16. </toast>

上面所示的请求中,目标URL就是手机客户端注册的通过URL,这就是为什么客户端程序要把URL发送给服务器的原因,如果服务器不知道通道URL,就无法知晓要把消息发送给哪一台手机了。

通知的内容都是以XML的形式表示的(RAW通知除外,RAW是自定义通知),关于这些XML模板,参考文档上有介绍,而且我们也可以通过API来得到这些模板,这个现在先不说,后面我们会提到。注意几个HTTP头。

Authorization——传递服务器所获取到的access token,格式为“Bearer <token>”,Bearer与token之间有空格。

X-WNS-Type——通知的类型。wns/toast表示发送Toast通知;wns/tile表示磁贴通知;wns/badge表示锁屏通知……

其他标头可以参考这里:http://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/hh868245.aspx#pncodes_x_wns_type

开发测试服务器

现在,估计大家对推送通知的过程已有了解,趁热打铁、付诸实践是成为编程高手的重要因素,因此,为了成为编程高手,我们接下来马上开工,开发一个可以用于测试推送通的Win Forms程序。

1、以管理员身份运行VS 2013 Express for Desktop。我一直很喜欢Express版本,虽然我有MSDN订阅,但我还是用Express版,它比较简洁,但已经集成了VS的核心功能,用来做商业开发都没问题,最重要的是这家伙是免费的。偏偏有些垃圾公司喜欢装逼,开发个破产品还要弄个旗舰版,而且许多功能也用不上。为什么要以管理员身份运行呢?因为我计划用WCF服务来接收客户端APP发来的URL。

2、新建一个Windows窗体应用程序,我就不建WPF了,WinForm大家熟悉一点,用最成熟的方式有时候很爽。

3、我们首先完成的功能是如何根据不同通知生成XML模板。做过RT应用开发的朋友肯定会记得,在Windows.UI.Notifications命名空间下,使用ToastNotificationManager类或TileUpdateManager类,或BadgeUpdateManager类的GetTemplateContent方法,可以返回指定通知的XML模板。可是,我们这里建的是桌面应用,那有没有可能,在桌面应用中调用RT的API呢?告诉你,是可以的,但前题是你用的是Win 8.1系统。

来吧,咱们试试看。首先打开VS的“解决方案资源管理器”,选中刚才创建的桌面项目,右击鼠标,从快捷菜单中选择“卸载项目”,如下图。

同样,在项目节点上右击,从菜单中选择“编辑 <项目名>”。

这时候,就会用XML编辑器打开项目文件,找到第一个PropertyGroup节点,注意是第一个,不要找到其他去了,在第一个PropertyGroup节点下,加入一个TargetPlatformVersion节点,版本号为8.1。

<TargetPlatformVersion>8.1</TargetPlatformVersion>

然后保存并关闭文件。重新加载项目,在添加引用对话框中,就会看到Windows 8.1的选项卡了。

但是,这里列出的“Windows”程序集不是WP8.1的,通过下面的浏览按钮,找到C:\Program Files (x86)\Windows Phone Kits\8.1\References\CommonConfiguration\Neutral目录下的Windows.winmd文件,这才是WP 8.1的运行时API所在的程序集。另外,为了能正常访问,还要添加以下程序集:

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\Facades\System.Runtime.dll

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\Facades\System.Runtime.InteropServices.dll

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.1\System.Runtime.WindowsRuntime.dll

其中,System.Runtime.InteropServices.dll不一定要添加,但建议添加,因为如果不加,个别API无法附加事件处理程序。

现在,我们这个WinForm程序就可以访问WP8.1中的API了。

我正是要通过这种方法,把所有的通知模板的XML文档都读出来。

下面是部分代码:

  1. private Type enumType = null;
  2.  
  3. void cmbNotifiType_SelectedIndexChanged(object sender, EventArgs e)
  4. {
  5. ComboBox cb = sender as ComboBox;
  6. if (cb.SelectedIndex == -) return;
  7.  
  8. string[] enumNames = null;
  9. // 判断通知类型
  10. switch (cb.SelectedIndex)
  11. {
  12. case : //Toast通知
  13. enumType = typeof(ToastTemplateType);
  14. break;
  15. case : //磁贴通知
  16. enumType = typeof(TileTemplateType);
  17. break;
  18. case : //锁屏通知
  19. enumType = typeof(BadgeTemplateType);
  20. break;
  21. case : //自定义通知
  22. enumType = null; //无
  23. break;
  24. }
  25. enumNames = enumType == null ? null : Enum.GetNames(this.enumType);
  26. cmbTemplate.DataSource = enumNames;
  27. }
  28.  
  29. void cmbTemplate_SelectedIndexChanged(object sender, EventArgs e)
  30. {
  31. if (cmbTemplate.SelectedIndex == -)
  32. {
  33. return;
  34. }
  35. // 显示内容
  36. if (enumType == typeof(ToastTemplateType))
  37. {
  38. ToastTemplateType tem = (ToastTemplateType)Enum.Parse(enumType, cmbTemplate.SelectedItem as string);
  39. XmlDocument doc = ToastNotificationManager.GetTemplateContent(tem);
  40. txtContent.Text = doc.GetXml();
  41. }
  42. else if (enumType == typeof(TileTemplateType))
  43. {
  44. TileTemplateType tem = (TileTemplateType)Enum.Parse(enumType, cmbTemplate.SelectedItem as string);
  45. XmlDocument doc = TileUpdateManager.GetTemplateContent(tem);
  46. txtContent.Text = doc.GetXml();
  47. }
  48. else if (enumType == typeof(BadgeTemplateType))
  49. {
  50. BadgeTemplateType tem = (BadgeTemplateType)Enum.Parse(enumType, cmbTemplate.SelectedItem as string);
  51. XmlDocument doc = BadgeUpdateManager.GetTemplateContent(tem);
  52. txtContent.Text = doc.GetXml();
  53. }
  54. else
  55. {
  56. txtContent.Text = string.Empty;
  57. }
  58. }

通过Enum.GetNames方法可以把一个枚举类型在所有值的名字取出,以字符串数组的形式返回,使用这思路,我们可以将ToastTemplateType、TileTemplateType、BadgeTemplateType几个枚举的值的名称全部读出,显示在下拉列表框(ComboBox)中,当我们在界面上选择一个值时,又通过Enum.Parse方法从枚举值的名称生成枚举实例。最后可以通过GetTemplateContent方法来获取XML文档了。

这样做的好处在于,我们不用手动去准备XML文档。

4、打开Program.cs文件,在Program类中添加用于接收URL的代码。

  1. static class Program
  2. {
  3. /// <summary>
  4. /// 应用程序的主入口点。
  5. /// </summary>
  6. [STAThread]
  7. static void Main()
  8. {
  9. HttpListener listener = new HttpListener();
  10. listener.Prefixes.Add("http://+:85/svr/");
  11. try
  12. {
  13. listener.Start();
  14. listener.BeginGetContext(new AsyncCallback(OnGetAcceptCallback), listener);
  15. }
  16. catch { }
  17.  
  18. /* WinForm项目生成的代码 */
  19.  
  20. try
  21. {
  22. listener.Stop();
  23. }
  24. catch { }
  25. }
  26.  
  27. private static void OnGetAcceptCallback(IAsyncResult ar)
  28. {
  29. HttpListener listener = (HttpListener)ar.AsyncState;
  30.  
  31. try
  32. {
  33. var context = listener.EndGetContext(ar);
  34. string url;
  35. using (var stream = context.Request.InputStream)
  36. {
  37. long len = context.Request.ContentLength64;
  38. byte[] buffer = new byte[len];
  39. stream.Read(buffer, , buffer.Length);
  40. url = System.Text.Encoding.UTF8.GetString(buffer);
  41. }
  42. if (OnGetUrl != null)
  43. {
  44. OnGetUrl(url);
  45. }
  46. }
  47. catch { }
  48.  
  49. try
  50. {
  51. listener.BeginGetContext(new AsyncCallback(OnGetAcceptCallback), listener);
  52. }
  53. catch { }
  54. }
  55.  
  56. // 当收到WP客户端应用发来的URL时会触发该事件
  57. public static event Action<string> OnGetUrl;
  58. }

这里我选用HttpListener对象来监听HTTP请求,注意如果你用真实手机发送时,要配置一下防火墙,如果你嫌麻烦,就暂时把防火墙关了。地址http://+:85/svr/表示监听本机所有地址,如http://192.168.1.50:85/svr/,端口号是85,当然你可以根据实际情况自己改一下。

Program类中还定义了一个静态事件OnGetUrl,当接收到手机发来的URL后会引发这个事件,我们在主窗口中可以通过处理该事件来在用户界面上显示URL。如

  1. this.Load += (s1, a1) =>
  2. {
  3. Program.OnGetUrl += GetUrlService_OnUrlGot;
  4. };
  5. this.FormClosed += (s2, a2) =>
  6. {
  7. Program.OnGetUrl -= GetUrlService_OnUrlGot;
  8. };
  9.  
  10. ……
  11.  
  12. void GetUrlService_OnUrlGot(string url)
  13. {
  14. BeginInvoke(new Action(() =>
  15. {
  16. if (cmbURLs.FindString(url) < )
  17. {
  18. cmbURLs.Items.Add(url);
  19. }
  20. }));
  21. }

当收到URL后,将URL放进一个ComboBox控件的下拉列表中。

5、下面要完成access token验证功能的实现。

  1. // 发起请求
  2. using (HttpClient client = new HttpClient())
  3. {
  4. // 准备要提交的数据
  5. IDictionary<string, string> formdata = new Dictionary<string, string>()
  6. {
  7. { "grant_type", "client_credentials" }, /* 固定值 */
  8. { "client_id", txtSID.Text.Trim() }, /* SID */
  9. { "client_secret", txtSecret.Text.Trim() }, /* 客户端密钥,勿公开 */
  10. { "scope", "notify.windows.com" } /* 固定值 */
  11. };
  12. FormUrlEncodedContent content = new FormUrlEncodedContent(formdata.AsEnumerable());
  13. // POST,并获取返回数据
  14. btnVertify.Enabled = false;
  15. HttpResponseMessage resp = await client.PostAsync("https://login.live.com/accesstoken.srf", content);
  16. btnVertify.Enabled = true;
  17. if (resp.StatusCode == HttpStatusCode.OK) //成功
  18. {
  19. System.IO.Stream streamIn = await resp.Content.ReadAsStreamAsync();
  20. // 反序列化,得到access token
  21. DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(OAuth2Data));
  22. OAuth2Data odata = (OAuth2Data)js.ReadObject(streamIn);
  23. streamIn.Close();
  24. if (odata != null)
  25. {
  26. // 显示token
  27. txtToken.Text = odata.AccessToken;
  28. }
  29. }
  30. else
  31. {
  32. StringBuilder sb = new StringBuilder();
  33. foreach (var hd in resp.Headers)
  34. {
  35. sb.AppendLine(hd.Key + " : " + string.Join(",", hd.Value.ToArray()));
  36. }
  37. MessageBox.Show(string.Format("验证失败,错误码:{0}。\n{1}", (int)resp.StatusCode, sb.ToString()));
  38. }

前面说过,申请token需要向服务器POST格式为application/x-www-form-urlencoded的数据。上面代码中我用的是比较智能的HttpClient类,它可以很轻松地向服务器发送Content,此处应选用FormUrlEncodedContent类。注意这个类比较智能,它会自动帮我们做URL编码处理,因此我们放进去的内容是不需要手动编码的,如果将已编码的内容放进去,会导致重复编码,导致意外错误,token申请失败。
Access Token是以一个JSON对象的形式出现的。还记得吗,上文中我们说过的。如何处理服务器返回的JSON呢?最简单的方法是使用反序列化,直接把JSON对象反序列化为一个类实例就好了。

用来封装token的类定义如下。

  1. [DataContract]
  2. public class OAuth2Data
  3. {
  4. [DataMember(Name="access_token")]
  5. public string AccessToken { get; set; }
  6. [DataMember(Name = "token_type")]
  7. public string TokenType { get; set; }
  8. }

DataMember特性的Name所指定的值一定要与JSON数据的字段名匹配,否则反序列化时无法识别。

6、接下来完成发送通知的功能。

  1. string wns_type_header = ""; //推送类型
  2. string content_type = "text/xml"; //内容格式标头
  3. if (enumType == typeof(ToastTemplateType)) //toast
  4. {
  5. wns_type_header = "wns/toast";
  6. }
  7. else if (enumType == typeof(TileTemplateType)) //tile
  8. {
  9. wns_type_header = "wns/tile";
  10. }
  11. else if (enumType == typeof(BadgeTemplateType)) //badge
  12. {
  13. wns_type_header = "wns/badge";
  14. }
  15. else
  16. {
  17. wns_type_header = "wns/raw";
  18. content_type = "application/octet-stream";
  19. }
  20. // 验证标头
  21. string author_header = string.Format("Bearer {0}", txtToken.Text);
  22.  
  23. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(cmbURLs.Text);
  24. // 添加必要标头
  25. request.ContentType = content_type;
  26. request.Headers.Add("X-WNS-Type", wns_type_header);
  27. request.Headers.Add("Authorization", author_header);
  28. // 添加可选标头
  29. if (ckbSuppressPop.Checked && enumType == typeof(ToastTemplateType))
  30. {
  31. request.Headers.Add("X-WNS-SuppressPopup", "true");
  32. }
  33. if (ckbWns_Tag.Checked && string.IsNullOrWhiteSpace(txtWNS_Tag.Text) == false)
  34. {
  35. request.Headers.Add("X-WNS-Tag", txtWNS_Tag.Text);
  36. }
  37. if (ckbWns_Group.Checked && string.IsNullOrWhiteSpace(txtWns_Group.Text) == false)
  38. {
  39. request.Headers.Add("X-WNS-Group", txtWns_Group.Text);
  40. }
  41.  
  42. // POST
  43. request.Method = "POST";
  44. // 内容
  45. byte[] data = Encoding.UTF8.GetBytes(txtContent.Text);
  46. //request.ContentLength = data.Length;
  47. // 写入
  48. using (var streamout = request.GetRequestStream())
  49. {
  50. streamout.Write(data, , data.Length);
  51. }
  52.  
  53. // 发起请求
  54. HttpWebResponse response = null;
  55. try
  56. {
  57. response = (HttpWebResponse)request.GetResponse();
  58. if (response.StatusCode == HttpStatusCode.OK)
  59. {
  60. MessageBox.Show("发送成功。");
  61. }
  62. else
  63. {
  64. MessageBox.Show("发送失败。");
  65. }
  66. }
  67. catch (WebException webex)
  68. {
  69. ……
  70. }

通知的正文是XML文档(RAW通知除外),以下几个标头是必须的:
Authorization:验证后获取的token就通过该头来传递,格式为Bearer <access token>,注意Bearer与Token之间有个空格,之个access token就是我们通过验证后获取的Token。

X-WNS-Type:通知的类型。如果是Toast通知就wns/toast,如果是磁贴通知就wns/tile,反正照着MSDN的文档套就行了,就像抄袭论文一样轻松。

ContentType:基本上都是text/xml,只有RAW通知使用application/octet-stream

然后是一些可选的标头:

X-WNS-Tag和X-WNS-Group可以一起用,tag相当于通知的id,Toast通知比较明显,如果我发送了一条tag为a的Toast通知,然后再发一条tag为b的Toast通知,就算a和b的内容完全一样,但由于tag不同,它们在手机上会显示为两条消息。

如果第一条Toast的tag为c,然后再发一条tag也为c的Toast通知,那么,第二条Toast会替换掉第一条Toast,也就是说始终在手机上只会提示一条通知。

如果用上了X-WNS-Group,就可以对Tag进行分组,同一组下的tag必须唯一,但不同组之间可以存在相同的tag的通知,比如,组1中有一条通知的tag为f1,组2中有一条通知的tag为f1,虽然它们tag相同,但处于不同的组中,因此它们在手机上将显示为两条通知。

X-WNS-SuppressPopup标头是针对Toast通知而言的,如果为false,则Toast会弹出,如下图所示。

如果使用了X-WNS-SuppressPopup了标头,并设置为true,则Toast通知不会弹出来,而直接放进通知中心了。如下图

现在,这个用于测试推送通知的服务器已经完成了,我们可以用它来发送通知了。源码下载地址:http://files.cnblogs.com/tcjiaan/PushNotificationTestServer.rar

下一篇文章咱们来完成WP手机客户端程序,来接收推送通知。

【WP 8.1开发】推送通知测试服务端程序的更多相关文章

  1. iOS开发推送--客户端 服务端

    1.推送过程简介 (1)App启动过程中,使用UIApplication::registerForRemoteNotificationTypes函数与苹果的APNS服务器通信,发出注册远程推送的申请. ...

  2. iOS推送通知的实现步骤

    一.关于推送通知 来源:http://blog.csdn.net/enuola/article/details/8627283 推送通知,也被叫做远程通知,是在iOS 3.0以后被引入的功能.是当程序 ...

  3. iOS推送通知

    推送通知 此通知非彼通知. NSNotification是抽象的,看不见的,但是可以监听,属于观察者模式的一种设计模式. 推送通知是可见的,能用肉眼看见的,是真正的和用户打交道的通知. 推送通知分为两 ...

  4. IOS 远程推送通知(UIRemoteNotification)

    ●  什么是远程推送通知 ●  顾名思义,就是从远程服务器推送给客户端的通知(需要联网) ●  远程推送服务,又称为APNs(Apple Push Notification Services) ●   ...

  5. 【WP 8.1开发】手机客户端应用接收推送通知

    上一篇文章中,已经完成了用于发送通知的服务器端,接下来我们就用这个服务端来测试一下. 在开始测试之前,我们要做一个接收通知的WP应用. 1.启动VS Express for Windows,新建项目, ...

  6. Windows Phone开发(45):推送通知大结局——Raw通知

    原文:Windows Phone开发(45):推送通知大结局--Raw通知 为什么叫大结局呢?因为推送通知服务就只有三种,前面扯了两种,就剩下一种--Raw通知. 前面我们通过两节的动手实验,相信大家 ...

  7. Windows Phone开发(43):推送通知第一集——Toast推送

    原文:Windows Phone开发(43):推送通知第一集--Toast推送 好像有好几天没更新了,抱歉抱歉,最近"光荣"地失业,先是忙于寻找新去处,唉,暂时没有下文.而后又有一 ...

  8. Windows Phone开发(44):推送通知第二集——磁贴通知

    原文:Windows Phone开发(44):推送通知第二集--磁贴通知 前面我们说了第一个类型--Toast通知,这玩意儿不知大家是不是觉得很新鲜,以前玩.NET编程应该没接触过吧? 其实这东西绝对 ...

  9. iOS 10推送通知开发

    原文地址:Developing Push Notifications for iOS 10,译者:李剑飞 虽然通知经常被过度使用,但是通知确实是一种获得用户关注和通知他们需要更新或行动的有效方式.iO ...

随机推荐

  1. STL源码--序列式容器

    1. vector: vector的内存管理,动态分配内存,不需要程序员来维护存储空间,是与array最大的区别,程序员只需从逻辑上关注代码,而不需要对内部的存储空间的分配和回收分心.首先,vecto ...

  2. Android Activity生命周期

    从android api文档摘抄出来的activity生命周期图如下: Activity有如下四种状态 a.活动状态  activity处于屏幕前台,获取到了焦点可以和用户进行交互,同一时刻只有一个a ...

  3. MYSQL中 ENUM 类型

    MYSQL中 ENUM 类型的详细解释 ENUM类型 ENUM 是一个字符串对象,其值通常选自一个允许值列表中,该列表在表创建时的列规格说明中被明确地列举. 在下列某些情况下,值也可以是空串(&quo ...

  4. 网上搜集了点资料,学web的人互相分享共同进步吧(php编码的好习惯必须养成)

    网上搜集了点资料,学web的人互相分享共同进步吧 一.优秀的代码应该是什么样的? 优秀的PHP代码应该是结构化的.大段的代码应该被分割整理成一个个函数或方法,而那些不起眼的小段代码则应该加上注释,以便 ...

  5. C#多线程学习

    一.线程的定义 进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源.进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程 ...

  6. chrome插件开发-消息机制中的bug与解决方案

    序言 最近开发chrome插件,涉及到消息传递机时按照教程去敲代码,结果总是不对.研究了大半天终于找到原因,现在记录下. 程序 插件程序参考官网 chrome官网之消息传递机制, 不能FQ的同事也可以 ...

  7. 《利用Python进行数据分析》第8章学习笔记

    绘图和可视化 matplotlib入门 创建窗口和画布 fig = plt.figure() ax1 = fig.add_subplot(2,2,1) ax2 = fig.add_subplot(2, ...

  8. jq实现 按钮点击一次后 3秒后在可点击

    if(printRemind(selectPrintTemplate,selectOrders,orderStatus,isPreview)) //调用打印数据并打印 ajaxDataAndDoPri ...

  9. mysql客户端导入sql文件命令

    mysql -h localhost -u root -p dbname < filename

  10. 移动开发发展方向-----Hybird混合开发3大方案

    移动开发发展方向-----Hybird混合开发3大方案