IM開發乾貨分享:有贊移動端IM的組件化SDK架構設計實踐

2020-12-16 即時通訊技術分享

本文由有贊技術團隊原創分享,原題「有贊 APP IM SDK 組件架構設計」,即時通訊網收錄時有修訂和改動,感謝原作者的無私分享。

1、引言

本文主要以Android客戶端為例,記錄了有贊旗下 App 中使用自研 IM,並將IM提煉成組件化SDK的設計思路。此項工作由有贊移動開發組 IM SDK 團隊共同討論完成。

在有贊產品中,存在大量需要交易雙方溝通交流的場景,比如,客戶諮詢商家產品信息,售前售後簡單的答疑和維權等。另外,有贊業務還存在一些特殊的複雜場景,如供應商、分銷商、客戶三方之間需要同步溝通,會同時存在多種溝通角色。

此時需要較為完善的即時通信(IM)解決方案,但是由於有贊針對不同的商戶和使用場景有多個APP,APP自行實現IM功能代價較大,且維護起來人力分散,於是,IM SDK項目便應運而生了,APP 通過接入此給件化SDK,可以快速實現IM基本功能。

本文已同步發布於「即時通訊技術圈」公眾號。

2、相關文章

《從遊擊隊到正規軍(一):馬蜂窩旅遊網的IM系統架構演進之路》《從遊擊隊到正規軍(二):馬蜂窩旅遊網的IM客戶端架構演進和實踐總結》(* 推薦)《從遊擊隊到正規軍(三):基於Go的馬蜂窩旅遊網分布式IM系統技術實踐》《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》《從零到卓越:京東客服即時通訊系統的技術架構演進歷程》《一套原創分布式即時通訊(IM)系統理論架構方案》《蘑菇街即時通訊/IM伺服器開發之架構選擇》《自已開發IM有那麼難嗎?手把手教你自擼一個Andriod版簡易IM (有源碼)》《適合新手:從零開發一個IM服務端(基於Netty,有完整源碼)》《拿起鍵盤就是幹:跟我一起徒手開發一套分布式IM系統》

3、設計目標

本次IM組件化SDK的設計目標有以下幾點:

1)IM 主流程穩定可用:消息傳輸具有高可靠性;2)UI 組件直接集成進入SDK,並支持可定製化;3)富媒體發送集成進入SDK,並可按需定製需要的富媒體類型;4)實現消息傳輸層SDK,與帶有UI的SDK的功能分離,業務調用方既可以使用消息傳輸SDK,處理消息,然後自行處理UI,也可以使用帶有UI組件的SDK,一步實現較為完備的IM功能。4、整體結構

下圖中簡要描述了有贊客戶端中IM系統的基本結構 :

如上圖所示,各分層的職責分工如下:

1)消息通道層:維護Socket長連接作為消息通道,消息收發流程主要在這一層中完成;2)持久化層:主要將消息存入資料庫中,富媒體文件存入文件緩存中,方便第二次展示消息時候,從本地加載,而不是網絡層獲取;3)邏輯處理層:完成各種消息相關的邏輯處理,如排序,富媒體文件的預處理等;4)UI顯示層:將數據在UI上進行呈現。5、設計要點1:Socket長連接的創建與維護

IM SDK 所有數據收發流程,均通過Socket長連接完成,如何維護一個穩定Socket通道,是IM系統是否穩定的重要一環。

下面描述下Socket通道幾個重要的流程。

1)創建流程(連接) :

如圖上所示,當IM SDK初始化後,業務調用連接請求接口,會開始連接的創建過程,創建成功後,會完成鑑權操作,當創建和鑑權都完成後,會開啟消息收發線程,為了維持長連接,會有心跳機制,特別的,會開啟一個心跳輪詢線程。

2)心跳機制 :

心跳機制,是IM系統設計中的常見概念,簡單的解釋就是每隔若干時間發送一個固定信息給服務端,服務端收到後及時回復一個固定信息,如果服務端若干時間內沒有收到客戶端心跳信息則視客戶端斷開,同理如果客戶端若干時間沒有收到服務端心跳回值則視服務端斷開。

