1. 程式人生 > >構建Android Push Notification Service服務端及客戶端[含程式碼]

構建Android Push Notification Service服務端及客戶端[含程式碼]

終於又開始上班了,只有在值班的時候,才是我比較清閒的時候,可以靜下來做自己喜歡的事情,看自己喜歡的文章,寫自己喜歡的部落格。在Android架構部分,幾個比較難啃的骨頭裡面,Android Push Notification Service算一個。我想今天來解釋一下她的實現以及使用。

1 這個服務的必要性問題

在手機的使用過程中,我們知道,正睡覺呢,突然響起了簡訊聲,開啟一看,原來是移動/電信在提醒我們該上廁所了,或者天邊冷了,多穿點衣服吧之類的話語。而在使用Android手機的時候,我們發現,如果有Gmail端,收到郵件的時候,會彈出一個提示,你有一條新郵件,幷包含郵件的標題和相關資訊。不知道你會不會好奇,這是如何實現的呢?我很好奇,所以便有了此文的寫作動機。而對於QQ、安卓市場之類的軟體,時不時的也彈出來這類資訊,相信大家可以明白,這東西應該是有點用處的。比如我們開發一款應用,需要實時的提醒我們的安裝使用者一些事情,相信,你就會明白,這個服務是很有必要的,相信,在未來移動網際網路、物聯網佔據大片江山的時候,也是很有必要的。

2 幾個問題
好了,我們提出了這個東西的必要性,但是在做的時候,我們必須要考慮幾個問題。
2.1 俺的電池不怎麼抗用,可千萬別太耗我的電量啊,這是哥最在意的啊。
2.2 除了花點流量,這玩意不要花我另外的錢,我可是月光族啊。
2.3 我著急要收到這個訊息,別半小時後才把訊息發給我,那樣的話,會損失我的訂單的。
2.4 必須要可靠哦,別用著用著,不好使了。

秉著以上的幾個關鍵問題,我們開始了下一部分的探討了。

3 幾種可能的方案
我們來思考一下,要實現實時得到資訊,有哪幾種方法呢?
1 通過http/https或者其他協議,客戶端以服務的方式,每隔10分鐘或者10秒鐘,向伺服器請求一次,伺服器判斷這段時間是否有新訊息,需要發給客戶端,如果有就通過json或者xml方式發給客戶端。
2 通過簡訊的方式,伺服器端通過SMS的方式,將所需要的訊息及時傳送回來。
3 使用tcp長連線和心跳包的機制,實現資料定時推送。

4 採用的方案
從我的能力,我目前只能想到這麼幾種辦法,下面我們來根據第二條裡面的準則來分析上面提到的幾種方案。
第一條通過http或者https的方式,向伺服器每隔多長時間請求一次的方式,的確可以實現我們的功能,但是違反了我們的2.1和2.3原則。首先這種方式會耗電,當然你可以說時間設定長一點,但是這樣又違背了2.3原則。所以這條一般是不會被採納的。除非某些特殊應用。
第二條呢,2.1、2.3、2.4都符合,可是,違背了2.2,所以我們也不會考慮的。
第三條呢,好像全部符合,但是有一個小問題在裡面,就是如果以Service的方式進行,由於Android系統的特殊性,在記憶體不夠用的時候,會主動結束一些服務,這個服務包括了我們的定義服務,這麼說,他違背了2.4。

但是,我們還是有辦法的。

5 被採用方案的可實施方法
在Android 2.2以後,Google放出了C2DM【Android Cloud to Device Messaging Framework】服務,從服務的使用方法上,我們就可以明白他們採用了第三種方式。
隨著他們推出這個服務後,很多公司開始基於這個服務做一些應用,如推送廣告、推送定製資訊等。如xtifyairpush等,國內也有一些企業加入了這種陣營,如單獨提供服務的push-notification,當然QQ也有這樣的服務存在。

在這種方案裡面,有幾個細節地方,需要來解釋一下。
5.1 傳輸的時候使用什麼協議?
5.2 傳輸的時候如何保證資料的安全性?
5.3 對於多平臺,多使用者的push如何保證惟一性?
5.4 伺服器端的如何部署?

5.1的問題目前有幾種方式,使用xmpp協議、IBM的MQTT、自定義協議。 目前有一些開源的專案中,大都採用第一種和第二種,當然,如果有特殊需求,可以採取自定義協議的。
5.2的問題可以對資料進行可逆加密。
5.3的問題,一般是將手機的ID傳遞到伺服器端進行惟一性驗證。
5.4的問題,伺服器端可以自己使用任何語言開發,也可以使用Nginx + 指令碼語言部署。

