用Socket.io打造你的聊天和消息推送

你還在用第三方的框架實現(xiàn)聊天和消息推送?快快試試吧


Socket.IO(官網(wǎng))介紹

是一個跨平臺的聊天框架董栽,可以實現(xiàn) web 端和移動端的實時聊天企孩,簡單說就是用來做聊天和消息推送的勿璃。最初以為項目做消息推送會直接使用第三方的推汽,比如小米歹撒、極光啥的暖夭,但是撵孤,架構(gòu)師說我們不用第三方的邪码,自己要搭建消息推送咬清,Socket.io技術(shù)很成熟了旧烧。所以就這樣開始了Socket.io的學習之路掘剪。第一次接觸Socket.io以為很難做,因為不了解肆汹,加上能找的資源有限(上網(wǎng)查找的都是英文資料昂勉,有關(guān)于Android端的Socke.io更是少之又少扫腺,除非你去stackoverflow里面去找笆环,英語還得好)躁劣,所以寫篇文章記錄下實現(xiàn)該功能流程以及遇到的問題。
官方Demo志膀,Github的Demo溉浙,這兩個 demo 都是一個聊天室蒋荚,可以在里面聊天,剛開始弄這個的時候發(fā)現(xiàn)有好多哥們在那里期升,還找?guī)讉€一起在做聊天功能的小伙伴,快快下載試試吧吨铸。

本文不說服務(wù)端的搭建和web端的實現(xiàn)祖秒,只是來說說 Android 端如何使用Socket.io實現(xiàn)消息推送功能竭缝。

(1)導包