當長連接創建成功後,會開啟一個輪詢線程,每隔一段時間發送心跳消息給伺服器端,以維持長連接。

有關IM心跳方面的專項文章,請見:

《手把手教你用Netty實現網絡通信程序的心跳機制、斷線重連機制》《為何基於TCP協議的移動端IM仍然需要心跳保活機制?》《移動端IM實踐:實現Android版微信的智能心跳機制》《移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》《一文讀懂即時通訊應用中的網絡心跳包機制:作用、原理、實現思路等》《正確理解IM長連接的心跳及重連機制,並動手實現(有完整IM源碼)》《一種Android端IM智能心跳算法的設計與實現探討(含樣例代碼)》《手把手教你用Netty實現網絡通信程序的心跳機制、斷線重連機制》

3)重連流程 :

重連被觸發時,如果該次連接成功,退出重連。反之重連失敗後,會判斷當前重連的次數是否超過預期值(這裡設為6次),並對重連次數計數,如果超過就會退出重連,反之休眠預設的時間後再次進行重連操作。

重連觸發條件分為三種:

a. 主動連接不成功(主動連接Socket,如果連接失敗,會觸發重連機制);b. 網絡被主動斷開(正常建立連接,操作過程中,網絡被斷開,通過系統廣播觸發重連);c. 伺服器沒響應,心跳沒回值(服務端心跳預設時間內沒回值,客戶端認為服務端已經斷開,觸發重連)。有關重連機制的深入學習,可以閱讀以下兩篇:

《正確理解IM長連接的心跳及重連機制,並動手實現(有完整IM源碼)》《手把手教你用Netty實現網絡通信程序的心跳機制、斷線重連機制》4)網絡狀態判斷:

TCP API並沒有提供一個可靠的方法判斷當前長連接通道狀態,isConnected()和isClosed()僅僅告訴你當前的Socket狀態,不是是長連接斷開是一回事。 isConnected()告訴你是否Socket與Romote host保持連接,isClosed()告訴你是否Socket被關閉。

假如你判斷長連接通道是否被關閉,只能通過和流操作相關的以下方法:

a. read() return -1;b. readLine() return null;c. readXXX() throw EOPException for any other XXX;d. write 將拋出IOException: Broken pipe(通道被關閉)。所以SDK封裝isConnected(方法的時候,是根據這幾種情況綜合判斷當前的通道狀態,而不是僅僅通過Socket.isConnected()或者Socket.isClosed()。

6、設計要點2:消息發送流程

消息發送流程主要有兩大類:

1)一類是IM相關數據的請求,例如:歷史消息列表,會話列表等;

2)另一類是IM消息的發送,主要是文字消息。

(富媒體消息發送,會將富媒體文件先上傳伺服器後,拿到文件URL, 通過文字消息,將此URL發給接收方,接收方下載後進行UI展示)。

以上兩類消息發送,均使用上圖的流程進行發送,可通過發送回調感知請求的結果。

如上圖所示,消息發送流程,需要先封裝消息請求,在通過發送隊列發送至伺服器,發送前,在將請求id和對應回調存入本地Map數據結構中。

if(requestCallBack != null) { mCallBackMap.put(requestId, requestCallBack);}

之後接收伺服器推送消息(此消息帶有發送請求時的請求id),在本地的Map數據找到請求id對應的回調,然後通過回調返回伺服器推送過來的數據。

請求可以通過泛型指定返回值類型,SDK中會自行解析伺服器數據返回的數據,直接返回給業務調用方model對象,方便使用。(目前支持json格式的數據解析)

private void IMResponseOnSuccess(String requestid, String response) { if(mCallBackMap != null) { IMCallBack callBack = mCallBackMap.get(requestid); if(callBack == null) { return; } if(callBack instanceofJsonResultCallback) { finalJsonResultCallback resultCallback = (JsonResultCallback) callBack; if(resultCallback.mType == String.class) { callBack.onResponse(response); } else{ Object object = newGson().fromJson(response, resultCallback.mType); callBack.onResponse(object); } removeCallBack(requestid); } }}

