https://github.com/coderMyy/MYCoreTextLabel 圖文混排 , 實現(xiàn)圖片文字混排 , 可顯示常規(guī)鏈接比如網(wǎng)址,@,話題等 , 可以自定義鏈接字,設(shè)置關(guān)鍵字高亮等功能 . 適用于微博,微信,IM聊天對話等場景 . 實現(xiàn)這些功能僅用了幾百行代碼队寇,耦合性也較低
https://github.com/coderMyy/MYDropMenu 上拉下拉菜單藻三,可隨意自定義富弦,隨意修改大小,位置确沸,各個項目通用
https://github.com/coderMyy/MYPhotoBrowser 照片瀏覽器。功能主要有 : 點擊點放大縮小 , 長按保存發(fā)送給好友操作 光坝, 帶文本描述照片,從點擊照片放大圾笨,當前瀏覽照片縮小等功能教馆。功能逐漸完善增加中.
https://github.com/coderMyy/MYNavigationController 導(dǎo)航控制器的壓縮 , 使得可以將導(dǎo)航范圍縮小到指定區(qū)域 , 實現(xiàn)頁面中的頁面效果 . 適用于路徑選擇,文件選擇等
pragma mark - ---------大體思路羅列
/*
以下思路 , 僅表個人意見 , 且是我能想到比較好的處理方法
=====================================================================================
<<< 大致代碼結(jié)構(gòu) >>>
GCDSocket
(提供最原始的寫入,讀取,超時等方法)
delegate : ChatHandler單例
怎么去發(fā)送消息,接收消息的實際操作者
||
?
ChatHanlder
(作為中間業(yè)務(wù)邏輯處理層 , 主要將發(fā)送消息,接收消息和各個實際接收的控制器們連接起來,數(shù)據(jù)緩存,處理等)
delegate : 需要接收消息的所有對象 (注冊成為ChatHandler的代理,ChatHandler中數(shù)組對各個對象進行存儲)
數(shù)據(jù)處理(數(shù)據(jù)庫以及沙盒),數(shù)據(jù)緩存(數(shù)據(jù)庫以及沙盒),消息發(fā)送接收業(yè)務(wù)邏輯處理實際操作者
|| || ||
? ? ?
ViewController1 ViewController2 .........
控制器里需要做的 , 是對內(nèi)存中消息模型進行操作 , 以此更新UI . 比如進度顯示 , 失敗紅嘆號顯示 , 轉(zhuǎn)圈 , 消息刪除 , 撤回等 ...
這么設(shè)計的好處 :
1. 分工明確 , 每個控制器得到的消息 , 都是可以直接使用的 , 控制器只需要負責(zé)主要和V層進行交互 , 避免了在控制器中處理過多的邏輯
2. ChatHandler作為全局單例 , 生命周期和整個應(yīng)用保持一致 . 而控制器 , 會隨著用戶操作而銷毀 , 如果把數(shù)據(jù)放到控制器里處理 , 很可能會造成數(shù)據(jù)的丟失
=====================================================================================
<<< 關(guān)于緩存結(jié)構(gòu) >>>
文本/表情消息 語音消息 圖片消息 視頻消息 文件消息 撤回消息 提示語消息
|| || || || || || ||
? ? ? ? ? ? ?
數(shù)據(jù)庫存儲 數(shù)據(jù)庫存儲 數(shù)據(jù)庫存儲 數(shù)據(jù)庫存儲 數(shù)據(jù)庫存儲 數(shù)據(jù)庫存儲 數(shù)據(jù)庫存儲
|| || ||
沙盒緩存(語音data) 沙盒緩存(圖片data) 沙盒緩存(視頻)
===================================================================================
<<<<Socket連接>>>>
登錄 -> 連接服務(wù)器端口 -> 成功連接 -> SSL驗證 -> 發(fā)送登錄TCP請求(login) -> 收到服務(wù)端返回登錄成功回執(zhí)(loginReceipt) ->發(fā)送心跳 -> 出現(xiàn)連接中斷 ->斷網(wǎng)重連3次 -> 退出程序主動斷開連接
<<<<關(guān)于連接狀態(tài)監(jiān)聽>>>>
- 普通網(wǎng)絡(luò)監(jiān)聽
由于即時通訊對于網(wǎng)絡(luò)狀態(tài)的判斷需要較為精確 ,原生的Reachability實際上在很多時候判斷并不可靠 擂达。
主要體現(xiàn)在當網(wǎng)絡(luò)較差時土铺,程序可能會出現(xiàn)連接上網(wǎng)絡(luò) , 但并未實際上能夠進行數(shù)據(jù)傳輸 板鬓。
開始嘗試著用Reachability加上一個普通的網(wǎng)絡(luò)請求來雙重判斷實現(xiàn)更加精確的網(wǎng)絡(luò)監(jiān)聽 悲敷, 但是實際上是不可行的 。
如果使用異步請求依然判斷不精確 俭令, 若是同步請求 后德, 對性能的消耗會很大 。
最終采取的解決辦法 抄腔, 使用RealReachability 瓢湃,對網(wǎng)絡(luò)監(jiān)聽同時 ,PING服務(wù)器地址或者百度 赫蛇,網(wǎng)絡(luò)監(jiān)聽問題基本上得以解決
- TCP連接狀態(tài)監(jiān)聽:
TCP的連接狀態(tài)監(jiān)聽主要使用服務(wù)器和客戶端互相發(fā)送心跳 绵患,彼此驗證對方的連接狀態(tài) 。
規(guī)則可以自己定義 悟耘, 當前使用的規(guī)則是 落蝙,當客戶端連接上服務(wù)器端口后 ,且成功建立SSL驗證后 暂幼,向服務(wù)器發(fā)送一個登陸的消息(login)筏勒。
當收到服務(wù)器的登陸成功回執(zhí)(loginReceipt)開啟心跳定時器 ,每一秒鐘向服務(wù)器發(fā)送一次心跳 旺嬉,心跳的內(nèi)容以安卓端/iOS端/服務(wù)端最終協(xié)商后為準 管行。
當服務(wù)端收到客戶端心跳時,也給服務(wù)端發(fā)送一次心跳 鹰服。正常接收到對方的心跳時病瞳,當前連接狀態(tài)為已連接狀態(tài) 揽咕,當服務(wù)端或者客戶端超過3次(自定義)沒有收到對方的心跳時,判斷連接狀態(tài)為未連接套菜。
<<<<關(guān)于本地緩存>>>>
- 數(shù)據(jù)庫緩存
建議每個登陸用戶創(chuàng)建一個DB 亲善,切換用戶時切換DB即可 。
搭建一個完善IM體系 逗柴, 每個DB至少對應(yīng)3張表 蛹头。
一張用戶存儲聊天列表信息,這里假如它叫chatlist 戏溺,即微信首頁 渣蜗,用戶存儲每個群或者單人會話的最后一條信息 。來消息時更新該表旷祸,并更新內(nèi)存數(shù)據(jù)源中列表信息耕拷。或者每次來消息時更新內(nèi)存數(shù)據(jù)源中列表信息 托享,退出程序或者退出聊天列表頁時進行數(shù)據(jù)庫更新骚烧。后者避免了頻繁操作數(shù)據(jù)庫,效率更高闰围。
一張用戶存儲每個會話中的詳細聊天記錄 赃绊,這里假如它叫chatinfo。該表也是如此 羡榴,要么接到消息立馬更新數(shù)據(jù)庫碧查,要么先存入內(nèi)存中,退出程序時進行數(shù)據(jù)庫緩存校仑。
一張用于存儲好友或者群列表信息 忠售,這里假如它叫myFriends ,每次登陸或者退出迄沫,或者修改好友備注档痪,刪除好友,設(shè)置星標好友等操作都需要更新該表邢滑。
- 沙盒緩存
當發(fā)送或者接收圖片、語音愿汰、文件信息時困后,需要對信息內(nèi)容進行沙盒緩存。
沙盒緩存的目錄分層 衬廷,個人建議是在每個用戶根據(jù)自己的userID在Cache中創(chuàng)建文件夾摇予,該文件夾目錄下創(chuàng)建每個會話的文件夾。
這樣做的好處在于 吗跋, 當你需要刪除聊天列表會話或者清空聊天記錄 侧戴,或者app進行內(nèi)存清理時 宁昭,便于找到該會話的所有緩存。大致的目錄結(jié)構(gòu)如下
../Cache/userID(當前用戶ID)/toUserID(某個群或者單聊對象)/...(圖片酗宋,語音等緩存)
<<<<關(guān)于消息分發(fā)>>>>
全局咱們設(shè)定了一個ChatHandler單例积仗,用于處理TCP的相關(guān)邏輯 。那么當TCP推送過來消息時蜕猫,我該將這些消息發(fā)給誰寂曹?誰注冊成為我的代理,我就發(fā)給誰回右。
ChatHandler單例為全局的隆圆,并且生命周期為整個app運行期間不會銷毀。在ChatHandler中引用一個數(shù)組 翔烁,該數(shù)組中存放所有注冊成為需要收取消息的代理渺氧,當每來一條消息時,遍歷該數(shù)組蹬屹,并向所有的代理推送該條消息.
<<<<聊天UI的搭建>>>>
- 聊天列表UI(微信首頁)
這個頁面沒有太多可說的 侣背, 一個tableView即可搞定 。需要注意的是 哩治,每次收到消息時秃踩,都需要將該消息置頂 。每次進入程序時业筏,拉取chatlist表存儲的每個會話的最后一條聊天記錄進行展示 憔杨。
- 會話頁面
該頁面tableView或者collectionView均可實現(xiàn) ,看個人喜好 蒜胖。這里是我用的是tableView .
根據(jù)消息類型大致分為普通消息 消别,語音消息 ,圖片消息 台谢,文件消息 寻狂,視頻消息 ,提示語消息(以上為打招呼內(nèi)容朋沮,xxx已加入群蛇券,xxx撤回了一條消息等)這幾種 ,固cell的注冊差不多為5種類型樊拓,每種消息對應(yīng)一種消息纠亚。
視頻消息和圖片消息cell可以復(fù)用 。
不建議使用過少的cell類型 筋夏,首先是邏輯太多 蒂胞,不便于處理 。其次是效率并不高条篷。
<<<<發(fā)送消息>>>>
- 文本消息/表情消息
直接調(diào)用咱們封裝好的ChatHandler的sendMessage方法即可 骗随, 發(fā)送消息時 蛤织,需要存入或者更新chatlist和chatinfo兩張表。若是未連接或者發(fā)送超時 鸿染,需要重新更新數(shù)據(jù)庫存儲的發(fā)送成功與否狀態(tài) 指蚜,同時更新內(nèi)存數(shù)據(jù)源 ,刷新該條消息展示即可牡昆。
若是表情消息 姚炕,傳輸過程也是以文本的方式傳輸 ,比如一個大笑的表情 丢烘,可以定義為[大笑] 柱宦,當然規(guī)則自己可以和安卓端web端協(xié)商,本地根據(jù)plist文件和表情包匹配進行圖文混排展示即可 播瞳。
https://github.com/coderMyy/MYCoreTextLabel 掸刊,圖文混排地址 , 如果覺得有用 赢乓, 請star一下 忧侧,好人一生平安
- 語音消息
語音消息需要注意的是 ,多和安卓端或者web端溝通 牌芋,找到一個大家都可以接受的格式 蚓炬,轉(zhuǎn)碼時使用同一種格式,避免某些格式其他端無法播放躺屁,個人建議Mp3格式即可肯夏。
同時,語音也需要做相應(yīng)的降噪 犀暑,壓縮等操作驯击。
發(fā)送語音大約有兩種方式 。
一是先對該條語音進行本地緩存 耐亏, 然后全部內(nèi)容均通過TCP傳輸并攜帶該條語音的相關(guān)信息徊都,例如時長,大小等信息广辰,具體的你得測試一條壓縮后的語音體積有多大暇矫,若是過大,則需要進行分割然后以消息的方法時發(fā)送择吊。接收語音時也進行拼接袱耽。同時發(fā)送或接收時,對chatinfo和chatlist表和內(nèi)存數(shù)據(jù)源進行更新 干发,超時或者失敗再次更新。
二是先對該條語音進行本地緩存 史翘, 語音內(nèi)容使用http傳輸枉长,傳輸?shù)椒?wù)器生成相應(yīng)的id 冀续,獲取該id再附帶該條語音的相關(guān)信息 ,以TCP方式發(fā)送給對方必峰,當對方收到該條消息時洪唐,先去下載該條信息,并根據(jù)該條語音的相關(guān)信息進行展示吼蚁。同時發(fā)送或接收時凭需,對chatinfo和chatlist表和內(nèi)存數(shù)據(jù)源進行更新 ,超時或者失敗再次更新肝匆。
- 圖片消息
圖片消息需要注意是 ,通過拍照或者相冊中選擇的圖片應(yīng)當分成兩種大小 , 一種是壓縮得非常小的狀態(tài)承绸,一種是圖片本身的大小狀態(tài)蜕琴。 聊天頁面展示的 ,僅僅是小圖 能曾,只有點擊查看時才去加載大圖度硝。這樣做的目的在于提高發(fā)送和接收的效率。
同樣發(fā)送圖片也有兩種方式 寿冕。
一是先對該圖片進行本地緩存 蕊程, 然后全部內(nèi)容均通過TCP傳輸 ,并攜帶該圖片的相關(guān)信息 驼唱,例如圖片的大小 藻茂,名字 ,寬高比等信息 曙蒸。同樣如果過大也需要進行分割傳輸捌治。同時發(fā)送或接收時,對chatinfo和chatlist表和內(nèi)存數(shù)據(jù)源進行更新 纽窟,超時或者失敗再次更新肖油。
二是先對該圖片進行本地緩存 , 然后通過http傳輸?shù)椒?wù)器 臂港,成功后發(fā)送TCP消息 森枪,并攜帶相關(guān)消息 。接收方根據(jù)你該條圖片信息進行UI布局审孽。同時發(fā)送或接收時县袱,對chatinfo和chatlist表和內(nèi)存數(shù)據(jù)源進行更新 ,超時或者失敗再次更新佑力。
- 視頻消息
視頻消息值得注意的是 式散,小的視頻沒有太多異議,跟圖片消息的規(guī)則差不多 打颤。只是當你從拍照或者相冊中獲取到視頻時暴拄,第一時間要獲取到視頻第一幀用于展示 漓滔,然后再發(fā)送視頻的內(nèi)容。大的視頻 乖篷,有個問題就是當你選擇一個視頻時响驴,首先做的是緩存到本地,在那一瞬間 撕蔼,可能會出現(xiàn)內(nèi)存峰值問題 豁鲤。只要不是過大的視頻 ,現(xiàn)在的手機硬件配置完全可以接受的鲸沮。而上傳采取分段式讀取琳骡,這個問題并不會影響太多。
視頻消息我個人建議是走http上傳比較好 诉探,因為內(nèi)容一般偏大 日熬。TCP部分僅需要傳輸該視頻封面以及相關(guān)信息比如時長,下載地址等相關(guān)信息即可肾胯。接收方可以通過視頻大小判斷竖席,如果是小視頻可以接收到后默認自動下載,自動播放 敬肚,大的視頻則只展示封面毕荐,只有當用戶手動點擊時才去加載。具體的還是需要根據(jù)項目本身的設(shè)計而定艳馒。
- 文件消息
文件方面 憎亚,iOS端并不如安卓端那種可操作性強 ,安卓可以完全獲取到用戶里的所有文件,iOS則有保護機制弄慰。通常iOS端發(fā)送的文件 第美,基本上僅僅局限于當前app自己緩存的一些文件 ,原理跟發(fā)送圖片類似陆爽。
- 撤回消息
撤回消息也是消息內(nèi)容的一種類型 什往。例如 A給B發(fā)送了一條消息 "你好" ,服務(wù)端會對該條消息生成一個messageID 慌闭,接收方收到該條消息的messageID和發(fā)送方的該條消息messageID一致别威。如果發(fā)送端需要撤回該條消息 ,僅僅需要拿到該條消息messageID 驴剔,設(shè)置一下消息類型 省古,發(fā)送給對方 ,當收到撤回消息的成功回執(zhí)(repealReceipt)時丧失,移除該會話的內(nèi)存數(shù)據(jù)源和更新chatinfo和chatlist表 豺妓,并加載提示類型的cell進行展示例如“你撤回了一條消息”即可。接收方收到撤回消息時 ,同樣移除內(nèi)存數(shù)據(jù)源 科侈,并對數(shù)據(jù)庫進行更新 载佳,再加載提示類型的cell例如“張三撤回了一條消息”即可。
- 提示語消息
提示語消息通常來說是服務(wù)器做的事情更多 臀栈,除了撤回消息是需要客戶端自己做的事情并不多。
當有人退出群 挠乳,或者自己被群主踢掉 权薯,時服務(wù)端推送一條提示語消息類型,并附帶內(nèi)容睡扬,客戶端僅僅需要做展示即可盟蚣,例如“張三已經(jīng)加入群聊”,“以上為打招呼內(nèi)容”卖怜,“你已被踢出該群”等屎开。
當然 ,撤回消息也可以這樣實現(xiàn) 马靠,這樣提示消息類型邏輯就相當統(tǒng)一奄抽,不會顯得很亂 。把主要邏輯交于了服務(wù)端來實現(xiàn)甩鳄。
<<<<消息刪除>>>>
這里需要注意的一點是 逞度,類似微信的長按消息操作 ,我采用的是UIMenuController來做的 妙啃,實際上有一點問題 档泽,就是第一響應(yīng)者的問題 ,想要展示該menu 揖赴,必須將該條消息的cell置為第一響應(yīng)者馆匿,然后底部的鍵盤失去第一響應(yīng)者,會降下去 燥滑。所以該長按出現(xiàn)menu最好還是自定義 渐北,根據(jù)計算相對frame進行布局較好,自定義程度也更好突倍。
消息刪除大概分為刪除該條消息 腔稀,刪除該會話 ,清空聊天記錄幾種
刪除該條消息僅僅需要移除本地數(shù)據(jù)源的消息模型 羽历,更新chatlist和chatinfo表即可焊虏。
刪除該會話需要移除chatlist和chatinfo該會話對應(yīng)的列 ,并根據(jù)當前登錄用戶的userID和該會話的toUserID或者groupID移除沙盒中的緩存秕磷。
清空聊天記錄诵闭,需要更新chatlist表最后一條消息內(nèi)容 ,刪除chatinfo表,并刪除該會話的沙盒緩存.
<<<<消息拷貝>>>>
這個不用多說 疏尿,一兩句話搞定
<<<<消息轉(zhuǎn)發(fā)>>>>
拿到該條消息的模型 瘟芝,并創(chuàng)建新的消息 ,把內(nèi)容賦值到新消息 褥琐,然后選擇人或者群發(fā)送即可锌俱。
值得注意的是 ,如果是轉(zhuǎn)發(fā)圖片或者視頻 敌呈,本地沙盒中的緩存也應(yīng)當copy一份到轉(zhuǎn)發(fā)對象所對應(yīng)的沙盒目錄緩存中 贸宏,不能和被轉(zhuǎn)發(fā)消息的會話共用一張圖或者視頻 。因為比如 :A給B發(fā)了一張圖 磕洪,A把該圖轉(zhuǎn)發(fā)給了C 吭练,A移除掉A和B的會話 ,那么如果是共用一張圖的話 析显,A和C的會話中就再也無法找到這張圖進行展示了鲫咽。
<<<<重新發(fā)送>>>>
這個沒有什么好說的。
<<<<標記已讀>>>>
功能實現(xiàn)比較簡單 谷异,僅僅需要修改數(shù)據(jù)源和數(shù)據(jù)庫的該條會話的未讀數(shù)(unreadCount)分尸,刷新UI即可。
<<<<以下為大致的實現(xiàn)步驟>>>>
文本/表情消息 :
方式一: 輸入 ->發(fā)送 -> 消息加入聊天數(shù)據(jù)源 -> 更新數(shù)據(jù)庫 -> 展示到聊天會話中 -> 調(diào)用TCP發(fā)送到服務(wù)器(若超時晰绎,更新聊天數(shù)據(jù)源寓落,更新數(shù)據(jù)庫 ,刷新聊天UI) ->收到服務(wù)器成功回執(zhí)(normalReceipt) ->修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) -> 更新數(shù)據(jù)庫
方式二: 輸入 ->發(fā)送 -> 消息加入聊天數(shù)據(jù)源 -> 展示到聊天會話中 -> 調(diào)用TCP發(fā)送到服務(wù)器(若超時荞下,更新聊天數(shù)據(jù)源伶选,刷新聊天UI) ->收到服務(wù)器成功回執(zhí)(normalReceipt) ->修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) ->退出app或者頁面時 ,更新數(shù)據(jù)庫
語音消息 :(這里以http上傳尖昏,TCP原理一致)
方式一: 長按錄制 ->壓縮轉(zhuǎn)格式 -> 緩存到沙盒 -> 更新數(shù)據(jù)庫->展示到聊天會話中仰税,展示轉(zhuǎn)圈發(fā)送中狀態(tài) -> 調(diào)用http分段式上傳(若失敗,刷新UI展示) ->調(diào)用TCP發(fā)送該語音消息相關(guān)信息(若超時抽诉,刷新聊天UI) ->收到服務(wù)器成功回執(zhí) -> 修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) ->修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend)-> 更新數(shù)據(jù)庫-> 刷新聊天會話中該條消息UI
方式二: 長按錄制 ->壓縮轉(zhuǎn)格式 -> 緩存到沙盒 ->展示到聊天會話中陨簇,展示轉(zhuǎn)圈發(fā)送中狀態(tài) -> 調(diào)用http分段式上傳(若失敗,更新聊天數(shù)據(jù)源迹淌,刷新UI展示) ->調(diào)用TCP發(fā)送該語音消息相關(guān)信息(若超時,更新聊天數(shù)據(jù)源河绽,刷新聊天UI) ->收到服務(wù)器成功回執(zhí) -> 修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend -> 刷新聊天會話中該條消息UI - >退出程序或者頁面時進行數(shù)據(jù)庫更新
圖片消息 :(兩種考慮,一是展示和http上傳均為同一張圖 唉窃,二是展示使用壓縮更小的圖耙饰,http上傳使用選擇的真實圖片,想要做到精致纹份,方法二更為可靠)
方式一: 打開相冊選擇圖片 ->獲取圖片相關(guān)信息苟跪,大小廷痘,名稱等,根據(jù)用戶是否選擇原圖件已,考慮是否壓縮 ->緩存到沙盒 -> 更新數(shù)據(jù)庫 ->展示到聊天會話中笋额,根據(jù)上傳顯示進度 ->http分段式上傳(若失敗,更新聊天數(shù)據(jù),更新數(shù)據(jù)庫,刷新聊天UI) ->調(diào)用TCP發(fā)送該圖片消息相關(guān)信息(若超時篷扩,更新聊天數(shù)據(jù)源兄猩,更新數(shù)據(jù)庫,刷新聊天UI)->收到服務(wù)器成功回執(zhí) -> 修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) ->更新數(shù)據(jù)庫 -> 刷新聊天會話中該條消息UI
方式二:打開相冊選擇圖片 ->獲取圖片相關(guān)信息,大小鉴未,名稱等厦滤,根據(jù)用戶是否選擇原圖,考慮是否壓縮 ->緩存到沙盒 ->展示到聊天會話中歼狼,根據(jù)上傳顯示進度 ->http分段式上傳(若失敗,更細聊天數(shù)據(jù)源 享怀,刷新聊天UI) ->調(diào)用TCP發(fā)送該圖片消息相關(guān)信息(若超時羽峰,更新聊天數(shù)據(jù)源 ,刷新聊天UI)->收到服務(wù)器成功回執(zhí) -> 修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) -> 刷新聊天會話中該條消息UI ->退出程序或者離開頁面更新數(shù)據(jù)庫
視頻消息:
方式一:打開相冊或者開啟相機錄制 -> 壓縮轉(zhuǎn)格式 ->獲取視頻相關(guān)信息添瓷,第一幀圖片梅屉,時長,名稱鳞贷,大小等信息 ->緩存到沙盒 ->更新數(shù)據(jù)庫 ->第一幀圖展示到聊天會話中坯汤,根據(jù)上傳顯示進度 ->http分段式上傳(若失敗,更新聊天數(shù)據(jù),更新數(shù)據(jù)庫,刷新聊天UI) ->調(diào)用TCP發(fā)送該視頻消息相關(guān)信息(若超時搀愧,更新聊天數(shù)據(jù)源惰聂,更新數(shù)據(jù)庫,刷新聊天UI)->收到服務(wù)器成功回執(zhí) -> 修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) ->更新數(shù)據(jù)庫 -> 刷新聊天會話中該條消息UI
方式二:打開相冊或者開啟相機錄制 ->壓縮轉(zhuǎn)格式 ->獲取視頻相關(guān)信息,第一幀圖片咱筛,時長搓幌,名稱,大小等信息 ->緩存到沙盒 ->第一幀圖展示到聊天會話中迅箩,根據(jù)上傳顯示進度 ->http分段式上傳(若失敗溉愁,更細聊天數(shù)據(jù)源 ,刷新聊天UI) ->調(diào)用TCP發(fā)送該視頻消息相關(guān)信息(若超時饲趋,更新聊天數(shù)據(jù)源 拐揭,刷新聊天UI)->收到服務(wù)器成功回執(zhí) -> 修改數(shù)據(jù)源該條消息發(fā)送狀態(tài)(isSend) -> 刷新聊天會話中該條消息UI ->退出程序或者離開頁面更新數(shù)據(jù)庫
文件消息:
跟上述一致 ,需要注意的是奕塑,如果要實現(xiàn)該功能 堂污,接收到的文件需要在沙盒中單獨開辟緩存。比如接收到web端或者安卓端的文件
<<<<消息丟失問題>>>>
消息為什么會丟失 爵川?
最主要原因應(yīng)該歸結(jié)于服務(wù)器對客戶端的網(wǎng)絡(luò)判斷不準確敷鸦。盡管客戶端已經(jīng)和服務(wù)端建立了心跳驗證 , 但是心跳始終是有間隔的,且TCP的連接中斷也是有延遲的扒披。例如值依,在此時我向服務(wù)器發(fā)送了一次心跳,然后網(wǎng)絡(luò)失去了連接碟案,或者網(wǎng)絡(luò)信號不好愿险。服務(wù)器接收到了該心跳 ,服務(wù)器認為客戶端是處于連接狀態(tài)的价说,向我推送了某個人向我發(fā)送的消息 辆亏,然而此時我卻不能收到消息,所以出現(xiàn)了消息丟失的情況鳖目。
補充: CocoaSyncSocket的三次握手四次揮手驗證,僅僅表現(xiàn)在連接時確卑邕叮可靠的連接和斷開 ,而并不能保證消息數(shù)據(jù)傳輸中的可靠性 , 所以消息數(shù)據(jù)傳輸,我們可以模擬三次握手進行傳輸.
解決辦法 :客戶端向服務(wù)端發(fā)送消息,服務(wù)端會給客戶端返回一個回執(zhí)领迈,告知該條消息已經(jīng)發(fā)送成功彻磁。所以,客戶端有必要在收到消息時狸捅,也向服務(wù)端發(fā)送一個回執(zhí)衷蜓,告知服務(wù)端成功收到了該條消息。而客戶端尘喝,默認收到的所有消息都是離線的磁浇,只有收到客戶端的接收消息的成功回執(zhí)后,才會移除掉該離線消息緩存朽褪,否則將會把該條消息以離線消息方式同步推送置吓。離線消息后面會做解釋。此時的雙向回執(zhí)鞍匾,可以把消息丟失概率降到非常低 ,基本上算是模擬了一個消息數(shù)據(jù)傳輸?shù)娜挝帐帧?/p>
<<<<消息亂序問題>>>>
消息為什么會亂序 交洗?
客戶端發(fā)送消息,該消息會默認賦值當前時間戳 橡淑,收到安卓端或者web端發(fā)來的消息時构拳,該時間戳是安卓和web端獲取,這樣就可能會出現(xiàn)時間戳的誤差情況梁棠。比如當前聊天展示順序并沒有什么問題置森,因為展示是收到一條展示一條。但是當退出頁面重新進入時符糊,如果拉取數(shù)據(jù)庫是根據(jù)時間戳的降序拉取 凫海,那么就很容易出現(xiàn)混亂。
解決辦法 :表結(jié)構(gòu)設(shè)置自增ID 男娄,消息的順序展示以入庫順序為準 行贪,拉取數(shù)據(jù)庫獲取消息記錄時漾稀,根據(jù)自增ID降序拉取 。這樣就解決了亂序問題 建瘫,至少保證了崭捍,展示的消息順序和我聊天時的一樣。盡管時間戳可能并不一樣是按照嚴謹?shù)慕敌蚺帕械摹?/p>
<<<<離線消息>>>>
進入后臺啰脚,接收消息提醒:
解決方式要么采用極光推送進行解決 殷蛇,要么讓自己服務(wù)器接蘋果的服務(wù)器也行。畢竟極光只是作為一個中間者橄浓,最終都是通過蘋果服務(wù)器推送到每個手機粒梦。
進入程序加載離線消息:此處需要注意的是,若服務(wù)器僅僅是把每條消息逐個推送過來荸实,那么客戶端會出現(xiàn)一些小問題匀们,比如角標數(shù)為每次增加1,最后一條消息不斷更新 准给,直到離線消息接收到完畢昼蛀,造成一種不好的體驗。
解決辦法:離線消息服務(wù)端全部進行拼接或者以jsonArray方式圆存,并協(xié)議分割方式,客戶端收到后僅需僅需切割仇哆,直接在角標上進行總數(shù)的相加沦辙,并直接更新最后一條消息即可。亦或者讹剔,設(shè)置包頭信息油讯,告知每條消息長度,切割方式等延欠。
<<<<版本兼容性問題處理>>>>
其實 , 做IM遇到最麻煩的問題之一 , 就應(yīng)當是版本兼容問題 . 即時通訊的功能點有很多 , 項目不可能一期所有的功能全部做完 , 那么就會涉及到新老版本兼容的問題 . 當然如果服務(wù)端經(jīng)驗足夠豐富 , 版本兼容的問題可以交于服務(wù)端來完成 , 客戶端并不需要做太多額外的事情 . 如果是并行開發(fā) , 服務(wù)端思路不夠長遠 ,或者產(chǎn)品需求變更頻繁且比較大.那么客戶端也需要做一些相應(yīng)的版本兼容問題 . 處理版本兼容問題并不難 , 主要問題在于當增加一個新功能時 , 服務(wù)端或許會推送過來更多的字段 , 而老版本的項目數(shù)據(jù)庫如果沒有預(yù)留足夠的字段 , 就涉及到了數(shù)據(jù)庫升級 . 而當收到高版本新功能的消息時 , 客戶端也應(yīng)當對該消息做相應(yīng)的處理 . 例如,老版本的app不支持消息撤回 , 而新版本支持消息撤回 , 當新版本發(fā)送消息撤回時 , 老版本可以攔截到這條未知的消息類型 , 做相應(yīng)的處理 , 比如替換成一條提示"該版本暫不支持,請前往appstore下載新版本"等. 而當必要時 , 如果整個IM結(jié)構(gòu)沒有經(jīng)過深思熟慮 , 還可能會涉及到強制升級 .
<<<<[有人@你]>>>>
對于 有人@你 這個功能的實現(xiàn)可分為2部分 :
聊天列表最后一條消息前展示[有人@你] ,例如 [有人@你]你好 . 因為有人@你這個功能只出現(xiàn)在群組中 ,所以在發(fā)送消息時 ,設(shè)立"toUser"字段即可,當然這個字段名可以自己設(shè)定,當發(fā)送端@多個人時,自己定義規(guī)則進行拼接(安卓端,iOS端,web端協(xié)商),例如@"userID1,userID2,userID3"則表明了我@了這三個人.
當接收端接收到toUser字段不為空時,切割遍歷userID是否有自己的id即可.如果有,說明有人@我 ,偏好設(shè)置userdefault進行存儲狀態(tài)即可 .進入該條會話,清除userdefault .聊天對話頁的錨點展示 , 具體什么時候展示錨點,微信的邏輯大概是:
當一個群處于正常狀態(tài)下,有人@我 ,右上角展示 "有人@我"按鈕,點擊按鈕拉取所有的未讀消息,這里是未讀消息,而不是定位到"有人@我"那一條 . 若沒有人@我,則展示"未讀消息"按鈕 , 定位也是到第一條未讀消息位置
當一個群處于免打擾狀態(tài)下,有人@我,右上角展示"有人@你"按鈕 , 點擊按鈕 ,定位到 "有人@我"那一條.
這里我的做法是 , 直接拉取數(shù)據(jù)庫從 "有人@你" 或者所有的未讀消息到最新的一條消息間的所有消息, 這樣做法對于用戶量不是特別大的情況下 , 不會出現(xiàn)任何問題.若是消息數(shù)量特別龐大 , 幾千甚至上萬條 ,該做法就不適用了,拉取的數(shù)據(jù)過多,解決辦法可以借鑒微信做法 , 定位時獲取中間一段消息,然后進行上拉或者下拉再去類似分頁的去拉取數(shù)據(jù)庫.
<<<<[草稿]功能>>>>
草稿功能相對簡單 ,跟 "有人@你" 展示邏輯差不多 , 當退出聊天會話頁時,檢查一下鍵盤輸入框中是否有值 , 有值則userdefault存入該信息和對應(yīng)的該條會話的userID或者groupID.聊天頁面展示時,判斷一下該條會話是否有草稿,如果有展示 . 進入聊天會話頁時,也檢查一次是否有草稿 ,如果有,自動彈起鍵盤,填充上次的內(nèi)容即可.
<<<<注意事項>>>>
- 在搭建體系時 , 盡量把數(shù)據(jù)和業(yè)務(wù)邏輯都抽取到ChatHandler中 , 控制器里只需要拿到直接可用的消息模型即可 . 不然后期功能增多 , 控制器里的邏輯和代碼會越來越多 ,并且還需要考慮控制器的生命周期問題 , 比較麻煩
以上僅為大體的思路 , 實際上搭建IM , 更多的難點在于邏輯的處理和各種細節(jié)問題 . 比如數(shù)據(jù)庫,本地緩存,和服務(wù)端的通信協(xié)議,和安卓端私下通信協(xié)議.以及聊天UI的細節(jié)處理,例如聊天背景實時拉高,圖文混排等等一系列麻煩的事.沒辦法寫到很詳細 ,都需要自己仔細的去思考.難度并不算很大,只是比較費心.
寫在最后 :
其實上述的思路什么的 , 也許看的時候并不覺得復(fù)雜 , 但是實際上真正去搭建時 , 其中遇到的各種問題還是非常的惡心 ,直到功能全部做完 ,才對整體有了一個比較全面的認識 . 還有就是很多人在求UI方面的代碼 , 由于最近一直在抽時間學(xué)習(xí)java方面的東西 , UI的東西我會盡快的補上來 ,UI不難 ,難的是UI串起來的邏輯和細節(jié) ...
https://github.com/coderMyy/CocoaAsyncSocket_Demo github地址 ,會持續(xù)更新關(guān)于即時通訊的細節(jié) , 以及最終的UI代碼