構建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】服務,從服務的使用方法上,我們就可以明白他們採用了第三種方式。
隨著他們推出這個服務後,很多公司開始基於這個服務做一些應用,如推送廣告、推送定製資訊等。如xtify和airpush等,國內也有一些企業加入了這種陣營,如單獨提供服務的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
主要程式碼為如下:
- <?php
- require('SAM/php_sam.php');
- //create a new connection object
- $conn = new SAMConnection();
- //start initialise the connection
- $conn->connect(SAM_MQTT, array(SAM_HOST => '202.198.21.131',
- SAM_PORT => 1883));
- //create a new MQTT message with the output of the shell command as the body
- $msgCpu = new SAMMessage($_REQUEST['message']);
- //send the message on the topic cpu
- $conn->send('topic://'.$_REQUEST['target'], $msgCpu);
- $conn->disconnect();
- echo 'MQTT Message to ' . $_REQUEST['target'] . ' sent: ' . $_REQUEST['message'];
- ?>
將程式碼部署到php環境目錄裡面。輸入地址:http://localhost/androidpushservice/
步驟三:下載tokudu兄的android程式碼:
地址:https://github.com/tokudu/AndroidPushNotificationsDemo
本文提供下載:
tokudu-AndroidPushNotificationsDemo-ea18b09
這裡有一個Device Target號碼需要在php的介面裡面輸入。才可以傳送成功。
- /*
- * $Id$
- */
- package com.tokudu.demo;
- import java.io.BufferedWriter;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.io.Writer;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import android.os.Environment;
- publicclass ConnectionLog
- {
- private String mPath;
- private Writer mWriter;
- privatestaticfinal SimpleDateFormat TIMESTAMP_FMT =
- new SimpleDateFormat("[HH:mm:ss] ");
- public ConnectionLog()
- throws IOException
- {
- File sdcard = Environment.getExternalStorageDirectory();
- File logDir = new File(sdcard, "tokudu/log/");
- if (!logDir.exists()) {
- logDir.mkdirs();
- // do not allow media scan
- new File(logDir, ".nomedia").createNewFile();
- }
- open(logDir.getAbsolutePath() + "/push.log");
- }
- public ConnectionLog(String basePath)
- throws IOException
- {
- open(basePath);
- }
- protectedvoid open(String basePath)
- throws IOException
- {
- File f = new File(basePath + "-" + getTodayString());
- mPath = f.getAbsolutePath();
- mWriter = new BufferedWriter(new FileWriter(mPath), 2048);
- println("Opened log.");
- }
- privatestatic String getTodayString()
- {
- SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-hhmmss");
- return df.format(new Date());
- }
- public String getPath()
- {
- return mPath;
- }
- publicvoid println(String message)
- throws IOException
- {
- mWriter.write(TIMESTAMP_FMT.format(new Date()));
- mWriter.write(message);
- mWriter.write('\n');
- mWriter.flush();
- }
- publicvoid close()
- throws IOException
- {
- mWriter.close();
- }
- }
- <span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;">
- </span></span>
- <span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;"></span></span><pre name="code"class="java">package com.tokudu.demo;
- import java.io.IOException;
- import com.ibm.mqtt.IMqttClient;
- import com.ibm.mqtt.MqttClient;
- import com.ibm.mqtt.MqttException;
- import