Android Studio 導包抬纸,一共有兩種情況(PS: Eclipse 用戶也不要哭,下面會教你怎樣獲取到 JAR

  1. 第一種情況 compile 'com.github.nkzawa:socket.io-client:0.3.0'

  2. 第二種情況

     compile ('io.socket:socket.io-client:0.7.0') {
       // excluding org.json which is provided by Android
       exclude group: 'org.json', module: 'json'
     }
    

注意:兩個包的區(qū)別,如果你的項目沒有用到 Https ,那么你可以使用兩個當中的一個脖阵。如果有用到 Https 墅茉,那么你就要用二個包就斤,不然你會連接不上 Https 的,具體的連接方式坠宴,以下會介紹啄踊。

第一種情況的導包獲取到的 JAR 形式

第二種情況的導包獲取到的 JAR 形式

PSAndroid Studio 用戶直接跳過(如果你想看看你的Studio下載的 JAR 放在那里也可以看看)址晕。Eclipse 用戶獲取 Jar,如果你會去遠程倉庫下載 Jar启搂,那么你就去吧胳赌,也就是幾個 JAR 而已疑苫,不然的話你還是得借助 Android Studio 來獲取 Jar

用Studio導入包后找到你的 External Libraries ,選中你的 JAR 撼短,如 engine.io-client-0.7.0 點擊右鍵曲横,點擊 Library Properties ,會彈出一個對話框不瓶,Copy 這個URL蚊丐,打開我的電腦吠撮,粘貼到導航欄點擊確定就可以看到你的 JAR了泥兰,但是,這個 JAR 是一個資源文件的 JAR ,里面有源碼的膀捷,我們不需要這個,點擊back后退削彬,一般會有三個文件夾全庸,其中的一個就是你需要的 JAR了。流程圖如下:



(2)代碼使用

導完包剩下的就是代碼的使用了融痛。由于 Socket.io 封裝得很好壶笼,所以我們能用到的類和方法不多,也就幾個而已雁刷。

  • 獲取 Socket 和設(shè)置 url : mSocket = IO.socket( "http://192.168.205.125:10443" );
  • 連接 mSocket.mSocket.connect();覆劈。
  • 發(fā)送消息 mSocket.emit( "newMessage", data );,這里需要注意的是: data 是 JSONObject 的類责语。
  • 消息監(jiān)聽 mSocket.on( Socket.EVENT_CONNECT, onConnect );// 連接成功
  • 斷開連接 mSocket.disconnect();炮障。
  • 斷開消息監(jiān)聽 mSocket.off( Socket.EVENT_CONNECT, onConnect );

就是這么幾個方法就可以實現(xiàn)消息推送或者實現(xiàn)聊天了。由于本文主要是實現(xiàn)消息推送的功能坤候,所以把主要的代碼放在了 Service胁赢,順便把流程寫一下,方便初學者學習白筹。

    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.support.annotation.Nullable;
    import android.util.Log;
    
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import java.net.URISyntaxException;
    import java.security.KeyManagementException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    import io.socket.client.IO;
    import io.socket.client.Socket;
    import io.socket.emitter.Emitter;
    
    public class MessagePushService extends Service {
        
        private static final String TAG = MessagePushService.class.getSimpleName();
        
        private Socket mSocket;
        
        private boolean isConnected;
        
        /**
         * 初始化Socket,Https的連接方式
         */
        private void initSocketHttps() {
            SSLContext sc = null;
            TrustManager[] trustCerts = new TrustManager[] { new X509TrustManager() {
                
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                
                @Override
                public void checkServerTrusted( X509Certificate[] chain, String authType )
                    throws CertificateException {
                }
                
                @Override
                public void checkClientTrusted( X509Certificate[] chain, String authType )
                    throws CertificateException {
                    
                }
            } };
            try {
                sc = SSLContext.getInstance( "TLS" );
                sc.init( null, trustCerts, null );
                IO.Options opts = new IO.Options();
                opts.sslContext = sc;
                opts.hostnameVerifier = new HostnameVerifier() {
                    @Override
                    public boolean verify( String s, SSLSession sslSession ) {
                        return true;
                    }
                };
                mSocket = IO.socket( "https://192.168.205.125:10443", opts );
            } catch ( NoSuchAlgorithmException e ) {
                e.printStackTrace();
            } catch ( KeyManagementException e ) {
                e.printStackTrace();
            } catch ( URISyntaxException e ) {
                e.printStackTrace();
            }
        }
        
        /**
         * 初始化Socket,Http的連接方式
         */
        private void initSocketHttp() {
            try {
                mSocket = IO.socket( "http://192.168.205.125:10443" ); // 初始化Socket
            } catch ( URISyntaxException e ) {
                e.printStackTrace();
            }
        }
        
        private void connectSocket() {
            try {
                mSocket.connect();
                JSONObject jsonObject = new JSONObject();
                jsonObject.put( "userName", "小王" ); // 這里一般是設(shè)置登錄名
                mSocket.emit( "loginName", jsonObject ); // 發(fā)送登錄人
            } catch ( JSONException e ) {
                e.printStackTrace();
            }
            
            mSocket.on( Socket.EVENT_CONNECT, onConnect );// 連接成功
            mSocket.on( Socket.EVENT_DISCONNECT, onDisconnect );// 斷開連接
            mSocket.on( Socket.EVENT_CONNECT_ERROR, onConnectError );// 連接異常
            mSocket.on( Socket.EVENT_CONNECT_TIMEOUT, onConnectTimeoutError );// 連接超時
            mSocket.on( "newMessage", onConnectMsg );// 監(jiān)聽消息事件回調(diào)
        }
        
        private void disConnectSocket() {
            mSocket.disconnect();
            
            mSocket.off( Socket.EVENT_CONNECT, onConnect );// 連接成功
            mSocket.off( Socket.EVENT_DISCONNECT, onDisconnect );// 斷開連接
            mSocket.off( Socket.EVENT_CONNECT_ERROR, onConnectError );// 連接異常
            mSocket.off( Socket.EVENT_CONNECT_TIMEOUT, onConnectTimeoutError );// 連接超時
            mSocket.off( "newMessage", onConnectMsg );// 監(jiān)聽消息事件回調(diào)
        }
        
        private Emitter.Listener onConnectMsg = new Emitter.Listener() {
            @Override
            public void call( final Object... args ) {
                // 在這里處理你的消息
                Log.e( TAG, "服務(wù)器返回來的消息 : " + args[0] );
            }
        };
        
        /**
         * 實現(xiàn)消息回調(diào)接口
         */
        private Emitter.Listener onConnect = new Emitter.Listener() {
            @Override
            public void call( final Object... args ) {
                Log.e( TAG, "連接成功 " + args[0] );
                if (!isConnected) { // 如果已經(jīng)斷開智末,重新發(fā)送
                    try {
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put( "userName", "小王" ); // 這里一般是設(shè)置登錄名
                        mSocket.emit( "loginName", jsonObject ); // 發(fā)送登錄人
                    } catch ( JSONException e ) {
                        e.printStackTrace();
                    }
                    isConnected = true;
                }
            }
        };
        
        private Emitter.Listener onDisconnect = new Emitter.Listener() {
            @Override
            public void call( Object... args ) {
                Log.e( TAG, "斷開連接 " + args[0] );
                isConnected = false;
            }
        };
        
        private Emitter.Listener onConnectError = new Emitter.Listener() {
            @Override
            public void call( final Object... args ) {
                Log.e( TAG, "連接 失敗" + args[0] );
            }
        };
        
        private Emitter.Listener onConnectTimeoutError = new Emitter.Listener() {
            @Override
            public void call( final Object... args ) {
                Log.e( TAG, "連接 超時" + args[0] );
                
            }
        };
        
        @Nullable
        @Override
        public IBinder onBind( Intent intent ) {
            return null;
        }
    }
    

就這樣完成了一個消息推送,不需要集成什么東西遍蟋,也不用受制于人吹害。

總結(jié):剛開始做這個的時候找不到各種 JAR (項目還是在使用Eclipse開發(fā)中,所以還是需要去找到 JAR)虚青,那會自己才倒騰 Android Studio 沒多久它呀,把 Demo 下載下來運行沒問題,然后就去找 這個資源的 JAR棒厘,準備集成到 項目里面去纵穿,一開始以為只要 engine.io-client JAR 和 socket.io-client JAR ,結(jié)果就報錯了奢人,然后不斷的查資料找包(這時候還不知道 Android Studio 已經(jīng)把JAR 下載到電腦了)谓媒,最后才知道 Android Studio 下載的JAR在哪里(這才有了前面的找JAR),等到所有的JAR找到后何乎,集成到了項目里面去句惯,最后還是不斷的報 NoClassDefFoundError ,把這些JAR全部放到 Android Studio 集成運行卻沒有問題支救,這才發(fā)現(xiàn)是我的 Eclipse 問題抢野,等找到這些原因,時間已經(jīng)過去兩天了各墨,悲劇呀指孤,然后快速的集成到項目,最終運行一切Ok啦贬堵!但是恃轩,如果你覺得故事就這樣沒了,那就太沒意思了黎做,這個時候公司的接口全部轉(zhuǎn)成 https 叉跛,悲劇呀,項目運行不起來了蒸殿∶粱ィ花時間找源碼挽铁,看看怎樣實現(xiàn),最終在stackoverflow找到了關(guān)于加載證書的方式敞掘,但是翻源碼的時候卻發(fā)現(xiàn)沒有這個 opts.hostnameVerifier 屬性,再一次悲勯固(這個時候玖雁,我用的是上面的第一種JAR,也就是官網(wǎng)介紹的: compile 'com.github.nkzawa:socket.io-client:0.3.0')盖腕。這個時候時候發(fā)現(xiàn)了 Socket.io 官網(wǎng)的介紹和 Github 的介紹是兩種不同的 JAR赫冬,最后導入 githubJAR(第二種導包方式,所以你還是直接用這個包吧) 確實可以運行了溃列。滿滿的坑呀劲厌!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市听隐,隨后出現(xiàn)的幾起案子补鼻,更是在濱河造成了極大的恐慌,老刑警劉巖雅任,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件风范,死亡現(xiàn)場離奇詭異,居然都是意外死亡沪么,警方通過查閱死者的電腦和手機硼婿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禽车,“玉大人寇漫,你說我怎么就攤上這事⊙乘ぃ” “怎么了州胳?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钦勘。 經(jīng)常有香客問我陋葡,道長,這世上最難降的妖魔是什么彻采? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任腐缤,我火速辦了婚禮,結(jié)果婚禮上肛响,老公的妹妹穿的比我還像新娘岭粤。我一直安慰自己,他們只是感情好特笋,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布剃浇。 她就那樣靜靜地躺著巾兆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虎囚。 梳的紋絲不亂的頭發(fā)上角塑,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音淘讥,去河邊找鬼圃伶。 笑死,一個胖子當著我的面吹牛蒲列,可吹牛的內(nèi)容都是我干的窒朋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蝗岖,長吁一口氣:“原來是場噩夢啊……” “哼侥猩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抵赢,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤欺劳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后瓣俯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杰标,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年彩匕,在試婚紗的時候發(fā)現(xiàn)自己被綠了腔剂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡驼仪,死狀恐怖掸犬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绪爸,我是刑警寧澤湾碎,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站奠货,受9級特大地震影響介褥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜递惋,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一柔滔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萍虽,春花似錦睛廊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咆霜。三九已至,卻和暖如春嘶朱,著一層夾襖步出監(jiān)牢的瞬間蛾坯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工疏遏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留偿衰,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓改览,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缤言。 傳聞我的和親對象是個殘疾皇子宝当,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容