[C#] 利用ASP.net WebService和Windows Service實作Android手機的訊息推播(舊版C2DM)

[C#] 利用ASP.net WebService和Windows Service實作Android手機的訊息推播(舊版C2DM)

前言

Android前端+ .Net伺服器端的訊息推播(Push Notification),最近開發這功能差點開天窗,呵呵

還好有這篇:Android push notification implementation using ASP.NET and C#

不過流程說明應該有少寫(至少我看完還是寫不出來XD)

 

經過和App開發人員多次討論,終於弄懂詳細步驟

以下站在Server端角度做個說明

 

開發流程

image

 

先準備一個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:

image

Step 2:

加入System.Web.dll參考

image

image

Step 3:

在Android.cs中,using System.Web;

image

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

image

※安裝好Windows Service後,最好再從系統管理工具>服務 確認服務要被啟動

image

 

Server端開發完畢時,從CodePlex下載來的類別檔Android.cs的SendMessage函數裡有一行

postFieldNameValue.Add("data.message", Message);

要把data. 開頭的tag 告知App開發人員,這樣前端才知道訊息通知要顯示什麼訊息

 

 

執行結果:

(網路連線中才可收得到通知)

image

 

 

 

 

結語

當初要開發手機平台的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)-簡介篇

c2dm 使用心得总结  lytsing's Blog

 

 

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版)