[C#] 利用ASP.net WebService和Windows Service實作Android手機的訊息推播(舊版C2DM)
前言
Android前端+ .Net伺服器端的訊息推播(Push Notification),最近開發這功能差點開天窗,呵呵
還好有這篇:Android push notification implementation using ASP.NET and C#
不過流程說明應該有少寫(至少我看完還是寫不出來XD)
經過和App開發人員多次討論,終於弄懂詳細步驟
以下站在Server端角度做個說明
開發流程
先準備一個Google帳戶,然後到此網址申請該帳戶要使用C2DM服務
https://developers.google.com/android/c2dm/signup?hl=zh-TW
因為要填App的Package Name(這樣使用者點選通知時,手機裝置才知道要喚醒哪個App)
此部份請前端開發人員填表即可
見上圖
1、2步驟是前端App的事,交給前端開發人員煩惱就好
而Google C2DM為Google那邊的Server,開發時期不會實際碰觸到,所以不用理會C2DM的詳細實作及架設
Server端開發人員只要知道該送給C2DM哪些參數就好(詳見程式碼實作↓)
步驟3. 前端App將Registration ID(也有人稱token,因為iOS平台那邊叫token,不過意思一樣都是做為手機的識別值)
送給AP Server的時機不一定,看App開發人員的設計
AP Server這邊就架一個ASP.net網站,掛上Web Service或泛型處理常式來接Registration ID
並到DB檢查,如果沒有此Registration ID的話,就儲存進DB
此Web Service也可以再做一個「從DB移除Registration ID」的函數供前端App呼叫
步驟4. 因為Server端要自動訊息推播
除了可用Windows Service專案外,也可考慮使用Console專案搭配Windows作業系統的排程功能達到定期檢查有無新資料的目的
步驟5. C2DM把訊息送到手機上,這部份是Google他家的事,可以不用理會
只是要注意幾個限制:
1. C2DM送出的訊息有限制長度1024byte,所以Windows Service只要送出簡單的訊息就好,並不是把DB裡全部新的資料送出去。
2. C2DM限制發送的訊息數量,不過官方文件並沒指出確切數量是多少。
※Server端 .Net開發人員簡單講,只要寫兩支程式一個WebService,一個Windows Service就行了
代碼實作
步驟3:WebService部份我使用泛型處理常式接收App送過來的RegistrationID並儲存至DB
<%@ WebHandler Language="C#" Class="SaveAndroidRegisID" %>
using System;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using SystemDAO;//以下用的SqlHelper來自:http://www.cnblogs.com/sufei/archive/2010/01/14/1648026.html
public class SaveAndroidRegisID : IHttpHandler {
NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
//App傳送的RegistrationID
string RegistrationID = context.Request["RegistrationID"];
string Del = context.Request["Del"];//是否刪除RegistrationID
SqlParameter[] param = new SqlParameter[] { new SqlParameter() { ParameterName = "@RegistrationID", SqlDbType = SqlDbType.VarChar, Value = RegistrationID } };
string json = string.Empty;//輸出結果的json字串
try
{
if (!string.IsNullOrEmpty(Del) && "true".Equals(Del))
{//從DB把RegistrationID刪除
SqlHelper.ExecteNonQuery(CommandType.Text,"Delete From tb_ServerPush_AndroidRegisID Where RegistrationID =@RegistrationID",param);
}
else
{//新增RegistrationID到DB
SqlHelper.ExecteNonQuery(CommandType.Text, "Insert into tb_ServerPush_AndroidRegisID (RegistrationID) values (@RegistrationID)", param);
}
json = @"{""Success"":true}";
context.Response.Write(json);//輸出成功訊息
}
catch (Exception ex)
{
logger.Error(ex.ToString());//寫Log
json = @"{""Success"":false}";
context.Response.Write(json);//輸出失敗訊息
}
}
public bool IsReusable {
get {
return false;
}
}
}
步驟4:新增一個Windows Service專案,目的是為了定時檢查有無新的資料,並推播訊息至C2DM,再由C2DM發送訊息
而Windows Service推播訊息至C2DM的實作不需要自己寫,到CodePlex下載現成的Code:Android push notification implementation using ASP.NET and C#
要注意下載來的Android.cs因為會在Windows Service環境中使用HttpUtility.UrlEncode
所以要引用System.Web參考:
Step 1:
Step 2:
加入System.Web.dll參考
Step 3:
在Android.cs中,using System.Web;
Windows Service代碼實現:
using System;
using System.Collections.Generic;
using System.ComponentModel
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using SystemDAO;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Net;
using System.Configuration;
using System.Xml.Linq;
using PushNotification;
namespace ws_ServerPushNotification
{
public partial class Service_ServerPusth : ServiceBase
{
Timer timer = new Timer();
//這個objAndroid物件要從CodePlex下載:http://www.codeproject.com/Articles/339162/Android-push-notification-implementation-using-ASP
Android objAndroid = new Android();
public Service_ServerPusth()
{
InitializeComponent();
#region 設定timer
timer.Enabled = true;
timer.Interval = 5000;//輪詢間隔5秒
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
#endregion
dtTemp.Columns.Add("title", typeof(string));
dtTemp.Columns.Add("datetime", typeof(string));
}
protected override void OnStart(string[] args)
{
timer.Start();//開始輪詢
}
string googleAccount = ConfigurationManager.AppSettings["googleAccount"];//一開始申請的,擁有C2DM服務的Google mail帳號
string googlePwd = ConfigurationManager.AppSettings["googlePwd"];//帳號的密碼
//此事件會反覆執行
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
timer.Stop();//以下要長時間作業,所以timer先停止(沒停止的話,會變成非同步程式設計)
if (this.isNotyfy())//如果有要通知的話
{
//登入Google,這個objAndroid物件要從CodePlex下載:http://www.codeproject.com/Articles/339162/Android-push-notification-implementation-using-ASP
string theAuthCode = this.objAndroid.CheckAuthentication(googleAccount, googlePwd);//取得授權碼
//從DB取得RegistrationID的DataTable
DataTable dtRegistrationID = SqlHelper.GetTable(CommandType.Text, "Select RegistrationID from yourTable Order by RegistrationID ASC", null)[0];
foreach (DataRow row in dtRegistrationID.Rows)
{
string RegistrationID = row["RegistrationID"].ToString();
//這個objAndroid物件要從CodePlex下載:http://www.codeproject.com/Articles/339162/Android-push-notification-implementation-using-ASP
string sendResult = this.objAndroid.SendMessage(RegistrationID, "您有XXX的通知!" , theAuthCode);
if (!sendResult.ToLower().StartsWith("id"))
{//無效的RegistrationID時回傳:Error=InvalidRegistration
//無效的RegistrationID,要從DB移除此RegistrationID
SqlParameter[] param = new SqlParameter[] { new SqlParameter() { SqlDbType = SqlDbType.VarChar, ParameterName = "@RegistrationID", Value = RegistrationID } };
SqlHelper.ExecteNonQuery(CommandType.Text, "Delete from yourTable Where RegistrationID=@RegistrationID", param);
}
else if(sendResult.ToLower().StartsWith("id"))
{//成功送出訊息時,回傳id開頭的字串
EventLog.WriteEntry("送出一筆訊息,結果:" + sendResult);
}
}
}
timer.Start();//長時間作業結束,啟動timer
}
DataTable dtTemp = new DataTable();//要暫存資料的全域變數
string RssUrl = ConfigurationManager.AppSettings["RssUrl"];//資料來源Rss的超連結
private bool isNotify()
{
bool is_notify = false;//預設不通知
try
{
//Linq to Rss
XDocument xDoc = XDocument.Load(RssUrl);
IEnumerable<XElement> items = xDoc.Descendants("item");//抓出所有的目標資料
if (items.Any())//至少有一筆
{
XElement ele = items.FirstOrDefault();//取得第一筆item
string title = ele.Element("title").Value;
string datetime = ele.Element("datetime").Value;
#region 第一次如果全域變數沒有資料的話,就先存進全域變數DataTable
if (dtTemp.Rows.Count == 0)
{
dtTemp.Rows.Add(title, datetime);//加第一筆至DataTable
}
#endregion
#region 和全域變數比對(第一次執行不會進入此if)
if (title != dtTemp.Rows[0]["title"].ToString() ||
datetime != dtTemp.Rows[0]["datetime"].ToString())
{//任一資料不一樣
dtTemp.Clear();//清除舊數據
dtTemp.Rows.Add(title, datetime);//加第一筆至DataTable,下一次就和此數據比對
is_notify = true;//要通知
}
#endregion
}
}
catch (Exception ex)
{
EventLog.WriteEntry("isNotify()發生例外:" + ex.ToString());
}
return is_notify;
}
protected override void OnStop()
{
}
}
}
↑撰寫完成後,Windows Service專案的安裝專案建立可以參考:如何建立 Windows 服務應用程式的安裝專案在 Visual C# 中、[技術] 安裝Windows服務
※小提醒:為了讓Windows Service可以在32位元和64位元電腦上跑,記得Windows Service專案右鍵>屬性>建置>平台目標最好選Any CPU
※安裝好Windows Service後,最好再從系統管理工具>服務 確認服務要被啟動
Server端開發完畢時,從CodePlex下載來的類別檔Android.cs的SendMessage函數裡有一行
postFieldNameValue.Add("data.message", Message);
要把data. 開頭的tag 告知App開發人員,這樣前端才知道訊息通知要顯示什麼訊息
執行結果:
(網路連線中才可收得到通知)
結語
當初要開發手機平台的Server Push機制,原本我還傻傻地以為和Web平台的推播機制一樣
打算照抄Code收工了事:[ASP.NET] 長時間由伺服器不中斷供應資料的開發方法 - Comet Programming by 小朱
後來詢問了iOS和Android App開發人員知道手機平台不是這樣做
研究了一下,整理出上面的筆記,筆記精簡很多應該很好懂,若說明有不妥部份,請多指教
其他可參考的文章:
Android Cloud to Device Messaging Framework by Google官方文件(Android前端+觀念)
Ken Yang 筆記 Android C2DM (一):元件參數說明
小鰻的Android學習筆記 Android Push Notification推播機制(1)-簡介篇
2012.7.3追記
這功能才完成沒多久,Google竟然給我宣佈訊息推播服務改新版GCM…
https://developers.google.com/android/c2dm/index?hl=zh-TW
新版GCM的心得文→[C#] 利用ASP.net和Windows Service實作Android手機的訊息推播(2012/6月底GCM版)