自去年 LeanCloud 發佈實時通訊(IM)服務以後,基於用戶反饋和工程師對需求的消化和對業務的提煉,上週正式發佈了「實時通訊 2.0 」。設計理念依然是「靈活、解耦、可組合、可定製」,具體能夠參考《實時通訊開發指南》,瞭解 LeanCloud 實時通訊的基本概念和模型。html
能夠到 LeanCloud 官方下載點下載 LeanCloud IM SDK v2 版本。將下載到的 jar 包加入工程便可。android
若是您以爲一點點閱讀文檔較慢,能夠直接看咱們的「Demo 代碼」,而且下載本身運行一下試試看。git
咱們先從最簡單的環節入手,看看怎麼用 LeanCloud IM SDK 實現一對一文本聊天。github
和 LeanCloud 其餘服務同樣,實時聊天服務的初始化也是在 Application 的 onCreate
方法中進行的:web
public class MyApplication extends Application{ public void onCreate(){ ... AVOSCloud.initialize(this,"{{appId}}","{{appKey}}"); ... } }
而且在AndroidManifest.xml中間聲明:算法
<manifest> ... <application android:name=".MyApplication" ....> ... <service android:name="com.avos.avoscloud.PushService" /> <receiver android:name="com.avos.avoscloud.AVBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.USER_PRESENT" /> </intent-filter> </receiver> ... </application> </manifest>
接下來咱們開始一步一步接入聊天服務。數組
用戶在開始聊天以前,須要先登陸 LeanCloud 雲端。這個登陸並不須要用戶名、密碼認證,只是與 LeanCloud 雲端創建一個長鏈接,因此只須要傳入一個可惟一標識當前用戶的 clientId
便可。緩存
在本 SDK 中,咱們會爲每個終端用戶開啓一個 AVIMClient
實例,獲取這一實例的方法位於 com.avos.avoscloud.im.v2.AVIMClient
類中,其聲明以下:安全
public static AVIMClient getInstance(String clientId)
SDK 內部會爲每個 clientId 建立惟一的 AVIMClient
實例,同一個 clientId 屢次調用該方法,獲得的都是同一個結果。因此若是要支持同一個客戶端內多帳號登陸,只要使用不一樣的 clientId 屢次調用該方法便可。LeanCloud IM SDK 自己是支持多帳戶同時登陸的。服務器
獲得 AVIMClient
實例以後,咱們須要登陸 LeanCloud 雲端。這是經過調用 AVIMClient 的 open
方法實現的,其聲明以下:
public void open(final AVIMClientCallback callback)
open
函數返回的時候,會把 AVIMClient
實例和 AVException
信息(若是發生錯誤的話)傳給 AVIMClientCallback
回調接口。
好了,咱們如今來實際看一下這個過程如何實現。假定聊天發起方名叫 Tom,爲直觀起見,咱們使用用戶名來做爲 clientId
登陸聊天系統(LeanCloud 雲端只要求 clientId
在應用內惟一便可,具體用什麼數據由應用層決定),代碼以下:
AVIMClient imClient = AVIMClient.getInstance("Tom"); imClient.open(new IMClientCallback(){ @Override public void done(AVIMClient client, AVException e) { if (null != e) { // 出錯了,多是網絡問題沒法鏈接 LeanCloud 雲端,請檢查網絡以後重試。 // 此時聊天服務不可用。 e.printStackTrace(); } else { // 成功登陸,能夠開始進行聊天了(假設爲 MainActivity)。 Intent intent = new Intent(currentActivity, MainActivity.class); currentActivity.startActivity(intent); }; } });
在本版本 IM SDK 中,開始聊天以前,須要先建立或者加入一個「對話」(AVIMConversation),全部消息都是由某個 client 發往一個「對話」,「對話」內的全部成員會實時收到新消息。
對話支持以下默認屬性:
咱們能夠經過 AVIMClient
來建立一個對話,其函數聲明爲:
//指定成員、自定義屬性,建立對話 public void createConversation(final List<String> conversationMembers, final Map<String, Object> attributes, final AVIMConversationCreatedCallback callback); //指定成員、名字、自定義屬性,建立對話 public void createConversation(final List<String> conversationMembers, String name, final Map<String, Object> attributes, final AVIMConversationCreatedCallback callback); //指定成員、名字、自定義屬性和對話標誌,建立對話 public void createConversation(final List<String> conversationMembers, String name, final Map<String, Object> attributes, final boolean isTransient, final AVIMConversationCreatedCallback callback);
各參數的含義以下:
接下來咱們看看實際如何建立一個對話。假定咱們要跟「Bob」這個用戶進行聊天,咱們先建立一個對話,代碼以下:
List<String> clientIds = new ArrayList<String>(); clientIds.add("Tom"); clientIds.add("Bob"); // 咱們給對話增長一個自定義屬性 type,表示單聊仍是羣聊 // 常量定義: // int ConversationType_OneOne = 0; // 兩我的之間的單聊 // int ConversationType_Group = 1; // 多人之間的羣聊 Map<String, Object> attr = new HashMap<String, Object>(); attr.put("type", ConversationType_OneOne); imClient.createConversation(clientIds, attr, new AVIMConversationCreatedCallback() { @Override public void done(AVIMConversation conversation, AVException e) { if (null != conversation) { // 成功了,這時候能夠顯示對話的 Activity 頁面(假定爲 ChatActivity)了。 Intent intent = new Intent(this, ChatActivity.class); Intent.putExtra(「conversation」, conversation); startActivity(intent); } } });
創建的「對話」在控制檯怎麼查看
如你所見,咱們建立一個對話的時候,指定了成員(Tom 和 Bob)和一個額外的屬性({type: 0})。這些數據保存到雲端後,你在 控制檯 -> 存儲 -> 數據 裏面會看到,_Conversation 表中增長了一條記錄,新記錄的
m
屬性值爲["Tom", "Bob"]
,attr
屬性值爲{"type":0}
。如你所料,m
屬性就是對應着成員列表,attr
屬性就是用戶增長的額外屬性值(以對象的形式存儲)。
經過 AVIMConversation
的 sendMessage
族方法,能夠將消息發往目標對話。方法聲明以下:
// 直接發送一條消息,在大多數狀況下,你該調用這個方法 public void sendMessage(AVIMMessage message, final AVIMConversationCallback callback); // 發送消息時,指定特殊的消息選項,用來發送特別的消息 public void sendMessage(final AVIMMessage message, final int messageFlag, final AVIMConversationCallback callback);
各參數的含義以下:
接下來咱們試着發送一條普通文本消息。示例代碼以下:
AVIMMessage message = new AVIMMessage(); message.setContent("hello"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("發送成功,msgId=" + message.getMessageId()); } } });
好了,這樣一條消息就發送過去了。可是問題來了,對於「Bob」而言,他怎麼才能收到別人發給他的消息呢?
在 Bob 這一端,要能接收到消息,須要以下幾步:
1,進行初始化;
2,準備好本身的 AVIMMessageHandler
,響應新消息到達通知。
在本版本 IM SDK 中,咱們設計的框架是將消息類型與具體的 handler 類綁定起來,這樣開發者能夠爲不一樣類型的消息設置不一樣的 handler,處理起來更加靈活自由。這一綁定過程是經過 com.avos.avoscloud.im.v2.AVIMMessageManager
類的 void registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)
函數實現的。AVIMMessageManager
類中還有一個方法 void registerDefaultMessageHandler(AVIMMessageHandler handler)
則用來指定全局默認的消息處理 handler。
AVIMMessageHandler
的主要函數以下:
public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client);
對於 Tom 發過來的消息,要接收並顯示出來,咱們只需實現 onMessage
方法便可,示例代碼以下:
class CustomMessageHandler extends AVIMMessageHandler { @Override public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 新消息到來了。在這裏增長你本身的處理代碼。 String msgContent = message.getContent(); Logger.d(conversation.getConversationid() + " 收到一條新消息:" + msgContent); } }
3,進行登陸,代碼也與發送端同樣。
Bob 這邊要接收到 Tom 發過來的消息,其完整流程以下:
// 自定義消息響應類 class CustomMessageHandler extends AVIMMessageHandler { @Override public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 新消息到來了。在這裏增長你本身的處理代碼。 String msgContent = message.getContent(); Logger.d(conversation.getConversationid() + " 收到一條新消息:" + msgContent); } } // application 的初始化部分 public void onCreate(){ ... AVOSCloud.initialize(this,"{{appId}}","{{appKey}}"); AVIMMessageManager.registerDefaultMessageHandler(new CustomMessageHandler()); ... } // 用戶登陸部分 AVIMClient imClient = AVIMClient.getInstance("Bob"); imClient.open(new IMClientCallback(){ @Override public void done(AVIMClient client, AVException e) { if (null != e) { // 出錯了,多是網絡問題沒法鏈接 LeanCloud 雲端,請檢查網絡以後重試。 // 此時聊天服務不可用。 e.printStackTrace(); } else { // 成功登陸,能夠開始進行聊天了。 }; } });
注意!
AVIMMessageManager.registerDefaultMessageHandler()
必定要在AVIMClient.open()
以前調用,不然可能致使服務器發回來的部分消息丟失。
在 app 退出的時候,或者切換用戶的時候,咱們須要斷開與 LeanCloud 實時通訊服務的長鏈接,這時候須要調用 AVIMClient.close(final AVIMClientCallback callback)
函數。通常狀況下,這個函數都會關閉鏈接並馬上返回,這時候 Leancloud 實時通訊服務端就會認爲當前用戶已經下線。
從上面的例子中能夠看到,要接收到別人給你發送的消息,須要重載 AVIMMessageHandler 類。從 v2 版開始,LeanCloud IM SDK 大量採用回調來反饋操做結果,可是對於一些被動的消息通知,則仍是採用接口來實現的,包括:
LeanCloud IM SDK 內部使用了三種接口來響應這些事件。
主要用來處理網絡變化事件,接口定義在 AVIMClientEventHandler
,主要函數爲:
void onConnectionPaused(AVIMClient client)
指網絡鏈接斷開事件發生,此時聊天服務不可用。void onConnectionResume(AVIMClient client)
指網絡鏈接恢復正常,此時聊天服務變得可用。在網絡中斷的狀況下,全部的消息收發和對話操做都會出現問題。
經過 AVIMClient.setClientEventHandler(AVIMClientEventHandler handler)
能夠設定全局的 ClientEventHandler。
主要用來處理對話中成員變化的事件,接口定義在 AVIMConversationEventHandler
,主要函數爲:
onMemberLeft(AVIMClient client, AVIMConversation conversation, List<String> members, String kickedBy)
對話中有成員離開時全部剩餘成員都會收到這一通知。參數意義說明以下:onMemberJoined(AVIMClient client, AVIMConversation conversation, List<String> members, String invitedBy)
對話中有新成員加入時全部成員都會收到這一通知。參數意義說明以下:onKicked(AVIMClient client, AVIMConversation conversation, String kickedBy)
當前用戶被踢出對話的通知,參數意義說明以下:onInvited(AVIMClient client, AVIMConversation conversation, String operator)
當前用戶被邀請加入對話的通知。參數意義說明以下:經過 AVIMMessageManager.setConversationEventHandler(AVIMConversationEventHandler handler)
能夠設置全局的 ConversationEventHandler。
主要用來處理新消息到達事件,接口定義在 MessageHandler
,AVIMMessageHandler
是一個空的實現類,咱們應該經過重載 AVIMMessageHandler 的相關方法來完成消息處理。主要的方法有:
onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client)
指接收到新的消息。參數意義說明以下:onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client)
本身發送的消息被對方接收時會收到此通知,各參數意義同上。經過 AVIMMessageManager.registerDefaultMessageHandler(handler)
能夠設置全局的 MessageHandler。
咱們實現這三類接口,就能夠處理全部的通知消息了。示例代碼以下:
// 處理網絡狀態變化事件 class CustomNetworkHandler extends AVIMClientEventHandler { @Override public void onConnectionPaused(AVIMClient client) { // 請按本身需求改寫 Logger.d("connect paused"); } @Override public void onConnectionResume(AVIMClient client) { // 請按本身需求改寫 Logger.d("connect resume"); } } // 處理對話成員變化事件 class CustomConversationHandler extends AVIMConversationEventHandler { public private Context gContext = null; private void toast(String str) { Toast.makeText(gContext, str, Toast.LENGTH_SHORT).show(); } private void toast(Context context, String str) { Toast.makeText(context, str, Toast.LENGTH_SHORT).show(); } @Override public void onMemberLeft(AVIMClient client, AVIMConversation conversation, List<String> members, String kickedBy) { // 請按本身需求改寫 toast(MsgUtils.nameByUserIds(members) + " left, kicked by " + MsgUtils.nameByUserId(kickedBy)); //注:MsgUtils 是一個輔助類,nameByUserIds 用來將 userId 轉換成用戶名 } @Override public void onMemberJoined(AVIMClient client, AVIMConversation conversation, List<String> members, String invitedBy) { // 請按本身需求改寫 toast(MsgUtils.nameByUserIds(members) + " joined , invited by " + MsgUtils.nameByUserId(invitedBy)); //注:MsgUtils 是一個輔助類,nameByUserIds 用來將 userId 轉換成用戶名 } @Override public void onKicked(AVIMClient client, AVIMConversation conversation, String kickedBy) { // 請按本身需求改寫 toast("you are kicked by " + MsgUtils.nameByUserId(kickedBy)); } @Override public void onInvited(AVIMClient client, AVIMConversation conversation, String operator) { // 請按本身需求改寫 toast("you are invited by " + MsgUtils.nameByUserId(operator)); } }; // 處理新消息到達事件 class CustomMsgHandler extends AVIMMessageHandler { @Override public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 請按本身需求改寫 String msgContent = message.getContent(); Logger.d(conversation.getConversationid() + " 收到一條新消息:" + msgContent); } @Override public void onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client) { // 請按本身需求改寫 Logger.d("發往對話 " + conversation.getConversationid() + " 的消息 "+ message.getMessageId() +" 已被接收"); } } // 設置事件響應接口 AVIMClient.setClientEventHandler(new CustomNetworkHandler()); AVIMMessageManager.setConversationEventHandler(new CustomConversationHandler()); AVIMMessageManager.registerDefaultMessageHandler(new CustomMsgHandler());
上面的代碼演示瞭如何發送簡單文本信息,可是如今的交互方式已經愈來愈多樣化,圖像、語音、視頻已經是很是廣泛的消息類型。v2 版的 IM SDK 已經能夠很好地支持這些富媒體消息,具體說明以下:
咱們默認支持文本、圖像、語音、視頻、文件、地理位置等富媒體消息,全部這些消息類型都有一個共同的基類:AVIMTypedMessage,其聲明爲
public abstract class AVIMTypedMessage extends AVIMMessage { public AVIMTypedMessage(); public int getMessageType(); @Override public final String getContent(); @Override public final void setContent(String content); }
這裏咱們爲每一種富媒體消息定義了一個消息類型,LeanCloud SDK 自身使用的類型是負數(以下面列表所示),全部正數留給開發者自定義擴展類型使用,0 做爲「沒有類型」被保留起來。
消息 | 對應的消息類型 |
---|---|
文本消息 | -1 |
圖像消息 | -2 |
音頻消息 | -3 |
視頻消息 | -4 |
位置消息 | -5 |
文件消息 | -6 |
AVIMTypedMessage 子類,表示通常的文本消息,其聲明爲
public class AVIMTextMessage extends AVIMTypedMessage { public String getText(); public void setText(String text); public Map<String, Object> getAttrs(); public void setAttrs(Map<String, Object> attr); }
能夠看到,對於文本消息,主要的屬性有 text
和 attrs
兩個,經過簡單的 getter/setter 就能夠訪問到。要發送文本消息,示例代碼爲:
AVIMTextMessage message = new AVIMTextMessage(); message.setText("hello"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("message sent."); } } });
AVIMTypedMessage 子類,用來發送帶附件的消息,開發者能夠用它來發送「離線文件」。對於此類消息,LeanCloud IM SDK 內部會先把文件上傳到 LeanCloud 文件存儲服務器(自帶 CDN 功能),而後把文件元數據(url,文件大小等等)放在消息包內發送到 LeanCloud 實時通訊服務端。其構造函數聲明爲:
// 傳入本地文件路徑,構造消息對象 public AVIMFileessage(String localPath) throws FileNotFoundException, IOException; // 傳入本地文件,構造消息對象 public AVIMFileMessage(File localFile) throws FileNotFoundException, IOException; // 傳入 AVFile 實例,構造消息對象 public AVIMFileMessage(AVFile file);
與文本消息相似,文件消息也支持附帶文本和其餘自定義屬性,能夠經過以下方法添加 / 獲取更多信息:
發送文件消息的示例代碼爲:
String localZipfilePath; try { AVIMFileMessage message = new AVIMFileMessage(localZipfilePath); message.setText("這是你要的文檔"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到這樣消息以後,開發者能夠經過如下方法,獲取到文件元數據(size 等)和一個包含二進制數據的 AVFile 對象:
AVFile getAVFile()
方法會返回一個二進制文件的 AVFile 實例,以後能夠經過 AVFile 來完成數據下載或者其餘操做,具體能夠參見 AVFile 說明 String getFileUrl()
方法會返回二進制文件的 urllong getSize()
方法會返回二進制文件的實際大小(單位:byte)Map<String, Object> getFileMetaData()
能夠獲取二進制文件的其餘元數據信息。String getText()
方法會返回隨文件一塊兒發送的文本信息。AVIMFileMessage 子類,專門用來發送圖像和附帶文本的混合消息,其構造函數聲明爲:
// 傳入本地文件路徑,構造消息對象 public AVIMImageMessage(String localPath) throws FileNotFoundException, IOException; // 傳入本地文件,構造消息對象 public AVIMImageMessage(File localFile) throws FileNotFoundException, IOException; // 傳入 AVFile 實例,構造消息對象 public AVIMImageMessage(AVFile file);
發送圖像消息的示例代碼爲:
String localImagePath; try { AVIMImageMessage message = new AVIMImageMessage(localImagePath); message.setText("你說我好看不?"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到這樣消息以後,開發者能夠經過以下方法,獲取到若干圖像元數據(width,height,圖像 size)和一個包含圖像數據的 AVFile 對象:
int getWidth()
方法會返回圖像的寬度(單位:pixel)int getHeight()
方法會返回圖像的高度(單位:pixel)AVFile getAVFile()
(繼承自 AVIMFileMessage)方法會返回一個圖像文件的 AVFile 實例String getFileUrl()
(繼承自 AVIMFileMessage)方法會返回圖像文件的 urllong getSize()
(繼承自 AVIMFileMessage)方法會返回圖像文件的實際大小(單位:byte)String getText()
(繼承自 AVIMFileMessage)方法會返回隨圖像一塊兒發送的文本信息。Map<String, Object> getFileMetaData()
(繼承自 AVIMFileMessage)能夠獲取圖像的其餘元數據信息。AVIMFileMessage 子類,專門用來發送語音和附帶文本的混合消息,其構造函數聲明爲:
// 傳入本地文件路徑,構造消息對象 public AVIMAudioMessage(String localPath) throws FileNotFoundException, IOException; // 傳入本地文件,構造消息對象 public AVIMAudioMessage(File localFile) throws FileNotFoundException, IOException; // 傳入 AVFile 實例,構造消息對象 public AVIMAudioMessage(AVFile file);
發送音頻消息的示例代碼爲:
String localAudioPath; try { AVIMAudioMessage message = new AVIMAudioMessage(localAudioPath); message.setText("聽聽我唱的小蘋果:)"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到這樣消息以後,開發者能夠經過以下方法,獲取到若干音頻元數據(時長 duration、音頻 size)和一個包含音頻數據的 AVFile 對象:
double getDuration()
方法會返回音頻的長度(單位:秒)AVFile getAVFile()
(繼承自 AVIMFileMessage)方法會返回一個音頻文件的 AVFile 實例String getFileUrl()
(繼承自 AVIMFileMessage)方法會返回音頻文件的 urllong getSize()
(繼承自 AVIMFileMessage)方法會返回音頻文件的實際大小(單位:byte)String getText()
(繼承自 AVIMFileMessage)方法會返回隨音頻一塊兒發送的文本信息。Map<String, Object> getFileMetaData()
(繼承自 AVIMFileMessage)能夠獲取音頻的其餘元數據信息。AVIMFileMessage 子類,專門用來發送視頻和附帶文本的混合消息,其構造函數聲明爲:
// 傳入本地文件路徑,構造消息對象 public AVIMVideoMessage(String localPath) throws FileNotFoundException, IOException; // 傳入本地文件,構造消息對象 public AVIMVideoMessage(File localFile) throws FileNotFoundException, IOException; // 傳入 AVFile 文件,構造消息對象 public AVIMVideoMessage(AVFile file);
發送視頻消息的示例代碼爲:
String localVideoPath; try { AVIMVideoMessage message = new AVIMVideoMessage(localVideoPath); message.setText("敢不敢跟我比一比"); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } }); } catch (Exception ex) { }
接收到這樣消息以後,開發者能夠能夠經過以下方法,獲取到若干視頻元數據(時長 duration、視頻 size)和一個包含視頻數據的 AVFile 對象:
double getDuration()
方法會返回視頻的長度(單位:秒)AVFile getAVFile()
(繼承自 AVIMFileMessage)方法會返回一個視頻文件的 AVFile 實例String getFileUrl()
(繼承自 AVIMFileMessage)方法會返回視頻文件的 urllong getSize()
(繼承自 AVIMFileMessage)方法會返回視頻文件的實際大小(單位:byte)String getText()
(繼承自 AVIMFileMessage)方法會返回隨視頻一塊兒發送的文本信息。Map<String, Object> getFileMetaData()
(繼承自 AVIMFileMessage)能夠獲取視頻的其餘元數據信息。AVIMTypedMessage 子類,支持發送地理位置信息和附帶文本的混合消息,其聲明爲:
public class AVIMLocationMessage extends AVIMTypedMessage { public String getText(); public void setText(String text); public Map<String, Object> getAttrs(); public void setAttrs(Map<String, Object> attr); public AVGeoPoint getLocation(); public void setLocation(AVGeoPoint location); }
與文本消息相似,地理位置消息只是增長了一個 AVGeoPoint 的 Location 屬性。要發送位置消息的示例代碼爲:
AVIMLocationMessage message = new AVIMLocationMessage(); message.setText("快點過來!"); message.setLocation(new AVGeoPoint(15.9, 56.4)); conversation.sendMessage(message, new AVIMConversationCallback() { @Override public void done(AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { Logger.d("message sent"); } } });
接收到這樣的消息以後,開發者能夠獲取到具體的地理位置數據。
新版 LeanCloud IM SDK 內部封裝了對富媒體消息的支持,全部富媒體消息都是從 AVIMTypedMessage 派生出來的。發送的時候能夠直接調用 conversation.sendMessage()
函數。在接收端,咱們也專門增長了一類回調接口 AVIMTypedMessageHandler,其定義爲:
public class AVIMTypedMessageHandler<T extends AVIMTypedMessage> extends MessageHandler<T> { @Override public void onMessage(T message, AVIMConversation conversation, AVIMClient client); @Override public void onMessageReceipt(T message, AVIMConversation conversation, AVIMClient client); }
開發者能夠編寫本身的消息處理 handler,而後調用 AVIMMessageManager.registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)
函數來註冊目標 handler。
接收端對於富媒體消息的通知處理的示例代碼以下:
class MsgHandler extends AVIMTypedMessageHandler<AVIMTypedMessage> { @Override public void onMessage(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) { // 請按本身需求改寫 switch(message.getMessageType()) { case AVIMReservedMessageType.TextMessageType: AVIMTextMessage textMsg = (AVIMTextMessage)message; Logger.d("收到文本消息:" + textMsg.getText() + ", msgId:" + textMsg.getMessageId()); break; case AVIMReservedMessageType.FileMessageType: AVIMFileMessage fileMsg = (AVIMFileMessage)message; Logger.id("收到文件消息。msgId=" + fileMsg.getMessageId() + ", url=" + fileMsg.getFileUrl() + ", size=" + fileMsg.getSize()); break; case AVIMReservedMessageType.ImageMessageType: AVIMImageMessage imageMsg = (AVIMImageMessage)message; Logger.id("收到圖片消息。msgId=" + imageMsg.getMessageId() + ", url=" + imageMsg.getFileUrl() + ", width=" + imageMsg.getWidth() + ", height=" + imageMsg.getHeight()); break; case AVIMReservedMessageType.AudioMessageType: AVIMAudioMessage audioMsg = (AVIMAudioMessage)message; Logger.id("收到音頻消息。msgId=" + audioMsg.getMessageId() + ", url=" + audioMsg.getFileUrl() + ", duration=" + audioMsg.getDuration()); break; case AVIMReservedMessageType.VideoMessageType: AVIMVideoMessage videoMsg = (AVIMAudioMessage)message; Logger.id("收到視頻消息。msgId=" + videoMsg.getMessageId() + ", url=" + videoMsg.getFileUrl() + ", duration=" + videoMsg.getDuration()); break; case AVIMReservedMessageType.LocationMessageType: AVIMLocationMessage locMsg = (AVIMLocationMessage)message; Logger.id("收到位置消息。msgId=" + locMsg.getMessageId() + ", latitude=" + locMsg.getLocation().getLatitude() + ", longitude=" + locMsg.getLocation().getLongitude()); break; } } @Override public void onMessageReceipt(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) { // 請加入你本身須要的邏輯... } } MsgHandler msgHandler = new MsgHandler(); AVIMMessageManager.registerMessageHandler(AVIMTypedMessage.class, msgHandler);
LeanCloud IM SDK 內部消息分發的邏輯是這樣的:
這樣一來,在開發者爲 TypedMessage(及其子類) 指定了專門的 handler,也指定了全局的 defaultHandler 了的時候,若是發送端發送的是通用的 AVIMMessage 消息,那麼接受端就是 AVIMMessageManager.registerDefaultMessageHandler()中指定的 handler 被調用;若是發送的是 AVIMTypedMessage(及其子類)的消息,那麼接受端就是 AVIMMessageManager.registerMessageHandler()中指定的 handler 被調用。
繼承於 AVIMTypedMessage,開發者也能夠擴展本身的富媒體消息。其要求和步驟是:
123
)由開發者本身決定(LeanCloud 內建的消息類型使用負數,全部正數都預留給開發者擴展使用)。AVIMMessageManager.registerAVIMMessageType(Class<? extends AVIMTypedMessage> messageType)
函數進行類型註冊AVIMMessageManager.registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler)
函數進行消息處理 handler 註冊。AVIMTextMessage 的源碼以下,可供參考:
@AVIMMessageType(type = -1) public class AVIMTextMessage extends AVIMTypedMessage { @AVIMMessageField(name = "_lctext") String text; @AVIMMessageField(name = "_lcattrs") Map<String, Object> attrs; public String getText() { return this.text; } public void setText(String text) { this.text = text; } public Map<String, Object> getAttrs() { return this.attrs; } public void setAttrs(Map<String, Object> attr) { this.attrs = attr; } }
與前面的單聊相似,羣組聊天也須要先創建一個對話(AVIMConversation),而後發送、接收新的消息。
和單聊相似,創建一個多人聊天的羣組也是很簡單的。例如:
Map<String, Object> attr = new HashMap<String, Object>(); attr.put("type", ConversationType_Group); imClient.createConversation(clientIds, attr, new AVIMConversationCreatedCallback() { @Override public void done(AVIMConversation conversation, AVException e) { if (null != conversation) { // 成功了! Intent intent = new Intent(currentActivity, ChatActivity.class); Intent.putExtra(「conversation」, conversation); currentActivity.startActivity(intent); } } });
成功以後,咱們就能夠進入聊天界面了。
若是是其餘人,須要主動加入到並不是本身建立的羣組裏面,該怎麼作到呢?
AVIMConversation 有一個 join 方法,能夠用來主動加入一個羣組,其聲明爲:
void join(AVIMConversationCallback callback)
這裏參數的含義以下:
假定用戶 Jade 但願加入上面的羣組,其示例代碼爲:
// 以前是 Jade 登陸的代碼 conversation.join(new AVIMConversationCallback(){ @Override public void done(AVException e) { if (null != e) { // 出錯了:( } else { // 成功,此時能夠進入聊天界面了。。。 Intent intent = new Intent(currentActivity, ChatActivity.class); Intent.putExtra(「conversation」, conversation); currentActivity.startActivity(intent); } } });
發送消息很是簡單,與前面單聊的場景同樣。
咱們會注意到,AVIMConversation 還有一個發送消息的方法:
public void sendMessage(final AVIMMessage message, final int messageFlag, final AVIMConversationCallback callback)
而這裏 flag 的定義有以下三種類型:
public void onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client)
函數被調用的時機)。接收一個羣組的消息,與接收單聊的消息也是同樣的。
在查詢到聊天室成員以後,可讓用戶邀請一些本身的朋友加入,做爲管理員也能夠剔除一些「可怕」的成員。
加入新成員的 API 以下:
void addMembers(final List<String> friendsList, final AVIMConversationCallback callback)
這裏各參數的含義以下:
咱們試着在剛纔的對話中邀請幾我的:
// 假設須要邀請 Alex,Ben,Chad 三人加入對話 List<String> userIds = new ArrayList<String>(); userIds.add("Alex"); userIds.add("Ben"); userIds.add("Chad"); conversation.addMembers(userIds, new AVIMConversationCallback() { @Override public void done(AVException error) { if (null != error) { // 加入失敗,報錯. error.printStackTrace(); } else { // 發出邀請,此後新成員就能夠看到這個對話中的全部消息了。 Logger.d("invited."); } } });
邀請成功之後,相關方收到通知的時序是這樣的:
操做者(管理員) 被邀請者 其餘人 1, 發出請求 addMembers 2, 收到 onInvited 通知 3, 收到 onMemberJoined 通知 收到 onMemberJoined 通知 收到 onMemberJoined 通知
與加人相似,踢人的 API 聲明以下:
void kickMembers(final List<String> friendsList, final AVIMConversationCallback callback)
參數含義同上。咱們試着把 Alex 踢出去:
List<String> userIds = new ArrayList<String>(); userIds.add("Alex"); conversation.kickMembers(userIds, new AVIMConversationCallback() { @Override public void done(AVException error) { if (null != error) { // 失敗,報錯. error.printStackTrace(); } else { // 成功。 Logger.d("kicked."); } } });
踢人時,相關方收到通知的時序以下:
操做者(管理員) 被踢者 其餘人 1, 發出請求 kickMembers 2, 收到 onKicked 通知 3, 收到 onMemberLeft 通知 收到 onMemberLeft 通知
注意!
若是邀請、踢人操做發生的時候,被邀請者/被踢者當前不在線,那麼通知消息並不會被離線緩存,因此他們再上線的時候將不會收到通知。
任何成員,均可以主動退出一個羣組。AVIMConversation 有一個 quit 方法,其聲明爲:
void quit(AVIMConversationCallback callback)
這裏參數的含義以下:
退出羣組以後,該羣組內發生的任何事件或者消息,都不會再發到當前用戶,當前用戶也不能往羣組內發送任何消息。
假設用戶 Jade 又想退出上面的羣組了,其示例代碼爲:
// 以前是 Jade 登陸的代碼 conversation.quit(new AVIMConversationCallback(){ @Override public void done(AVException e) { if (null != e) { // 出錯了:( } else { // 成功,這下清靜了。。。 currentActivity.finish(); } } });
LeanMessage 會將非暫態消息自動保存在雲端,以後開發者能夠經過 AVIMConversation 來獲取該對話的全部歷史消息。獲取歷史消息的 API 以下:
// 查詢當前對話的最新消息,默認返回 100 條 void queryMessages(final AVIMHistoryMessageCallback callback); // 查詢當前對話的最新消息,返回 limit 指定的條數 void queryMessages(int limit, final AVIMHistoryMessageCallback callback); // 前向查詢當前對話的歷史消息,msgId/timestamp 指定消息的起點,limit 指定須要的結果條數 void queryMessages(String msgId, long timestamp, int limit, final AVIMHistoryMessageCallback callback);
各參數含義以下:
經過這一 API 拿到的消息就是 AVIMMessage 或者 AVIMTypedMessage 實例數組,開發者能夠像以前收到新消息通知同樣處理。示例代碼以下:
String oldestMsgId; long oldestMsgTimestamp; conversation.queryMessages(oldestMsgId,oldestMsgTimestamp, limit, new AVIMHistoryMessageCallback(){ @Override public void done(List<AVIMMessage> messages, AVException e) { if (null != e) { // 出錯了:( } else { // 成功,能夠將消息加入緩存,同時更新 UI } } });
注意:
翻頁加載獲取歷史消息的時候,LeanCloud 雲端是從某條消息開始,往前查找開發者指定的 N 條消息,返回給客戶端。爲此,獲取歷史消息須要傳入三個參數:起始消息的 msgId,起始消息的發送時間戳,須要獲取的消息條數。
無論是單聊仍是羣聊,當用戶 A 發出消息後,若是目標對話中有部分用戶當前不在線,LeanCloud 雲端能夠提供離線推送的方式來提醒用戶。這一功能默認是關閉的,你能夠在 LeanCloud 應用控制檯中開啓它。開啓方法以下:
這樣 iOS 平臺上的用戶就能夠收到 Push Notification 了(固然,前提是應用自己申請到了 RemoteNotification 權限,也將正確的推送證書上傳到了 LeanCloud 控制檯)。
無論是單聊仍是羣聊,對於發往普通的 Conversation 的普通消息,若是接收方當前不在線,LeanCloud 雲端支持經過 Push Notification 的方式進行提醒。通常狀況下這都是很好的,可是若是某個羣組特別活躍,那離線用戶就會收到過多的推送,會造成不小的干擾。
對此 LeanCloud IM 服務也容許單個用戶來關閉/打開某個對話的離線推送功能。
無論是單聊,仍是羣聊,在 LeanCloud IM SDK 裏面都是對話(Conversation)。咱們給對話設置了以下幾種屬性:
咱們提供了專門的類,來搜索特定的羣組:經過 imClient.getQuery()
獲得一個 AVIMConversationQuery
實例,而後調用 AVIMConversationQuery.whereXXX
系列方法來增長約束條件。
AVIMConversationQuery
的使用方法與 AVQuery 同樣,例如要搜索當前登陸用戶參與的全部羣聊對話,其代碼爲
// 搜索 Tom 參與的全部羣組對話 List<String> clients = new ArrayList<String>(); clients.add("Tom"); AVIMConversationQuery conversationQuery = imClient.getQuery(); conversationQuery.containsMember(clients); // 以前有常量定義: // const int ConversationType_OneOne = 0; // const int ConversationType_Group = 1; conversationQuery.whereEqualTo("attr.type", ConversationType_Group); conversationQuery.findInBackground(new AVIMConversationQueryCallback(){ @Override public void done(List<AVIMConversation> conversations, AVException e) { if (null != e) { // 出錯了。。。 e.printStackTrace(); } else { if (null != conversation) { Logger.d("找到了符合條件的 " + conversations.size() + " 個對話"); } else { Logger.d("沒有找到符合條件的對話"); } } } });
注意:
這裏 conversationQuery.containsMember()
表示對話的成員中至少包含這些人員,可用來根據部分紅員查找對話;
與此相似的還有一個 conversationQuery.withMembers()
則表示有且僅有這些成員,用來根據全部成員查找目標對話;conversationQuery.whereXXX()
系列方法可用來限定對話名稱和自定義屬性,這裏要強調的一點是,對於自定義屬性的約束條件,屬性名必定要以 attr
開頭,如上例所示,限定額外的 type
條件的時候須要指定的屬性名是 attr.type
。具體能夠參看其頭文件。
開放聊天室(也叫「暫態」對話)能夠用於不少地方,譬如彈幕、直播等等。在 LeanCloud IM SDK 中,開放聊天室是一類特殊的羣組,它也支持建立、加入/踢出成員等操做,消息記錄會被保存並可供獲取;與普通羣組不同的地方具體體現爲:
和普通的羣組相似,創建一個開放聊天室也是很簡單的,只是在 AVIMClient.createConversation(conversationMembers, name, attributes, isTransient, callback)
中咱們須要傳入 isTransient=true
選項。例如:
Map<String, Object> attr = new HashMap<String, Object>(); attr.put("type", ConversationType_Group); imClient.createConversation(clientIds, name, attr, true, new AVIMConversationCreatedCallback() { @Override public void done(AVIMConversation conversation, AVException e) { if (null != conversation) { // 成功了,進入聊天室 Intent intent = new Intent(currentActivity, ChatActivity.class); Intent.putExtra(「conversation」, conversation); currentActivity.startActivity(intent); } } });
建立成功以後,咱們就能夠進入聊天界面了。開放聊天室的其餘操做,都與普通羣組操做同樣。
只要應用層不作限制,任何終端用戶均可以加入開放聊天室,這部分邏輯與以前的加入羣組同樣。
一樣的,離開任何「對話」(不論普通仍是「暫態」),調用 AVIMConversation.quit(callback)
函數便可,這裏再也不贅述。
對於開放聊天室來講,與普通羣組有很大一點不一樣,就是沒有了參與用戶列表,取而代之的是能夠查詢實時在線人數。AVIMConversation.getMemberCount()
函數能夠完成這一功能,其聲明以下:
void getMemberCount(AVIMConversationMemberCountCallback callback)
參數含義說明以下:
這部分的示例代碼以下:
conversation.getMemberCount(new AVIMConversationMemberCountCallback(){ @Override public void done(Integer memberCount, AVException e) { if (null != e) { // 出錯了:( } else { // 成功,此時 memberCount 的數值就是實時在線人數 } } });
爲了知足開發者對權限和認證的要求,LeanCloud 還設計了操做簽名的機制。咱們能夠在 LeanCloud 應用控制檯中的「設置」->「應用選項」->「聊天推送」下面勾選「聊天服務簽名認證」來啓用簽名(強烈推薦這樣作)。啓用後,全部的用戶登陸、對話建立/加入、邀請成員、踢出成員等操做都須要驗證簽名,這樣開發者就能夠對消息進行充分的控制。
客戶端這邊究竟該如何使用呢?咱們只須要實現 SignatureFactory 接口,而後在用戶登陸以前,把這個接口的實例賦值給 AVIMClient 便可(AVIMClient.setSignatureFactory(factory)
)。
設定了 signatureFactory 以後,對於須要鑑權的操做,LeanCloud IM SDK 與服務器端通信的時候都會帶上應用本身生成的 Signature 信息,LeanCloud 雲端會使用 app 的 masterKey 來驗證信息的有效性,保證聊天渠道的安全。
對於 SignatureFactory 接口,咱們只須要實現這兩個函數便可:
/** * 實現一個基礎簽名方法 其中的簽名算法會在SessionManager和AVIMClient(V2)中被使用 */ public Signature createSignature(String peerId, List<String> watchIds) throws SignatureException; /** * 實現AVIMConversation相關的簽名計算 */ public Signature createConversationSignature(String conversationId, String clientId, List<String> targetIds, String action) throws SignatureException;
createSignature
函數會在用戶登陸的時候被調用,createConversationSignature
會在對話建立/加入、邀請成員、踢出成員等操做時被調用。
你須要作的就是按照前文所述的簽名算法實現簽名,其中 Signature
聲明以下:
public class Signature { public List<String> getSignedPeerIds(); public void setSignedPeerIds(List<String> signedPeerIds); public String getSignature(); public void setSignature(String signature); public long getTimestamp(); public void setTimestamp(long timestamp); public String getNonce(); public void setNonce(String nonce); }
其中四個屬性分別是:
下面的代碼展現了基於 LeanCloud 雲代碼進行簽名時,客戶端的實現片斷,你能夠參考它來完成本身的邏輯實現:
public class KeepAliveSignatureFactory implements SignatureFactory { @Override public Signature createSignature(String peerId, List<String> watchIds) { Map<String,Object> params = new HashMap<String,Object>(); params.put("self_id",peerId); params.put("watch_ids",watchIds); try{ Object result = AVCloud.callFunction("sign",params); if(result instanceof Map){ Map<String,Object> serverSignature = (Map<String,Object>) result; Signature signature = new Signature(); signature.setSignature((String)serverSignature.get("signature")); signature.setTimestamp((Long)serverSignature.get("timestamp")); signature.setNonce((String)serverSignature.get("nonce")); return signature; } }catch(AVException e){ throw (SignatureFactory.SignatureException) e; } return null; } @Override public Signature createConversationSignature(String convId, String peerId, List<String> targetPeerIds,String action){ Map<String,Object> params = new HashMap<String,Object>(); params.put("self_id",peerId); params.put("group_id",convId); params.put("group_peer_ids",targetPeerIds); params.put("action",action); try{ Object result = AVCloud.callFunction("group_sign",params); if(result instanceof Map){ Map<String,Object> serverSignature = (Map<String,Object>) result; Signature signature = new Signature(); signature.setSignature((String)serverSignature.get("signature")); signature.setTimestamp((Long)serverSignature.get("timestamp")); signature.setNonce((String)serverSignature.get("nonce")); return signature; } }catch(AVException e){ throw (SignatureFactory.SignatureException) e; } return null; } }
LeanCloud IM SDK 專一作好底層的通信服務,有更多能夠定製化的地方,譬如說:
由於缺乏 UI 組件,實事求是地講在新用戶接入成本可能稍高,可是在業務規模擴大、產品需求變多以後,相信你們會愈來愈喜歡 LeanCloud 這種自由靈活的使用體驗,以及穩定迅捷的服務質量。