廣而告之
使用socket.io作為跨瀏覽器平臺(tái)的實(shí)時(shí)推送首選,經(jīng)測(cè)試在各個(gè)主流瀏覽器上測(cè)試都確實(shí)具有良好的下實(shí)時(shí)表現(xiàn)。這里為推廣socketio-netty服務(wù)器端實(shí)現(xiàn)哈,做次廣告,同時(shí)預(yù)熱一下:
socketio-netty : 又一款socket.io服務(wù)器端實(shí)現(xiàn),兼容0.9-1.0版本~
示范目的
我們要構(gòu)建一個(gè)在市面上常見瀏覽器上都可以正常運(yùn)行的集體聊天應(yīng)用,保證在IE6+,F(xiàn)irefox,Chrome,Safari,Opear,IOS,Android等可以正常運(yùn)轉(zhuǎn),根據(jù)具體環(huán)境自動(dòng)選擇最佳的通信通道。
嗯,既然是跨瀏覽器平臺(tái),那自然選擇socket.io(客戶端js) 了。它也是本文的重心,本文的最終是讓socket.io(客戶端js) 版本在Phonegap包裝的Android Apk程序中可以使用Websocket協(xié)議,以達(dá)到快速交換數(shù)據(jù)的目的,提高交換性能。
同時(shí)也保證我們的示范應(yīng)用盡可能的做到編寫一次,到處運(yùn)行哈。
還好,有了socket.io(客戶端js) + socketio-netty(socket.io服務(wù)器端JAVA實(shí)現(xiàn)) + Phonegap, 我們構(gòu)建各種交互性非常強(qiáng)的的跨瀏覽器、跨移動(dòng)應(yīng)用的HTML應(yīng)用,是個(gè)不錯(cuò)的選擇。
另,本文示范項(xiàng)目為僅僅為演示其功能,不保證樣式。
Phonegap
官網(wǎng)
官網(wǎng)定義為:
PhoneGap is an HTML5 app platform that allows you to author native applications with web technologies and get access to APIs and app stores.
中文翻譯為:
PhoneGap是一個(gè)開源的開發(fā)框架,使用HTML,CSS和JavaScript來構(gòu)建跨平臺(tái)的的移動(dòng)應(yīng)用程序
可使用HTML + CSS + Javascript構(gòu)建跨平臺(tái)的移動(dòng)引用,確實(shí)很不錯(cuò),值得推薦。
更棒的,可以利用其云構(gòu)建服務(wù)(https://build.phonegap.com/apps ) ,本機(jī)編寫好應(yīng)用之后(保證首頁為index.html,涉及到的css/js存放在一起),打包成zip,上傳,自動(dòng)會(huì)為我們自動(dòng)構(gòu)建不同平臺(tái)下的部署包,十分方便。
表面上看,一切都很完美,但部署到Android系統(tǒng)之后,發(fā)現(xiàn)socket.io無法使用websocket雙向的通道,socket.io默認(rèn)采用xhr-long polling通信模式。有些無奈。
在實(shí)時(shí)交互數(shù)據(jù)量很大的情況下,相比xhr-long polling, jsonp polling,Websocket/Flashsocket具有無法超越的速度優(yōu)勢(shì),同時(shí)雙向數(shù)據(jù)傳輸通道,通過觀察可以很明顯的感覺到。
起因
- 我的android系統(tǒng)是2.3的,其原生的瀏覽器不支持websocket通信協(xié)議(ucweb,qq,opear mini 等都支持較為完整的HTML5規(guī)范)。
- Phonegap轉(zhuǎn)換的APK包,會(huì)調(diào)用android內(nèi)置瀏覽器,因此導(dǎo)致websocket無法使用。
- 據(jù)調(diào)研Android 2/3.* 原生瀏覽器不支持websocket,至于Android 4.*,沒有測(cè)試過。
如何確認(rèn)瀏覽器對(duì)html5的支持情況, 瀏覽器訪問 http://html5test.com 即可查詢對(duì)HTML5的支持情況,以及跑分等。
嗯,據(jù)傳言,Phonegap會(huì)在2.0版本之后,添加對(duì)Android的websocket支持,但目前版本為1.7。
解決方式
animesh kumar 開發(fā)的websocket-android-phonegap項(xiàng)目,已經(jīng)做到了讓Phonegap支持websocket客戶端協(xié)議,使用java nio編寫websocket客戶端協(xié)議連接,同時(shí)Phonegap支持自定義組件,支持JS和JAVA代碼的相互調(diào)用開放架構(gòu),這樣就促成了偽裝的webscoket.js。
其有些DWR的味道,但更為靈活。
另外還有一個(gè)單純的socket.io android客戶端實(shí)現(xiàn):
有興趣者,可以參考一下。
本打算使用Netty構(gòu)建一個(gè)websocket客戶端,然后結(jié)合js等,但有開源實(shí)現(xiàn),不再閉門造車。
- 在Eclipse中新建Android Project項(xiàng)目chatdemo
- 把animesh kumar的websocket-android-phonegap項(xiàng)目java文件打成phonegap-websocket-support.jar包,存放在 android project的libs目錄下
- 把websocket.js存放在 assets/www/js目錄下
- 修改項(xiàng)目啟動(dòng)類App.java
- 添加<script src="js/websocket.js"></script>
簡單說明
1. App.java修改后:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
package com.yongboy.chatdemo; |
|
|
|
import org.apache.cordova.DroidGap; |
|
|
|
import android.os.Bundle; |
|
|
|
import com.strumsoft.websocket.phonegap.WebSocketFactory; |
|
|
|
/** |
|
* |
|
* @author yongboy |
|
* @time 2012-5-10 |
|
* @version 1.0 |
|
*/ |
|
public class App extends DroidGap { |
|
|
|
@Override |
|
public void onCreate(Bundle savedInstanceState) { |
|
super.onCreate(savedInstanceState); |
|
super.loadUrl("file:///android_asset/www/index.html"); |
|
|
|
// 绑定websocket支持 |
|
appView.addJavascriptInterface(new WebSocketFactory(appView), |
|
"WebSocketFactory"); |
|
} |
|
} |
確保繼承DroidGap,并且指定綁定語句:
// 綁定websocket支持
appView.addJavascriptInterface(new WebSocketFactory(appView),
"WebSocketFactory");
JAVA端設(shè)定完畢。
2. 客戶端的修改
需要在html頁面端做些手腳,優(yōu)先加載websocket.js進(jìn)行一些環(huán)境變量的設(shè)定,這樣socket.io就可以檢測(cè)websocket的支持。
websocekt.js的初始化代碼:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
(function() { |
|
// window object |
|
var global = window; |
|
|
|
// WebSocket Object. All listener methods are cleaned up! |
|
var WebSocket = global.WebSocket = function(url) { |
|
// get a new websocket object from factory (check com.strumsoft.websocket.WebSocketFactory.java) |
|
this.socket = WebSocketFactory.getInstance(url); |
|
// store in registry |
|
if(this.socket) { |
|
WebSocket.store[this.socket.getId()] = this; |
|
} else { |
|
throw new Error('Websocket instantiation failed! Address might be wrong.'); |
|
} |
|
}; |
|
|
|
// storage to hold websocket object for later invokation of event methods |
|
WebSocket.store = {}; |
|
|
|
// static event methods to call event methods on target websocket objects |
|
WebSocket.onmessage = function (evt) { |
|
WebSocket.store[evt._target]['onmessage'].call(global, evt); |
|
} |
|
|
|
WebSocket.onopen = function (evt) { |
|
WebSocket.store[evt._target]['onopen'].call(global, evt); |
|
} |
|
|
|
WebSocket.onclose = function (evt) { |
|
WebSocket.store[evt._target]['onclose'].call(global, evt); |
|
} |
|
|
|
WebSocket.onerror = function (evt) { |
|
WebSocket.store[evt._target]['onerror'].call(global, evt); |
|
} |
|
|
|
// instance event methods |
|
WebSocket.prototype.send = function(data) { |
|
this.socket.send(data); |
|
} |
|
|
|
WebSocket.prototype.close = function() { |
|
this.socket.close(); |
|
} |
|
|
|
WebSocket.prototype.getReadyState = function() { |
|
this.socket.getReadyState(); |
|
} |
|
///////////// Must be overloaded |
|
WebSocket.prototype.onopen = function(){ |
|
throw new Error('onopen not implemented.'); |
|
}; |
|
|
|
// alerts message pushed from server |
|
WebSocket.prototype.onmessage = function(msg){ |
|
throw new Error('onmessage not implemented.'); |
|
}; |
|
|
|
// 其它原型函数,省略...... |
|
})(); |
需要注意其初始化代碼:
// window object
var global = window;
// WebSocket Object. All listener methods are cleaned up!
var WebSocket = global.WebSocket = function(url)
......
socket.io client websocket 代碼片段:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
(function (exports, io, global) { |
|
exports.websocket = WS; |
|
|
|
function WS (socket) { |
|
io.Transport.apply(this, arguments); |
|
}; |
|
|
|
io.util.inherit(WS, io.Transport); |
|
|
|
WS.prototype.name = 'websocket'; |
|
|
|
WS.prototype.open = function () { |
|
var query = io.util.query(this.socket.options.query) |
|
, self = this |
|
, Socket |
|
|
|
if (!Socket) { |
|
Socket = global.MozWebSocket || global.WebSocket; |
|
} |
|
|
|
this.websocket = new Socket(this.prepareUrl() + query); |
|
|
|
this.websocket.onopen = function () { |
|
self.onOpen(); |
|
self.socket.setBuffer(false); |
|
}; |
|
|
|
this.websocket.onmessage = function (ev) { |
|
self.onData(ev.data); |
|
}; |
|
|
|
this.websocket.onclose = function () { |
|
self.onClose(); |
|
self.socket.setBuffer(true); |
|
}; |
|
|
|
this.websocket.onerror = function (e) { |
|
self.onError(e); |
|
}; |
|
|
|
return this; |
|
}; |
|
|
|
WS.prototype.send = function (data) { |
|
this.websocket.send(data); |
|
return this; |
|
}; |
|
|
|
WS.prototype.payload = function (arr) { |
|
for (var i = 0, l = arr.length; i < l; i++) { |
|
this.packet(arr[i]); |
|
} |
|
return this; |
|
}; |
|
|
|
WS.prototype.close = function () { |
|
this.websocket.close(); |
|
return this; |
|
}; |
|
|
|
WS.prototype.onError = function (e) { |
|
this.socket.onError(e); |
|
}; |
|
|
|
WS.prototype.scheme = function () { |
|
return this.socket.options.secure ? 'wss' : 'ws'; |
|
}; |
|
|
|
WS.check = function () { |
|
return ('WebSocket' in global && !('__addTask' in WebSocket)) |
|
|| 'MozWebSocket' in global; |
|
}; |
|
|
|
WS.xdomainCheck = function () { |
|
return true; |
|
}; |
|
|
|
io.transports.push('websocket'); |
|
})( |
|
'undefined' != typeof io ? io.Transport : module.exports |
|
, 'undefined' != typeof io ? io : module.parent.exports |
|
, this |
|
); |
看一下websocket的檢測(cè)函數(shù):
WS.check = function () {
return ('WebSocket' in global && !('__addTask' in WebSocket))
|| 'MozWebSocket' in global;
};
很自然的,自定義的websocket.js 和 socket.io兩者就能夠很自然的銜接在一起了。
因此,必須的頁面JS加載順序?yàn)椋?/p>
<!--android平臺(tái)需要添加,其它移動(dòng)平臺(tái),比如ios則不需要 -->
<!--一定要放在socket.io.min.js前面 -->
<script src="js/websocket.js"></script>
<script src="js/socket.io.min.js"></script>
在HTML頁面端,我們僅僅需要添加一行<script src="js/websocket.js"></script>引用,就做到了讓android平臺(tái)下socket.io優(yōu)先選擇websocket,很簡單,也很使用。
至于其它平臺(tái),則不需要考慮這么,僅僅把/chatdemo/assets/www目錄下打包成zip包(切記把<script src="js/websocket.js"></script>去除掉),上傳到云構(gòu)建平臺(tái)自動(dòng)構(gòu)建即可。
小總結(jié)
Phonegap下讓android平臺(tái)支持websocket,步驟很簡單:
- 在eclipse下搭建android project
- 拷貝jar以及socekt.js到相應(yīng)目錄
- 修改App.java(其它android啟動(dòng)類,方法名不一樣,但方法體一致)
- 在首頁或者需要的頁面,在 socket.io js醫(yī)用的前面,添加<script src="js/websocket.js"></script>引用即可
示范代碼
- socket.io 框架內(nèi)置的chat聊天示范和socketio-netty所提供聊天示范完全一致,除了服務(wù)器端實(shí)現(xiàn)不同
- 簡單包裝成android項(xiàng)目,僅用于演示使用,因此界面有些大
- 需要一個(gè)服務(wù)器端,socket.io或者socketio-netty的都可以
- 本文Android示范chat下載 下載
- phonegap-websocket-support.jar
參考資料
- Phonegap:
http://phonegap.com/
-
- websocket-android-phonegap
https://github.com/anismiles/websocket-android-phonegap