6 例項說明
本文的例項採用了mqtt的架構,完全按照tokudu兄的文章而來,併成功實現了。裡面採取的不是IBM的Really Small Message Broker,而是採用的開源Mosquitto實現,

準備工作:

6.1 Android真機,本文為三星I809
6.2 Apache + Php環境
6.3 tokudu兄的Android原始碼
6.4 tukudu兄的php程式碼
6.5 mosquitto的可執行程式。

步驟1:
下載mosquitto的可執行程式,我選擇的是cygwin版本的,安裝後,進入目錄雙擊mosquitto.exe執行即可。

步驟2:下載tokudu兄的php程式碼,官方地址為:https://github.com/tokudu/PhpMQTTClient
我這裡也提供下載:androidpushservice

主要程式碼為如下:

  1. <?php  
  2. require('SAM/php_sam.php');  
  3. //create a new connection object
  4. $conn = new SAMConnection();  
  5. //start initialise the connection
  6. $conn->connect(SAM_MQTT, array(SAM_HOST => '202.198.21.131',  
  7.                                SAM_PORT => 1883));  
  8. //create a new MQTT message with the output of the shell command as the body
  9. $msgCpu = new SAMMessage($_REQUEST['message']);  
  10. //send the message on the topic cpu
  11. $conn->send('topic://'.$_REQUEST['target'], $msgCpu);  
  12. $conn->disconnect();           
  13. echo 'MQTT Message to ' . $_REQUEST['target'] . ' sent: ' . $_REQUEST['message'];   
  14. ?>  

將程式碼部署到php環境目錄裡面。輸入地址:http://localhost/androidpushservice/

步驟三:下載tokudu兄的android程式碼:
地址:https://github.com/tokudu/AndroidPushNotificationsDemo
本文提供下載:
tokudu-AndroidPushNotificationsDemo-ea18b09

匯入專案,編譯,在真機上面使用開啟即可。

這裡有一個Device Target號碼需要在php的介面裡面輸入。才可以傳送成功。

  1. /* 
  2. * $Id$ 
  3. */
  4. package com.tokudu.demo;  
  5. import java.io.BufferedWriter;  
  6. import java.io.File;  
  7. import java.io.FileWriter;  
  8. import java.io.IOException;  
  9. import java.io.Writer;  
  10. import java.text.SimpleDateFormat;  
  11. import java.util.Date;  
  12. import android.os.Environment;  
  13. publicclass ConnectionLog  
  14. {  
  15. private String mPath;  
  16. private Writer mWriter;  
  17. privatestaticfinal SimpleDateFormat TIMESTAMP_FMT =  
  18. new SimpleDateFormat("[HH:mm:ss] ");  
  19. public ConnectionLog()  
  20. throws IOException  
  21. {  
  22. File sdcard = Environment.getExternalStorageDirectory();  
  23. File logDir = new File(sdcard, "tokudu/log/");  
  24. if (!logDir.exists()) {  
  25. logDir.mkdirs();  
  26. // do not allow media scan
  27. new File(logDir, ".nomedia").createNewFile();  
  28. }  
  29. open(logDir.getAbsolutePath() + "/push.log");  
  30. }  
  31. public ConnectionLog(String basePath)  
  32. throws IOException  
  33. {  
  34. open(basePath);  
  35. }  
  36. protectedvoid open(String basePath)  
  37. throws IOException  
  38. {  
  39. File f = new File(basePath + "-" + getTodayString());  
  40. mPath = f.getAbsolutePath();  
  41. mWriter = new BufferedWriter(new FileWriter(mPath), 2048);  
  42. println("Opened log.");  
  43. }  
  44. privatestatic String getTodayString()  
  45. {  
  46. SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-hhmmss");  
  47. return df.format(new Date());  
  48. }  
  49. public String getPath()  
  50. {  
  51. return mPath;  
  52. }  
  53. publicvoid println(String message)  
  54. throws IOException  
  55. {  
  56. mWriter.write(TIMESTAMP_FMT.format(new Date()));  
  57. mWriter.write(message);  
  58. mWriter.write('\n');  
  59. mWriter.flush();  
  60. }  
  61. publicvoid close()  
  62. throws IOException  
  63. {  
  64. mWriter.close();  
  65. }  
  66. }  
  1. <span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;">  
  2. </span></span>  
  1. <span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;"></span></span><pre name="code"class="java">package com.tokudu.demo;  
  2. import java.io.IOException;  
  3. import com.ibm.mqtt.IMqttClient;  
  4. import com.ibm.mqtt.MqttClient;  
  5. import com.ibm.mqtt.MqttException;  
  6. import