如下的示例中,展示了一個獲取會話列表的請求,可以看出目前的請求封裝,和一些第三方的的網絡庫類似,使用起來較為方便。

RequestApi requestApi = new RequestApi(IMConstant.REQ_TYPE_GET_CONVERSATION_LIST, Enums Manager.IMType.IM_TYPE_WSC.getRequestChannel());requestApi.addRequestParams("limit", 100); requestApi.addRequestParams("offset", 0);IMEngine.getInstance().request(requestApi, newJsonResultCallback<List<ConversationEntity>>() { @Override publicvoidonResponse(List<ConversationEntity> response) { mSwipeRefreshLayout.setRefreshing(false); mAdapter.mDataset.clear(); mAdapter.mDataset.addAll(response); mAdapter.notifyDataSetChanged(); } @Override publicvoidonError(intstatusCode) { //do something }});

可以看出,該請求直接返回了一個會話類型的List集合,業務方可直接使用。

7、設計要點3:消息接收流程

消息的監聽流程主要使用了一個全局監聽的方式來進行,需要先註冊監聽器,監聽器中有默認的回調。

public interface IMListener { /** * 連接成功 */ void connectSuccess(); /** * 連接失敗 */ void connectFailure(EnumsManager.DisconnectType type); /** * 鑑權成功 */ void authorSuccess(); /** * 鑑權失敗 */ void authorFailure(); /** * 接收數據成功 */ void receiveSuccess(int reqType, String msgId, String requestChannel, String message, int statusCode); /** * 接收數據失敗 */ void receiveError(int reqType, String msgId, String requestChannel, int statusCode);}

該監聽器中可以接收如下類型的消息:

1)Socket連接狀態的返回結果;2)鑑權狀態的返回結果,(鑑權流程因有贊業務需要);3)接收的IM消息,或者其他類型的返回消息。可根據消息類型進行後續的分發處理。業務如需使用此全局監聽器,需要自行實現此接口,並在業務初始化時,註冊此監聽器即可。SDK中會根據註冊的監聽器,在讀取到伺服器推送消息後,直接通過監聽器到回調進行分發。

private void distributeData(IMEntity imEntity) { if(mIMListener != null&& imEntity != null) { // 省略部分邏輯代碼 …… if(status == Response.SUCCESS) { switch(responseModel.reqType) { caseIMConstant.REQ_TYPE_AUTH: // 鑑權成功 mIMListener.authorSuccess(); return; caseIMConstant.REQ_TYPE_OFFLINE: // 服務端踢客戶端下線 mIMListener.connectFailure(EnumsManager.DisconnectType.SERVER); break; caseIMConstant.REQ_TYPE_HEARTBEAT: // 心跳成功 caseIMConstant.REQ_TYPE_RECEIVER_MSG: // 收到回調消息 handleMessageID(responseModel.body); break; default: break; } mIMListener.receiveSuccess(responseModel.reqType, msgId, responseModel .requestChannel, responseModel.body, 0); } else{ mIMListener.receiveError(responseModel.reqType, msgId, responseModel .requestChannel, status); } }}

部分接收消息,如心跳,多端登錄時被踢下線通知等,sdk內部會自行處理,業務基本無感知。

8、設計要點4:可定製化的UI

隨著公司規模的擴大與業務線的快速迭代,可能新的業務也需要 IM 這個功能,眾所周知,IM UI 功能的嵌入會佔據大量的開發與調試時間, 為了解決這個痛點,決定將 IM UI 部分抽成一個 Library,實現可定製與單獨維護,做到真正的敏捷開發與快速迭代。

8.1 UIKit設計

IM UIKit暴露相應的api接口,業務方注入相應的功能定製項,針對UI的點擊回調通過EventBus總線post分發,減少了業務方與UIKit的耦合,底層業務方通過MVP模式對View與Model進行解耦。

定製項一般通過如下幾種方式。

1)XML(定製業務信息,資源信息,顯示條數,各個業務功能開關等):

<?xml version="1.0" encoding="utf-8"?><resources><stylename="limit"> <!--每屏展示的條數--> <itemname="swiplimit">5</item> ...... </style> ...... ...... <stylename="itembox"> <itemname="showvoice">true</item> ...... ...... <itemname="more"show="true"> <more> <iconstyle="mipmap">im_plus_image</icon> <itemname>測試</itemname> <callback>false</callback> </more> ...... ...... <more> <iconstyle="mipmap">ic_launcher</icon> <itemname>測試</itemname> <callback>true</callback> </more> </item> ...... ...... </style></resources>

2)Style(定製UI背景,氣泡顏色,字體大小等):

<?xml version="1.0" encoding="utf-8"?><resources><!--im 聊天背景--> <stylename="imui_background"> <itemname="android:background">@android:color/holo_red_dark</item> </style> ...... ...... <!--氣泡背景--> <stylename="bubble_background"> <itemname="android:background">@mipmap/bubble_right_green</item> </style> <!--背景和和欄位顏色定製--> <stylename="bg_and_textcolor"parent="bubble_background"> <itemname="android:textColor">@android:color/holo_red_dark</item> </style> ...... ......</resources>

3)Model定製(傳入預設的定製Model模板填入相應參數,UIKit裡面做相應解析):

public class Entity {publicString action1; publicString action2; publicString aciton3; ......}

8.2 UIKit 支持的富媒體類型

除了文字消息之外,現在主流的IM系統中也支持各種富媒體發送,在有贊IM SDK UIKit中,目前也支持幾種富媒體發送。 以下是發送流程圖和兩類常見富媒體消息簡介。

1)語音消息:除了使用常見的錄製和解碼播放的技術之外。還利用了 AudioManager 中 requestAudioFocus,abandonAudioFocus 相關方法,實現了錄製和播放語音消息,如果有第三方播放音樂,會自動暫停,錄製和播放語音消息結束後,聲音會自動播放。2)圖片消息:通過七牛伺服器設置了縮略圖,接收方收到消息後,會先下載縮略圖,當用戶再點擊進入圖片詳情頁時,會下載大圖,Andorid客戶端使用Picasso加載庫加載圖片,並做本地緩存。9、設計要點5:UI 中聊天會話數據加載策略

參考業界主流的IM系統方案,用戶聊天時,需要將已經發送和接收到的聊天信息保存到本地,而不是每次都拉取歷史數據。以達到節約流量和無網絡狀態下也查看數據的效果。

為此IM SDK持久化層的資料庫中,也實現了簡單存儲加載機制,下面描述典型的數據加載場景。

1)IM會話首次請求數據流程:

2)IM下拉獲取歷史數據流程:

3)IM單條消息發送持久化方案:

4)IM單條數據重發流程:

10、設計不足之處

1)消息回執:

當前的設計方案中,沒有消息回執的機制,也就是說接受方收到消息後,不會返回伺服器收到消息的通知,伺服器無法判斷消息是否推送成功,這樣在突然斷網,網絡模式切換,或者弱網環境下,會影響消息的到達率。

一種可行的設計方式是,發送方增加已送到和未送達的狀態,接收方收到消息後,給伺服器返回已收到消息的通知,伺服器再推送給發送方該狀態,如果沒有收到接收方回執,伺服器可嘗試重新推送。發送方接受到接收方的收到回執後,更新發送狀態已發送,如果未收到,則顯示未送達。為了防止接收方回執丟失,接收方接收消息時候,可維護本地去重隊列。

2)本地請求超時的判斷:

本地發起的請求,沒有用定時器,完全依賴伺服器返回或者出現Socket通道異常後上拋的通知作為超時判斷,部分場景可能覆蓋不到,需要對請求增加固定的超時處理機制,固定時候未收到請求,即認為超時。

* 推薦學習:針對以上兩點不足,感興趣的讀者,可以研究一下MobileIMSDK開源工程源碼https://github.com/JackJiang2011/MobileIMSDK,MobileIMSDK已經實現了完整的消息送達保證機制(包括:ACK回執、重傳、去重、超時判定等等)。(本文同步發布於:http://www.52im.net/thread-3088-1-1.html)

相關焦點

  • Vant - 有贊出品的移動UI組件庫
    Vant 是贊前端團隊維護的移動端組件庫,提供了一整套 UI 基礎組件和業務組件。介紹輕量、可靠的移動端 Vue 組件庫,採用 MIT 開源協議, 目前github star 數9k+,是有贊的一套開源組件庫。通過 Vant,可以快速搭建出風格統一的頁面,提升開發效率。目前已有近 60+ 個組件,這些組件被廣泛使用於有贊的各個移動端業務中。
  • 微信、陌陌等著名IM軟體設計架構詳解
    電量:對於行動裝置最大的瓶頸就是電量了。因為用戶不可能隨時攜帶電源,充電寶。所以必須考慮到電量問題。那就要檢查我們工程是不是有後臺運行,心跳包發送時間是不是合理。流量:對於好多國內大部分屌絲用戶來說可能還是包月30M,那麼我們必須站在廣大用戶角度來考慮問題了。一個包可以解決的就一個包。
  • 技術中臺之移動平臺安全架構設計
    常見的移動安全問題有哪些,建設移動App時網關如何設計,移動App終端如何建設保障數據安全等等,本文將會為大家一一解答。 手機日誌敏感信息洩露開發人員在開發期都會有列印日誌的習慣,一個不小心就會有敏感信息列印到控制臺,這樣的app上線後就會有很大的安全隱患。不知道大家有沒有在火車站充電樁給手機充過電,這裡的充電口如果被不法分子利用,插入充電的時候,你的手機控制臺的日誌信息就會被完全收集。
  • 兩張圖看懂webim網頁即時聊天系統的架構設計
    im-server服務主動向瀏覽器browserB發送一個消息通知報文messageN。瀏覽器browserB收到messageN後,會通過messageID對messageN進行去重,並且向im-server服務發送一個ack請求報文ackR。im-server服務成功處理後,返回瀏覽器browserB一個ack響應報文ackP。
  • 技術沙龍|賦能企業數位化轉型,移動云云原生應用架構實踐
    在這種情況下傳統架構實現集中化項目非常吃力,難於兼顧各個方面。在這個背景下,集中化項目依託集團PaaS平臺,搭建雲原生微服務架構,業務系統只需要重點關注業務的分析與設計,充分發揮移動雲對於雲原生的支撐能力。中國移動雲能力中心微服務領域技術專家袁健和陸遙分別針對微服務團隊組織方式、敏捷研發管理、微服務API集成,以及基於PaaS平臺的架構設計等重點話題進行了分享。
  • Chinas Automobilmarkt erholt sich im April
    Der Autoabsatz in China ist im April im Vergleich zum Vorjahreszeitraum um 4,4 Prozent auf 2,07 Millionen
  • 音視頻技術開發周刊
    每周一期,縱覽音視頻技術領域的乾貨和新聞投稿:contribute@livevideostack.com。Robert在這次演講中詳細分享了進行一次直播所需要的相關知識和所需準備。
  • 騰訊推出移動端動畫組件PAG,釋放設計生產力!
    傳統的AE設計,總結下來有以下三個核心痛點:研發成本高:每個動效都需要研發通過代碼來還原,單獨排期的特效以及手工配置還原的過程,,包括後續復用及改動都需要大量的研發人力持續投入。視覺動效弱:AE裡有很多複雜動效,使用純代碼還原起來非常困難,設計師只能不斷簡化效果以達到跟開發成本的平衡。為了解決上述痛點,騰訊PCG發布器中臺主導研發了一款動畫開發「神器」——PAG。一、PAG是什麼?
  • 建雲、築池,探索下一代城域網架構——河南移動BRAS雲化、池化實踐
    為此,河南移動聯合華為公司,在城域BRAS雲化及池化方面進行了深度合作創新,從雲化新型設備形態、存量網絡池化不同技術入手,多維度深入研究了未來城域網架構轉型。一、雲化BRAS實踐分析雲化BRAS(CloudBRAS)建立在虛擬化技術和雲計算的基礎上,核心是實現軟硬體解耦和業務自動化。
  • 移動雲Kata Container 2.0 性能調優探索與實踐
    來自阿里雲、螞蟻集團,Intel,中國移動,紅帽等公司的技術專家圍繞內核、容器及虛擬化等雲原生基礎設施技術展開了探討,解析相關開源技術內 幕及社區進展,分享企業落地及實踐經驗。 今天中國移動雲能力中心基礎軟體產品團隊分享的主題是「kata containers 2.0性能調優探索與實踐」。
  • 水底金影|Die Spiegelung des Goldes im Wasser
    昔有痴人往大池所,見水底影有真金像,謂呼有金,即入水中撓泥求覓。疲極不得,還出復坐。
  • 對啊網設計學院:學習移動端設計語言
    新年伊始,收到很多同學的私信,對於移動端的設計語言及規範還是有些混亂,不知道有沒有什麼好的借鑑?我們作為設計師,最主要的就是學習,當然,就是向優秀的人學習。關於借鑑也肯定是向優秀的公司借鑑,前沿的設計理念和完善的設計語言就是我們該掌握的了。下面是小編推薦的一些優秀公司的設計語言,希望對大家有所幫助。
  • Name.im雲殼:二維碼顛覆手機殼
    Name.im雲殼:把個人信息裝進智能配件 在手機殼背後印上二維碼,掃一掃可以識別出個人信息聚合頁面,這就是雲殼(Name.im)現在做的事情
  • 抖音im豆豆豆豆芽是什麼歌 豆芽和豆芽舞是什麼梗
    這讓很多網友表示不解,那抖音im豆芽是什麼歌呢?抖音im豆豆豆豆芽是什麼歌  這首歌曲的名字就叫做「im 豆芽」。豆芽和豆芽舞是什麼梗  其實抖音中的豆芽和豆芽舞是完全不同的兩種生物,其中豆芽指的是抖音的粉絲,一般都會被稱為豆芽。
  • 京東數科發布金融數位化解決方案T1 具有全組件化等三大特性
    據介紹,JDD T1旨在向金融機構提供涵蓋IaaS、PaaS、DaaS、FaaS在內的整體數位化解決方案,依託於「全組件化,無縫集成」、「自主可控、開放融合」、「業務共生、全棧服務」這三大特點,助力金融機構在數位化轉型中建立
  • OPPO網際網路業務多活架構演進和實踐
    多活架構如何落地,如何根據業務需求持續演進?針對複雜的系統,如何提供可靠的監控方案?......帶著這些疑問,InfoQ 記者採訪了 OPPO 網際網路服務系統後端框架團隊負責人羅工。據悉,他於 2015 年加入 OPPO,有十餘年的研發經歷,並在高可用架構、PaaS 平臺和基礎框架研發等方面有豐富的實踐經驗。
  • 移動端網站設計:它是什麼及其重要性
    您需要投資於新的網站建設和設計嗎?還是只是移動端網站的更新? 你甚至需要擔心嗎?---成都雲思禾網絡運營 浪知潮團隊多年來一直在設計適合行動裝置訪問的網站,我們很高興分享對移動所有事物的見解。讓我們從基礎開始:移動設計和常規Web設計有什麼區別,它對您的業務有何影響? 什麼是移動端網站設計?
  • 構建雲原生架構版圖:安信證券服務化平臺實踐
    微服務具有鬆耦合、敏捷迭代、去中心化和彈性架構等優點,但微服務並非「銀彈」,在實踐中主要有以下幾個方面的挑戰: (1)服務拆分粒度。儘管有領域驅動設計(DDD)[3] 等經典方法論指導服務的拆分,但服務是否拆分、拆分粒度仍然是仁者見仁智者見智的話題。不合理的拆分可能導致重複代碼多,服務間依賴關係錯綜複雜等問題。
  • 什麼是微內核架構設計?
    阿里妹導讀:作為一名Java程式設計師,相信同學們都聽說過微內核架構設計,也有自己的理解。那麼微內核是如何被提出來的?微內核在作業系統內核的設計中又有什麼作用?本文從插件化(Plug-in)架構的角度來詮釋微內核架構設計,通過微內核架構和微服務架構的對比,分享其對微服務設計的參考意義。