Android 應(yīng)用APP加入聊天功能
簡(jiǎn)介
自去年 LeanCloud 發(fā)布實(shí)時(shí)通信(IM)服務(wù)之后,基于用戶反饋和工程師對(duì)需求的消化和對(duì)業(yè)務(wù)的提煉,上周正式發(fā)布了「實(shí)時(shí)通信 2.0 」。設(shè)計(jì)理念依然是「靈活、解耦、可組合、可定制」,具體可以參考《實(shí)時(shí)通信開發(fā)指南》,了解 LeanCloud 實(shí)時(shí)通信的基本概念和模型。
下載和安裝
可以到 LeanCloud 官方下載點(diǎn)下載 LeanCloud IM SDK v2 版本。將下載到的 jar 包加入工程即可。
一對(duì)一的文本聊天
我們先從最簡(jiǎn)單的環(huán)節(jié)入手,看看怎么用 LeanCloud IM SDK v2 實(shí)現(xiàn)一對(duì)一文本聊天。
初始化
和 LeanCloud 其他服務(wù)一樣,實(shí)時(shí)聊天服務(wù)的初始化也是在 Application 的 onCreate 方法中進(jìn)行的:
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>
接下來我們需要完成用戶登錄。
登錄
假定聊天發(fā)起方名叫 Tom,為直觀起見,我們使用用戶名來作為 clientId 登錄聊天系統(tǒng)(LeanCloud 云端只要求 clientId 在應(yīng)用內(nèi)唯一即可,具體用什么數(shù)據(jù)由應(yīng)用層決定),代碼如下:
AVIMClient imClient = AVIMClient.getInstance("Tom");
imClient.open(new IMClientCallback(){
@Override
public void done(AVIMClient client, AVException e) {
if (e) {
// 出錯(cuò)了,可能是網(wǎng)絡(luò)問題無法連接 LeanCloud 云端,請(qǐng)檢查網(wǎng)絡(luò)之后重試。
// 此時(shí)聊天服務(wù)不可用。
} else {
// 成功登錄,可以開始進(jìn)行聊天了。
};
}
});
建立對(duì)話
假定我們要跟「Bob」這個(gè)用戶進(jìn)行聊天,我們先創(chuàng)建一個(gè)對(duì)話,代碼如下:
// 下面的代碼包含了實(shí)際應(yīng)用中的所有邏輯:查詢->創(chuàng)建「對(duì)話」。
// 先查詢一下是否已經(jīng)存在與「Bob」的私聊對(duì)話,可以先忽略這部分代碼
List<String> clientIds = new ArrayList<String>();
clientIds.add("Tom");
clientIds.add("Bob");
AVIMConversationQuery conversationQuery = imClient.getQuery();
conversationQuery.withMembers(clientIds);
// 之前有常量定義:
// int ConversationType_OneOne = 0; // 兩個(gè)人之間的單聊
// int ConversationType_Group = 1; // 多人之間的群聊
conversationQuery.whereEqualTo("attr.type", ConversationType_OneOne);
conversationQuery.findInBackground(new AVIMConversationQueryCallback(){
@Override
public void done(List<AVIMConversation> conversations, AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else if (null != conversations && conversations.size() > 0){
// 已經(jīng)有一個(gè)和 Bob 的對(duì)話存在,繼續(xù)在這一對(duì)話中聊天
...
} else {
// 不曾和 Bob 聊過,新建一個(gè)對(duì)話。??!**這里是重點(diǎn)**??!
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) {
// 成功了!
}
}
});
}
}
});
如何查詢「對(duì)話」
如你所見,我們創(chuàng)建一個(gè)對(duì)話的時(shí)候,指定了成員(Tom 和 Bob)和一個(gè)額外的屬性({type: 0})。這些數(shù)據(jù)保存到云端后,你在 控制臺(tái) -> 存儲(chǔ) -> 數(shù)據(jù) 里面會(huì)看到,_Conversation 表中增加了一條記錄,新記錄的 m 屬性值為["Tom", "Bob"],attr 屬性值為{"type":0}。如你所料,m 屬性就是對(duì)應(yīng)著成員列表,attr 屬性就是用戶增加的額外屬性值(以對(duì)象的形式存儲(chǔ))。
與 AVObject 的檢索方法一樣,要檢索這樣的對(duì)話,我們需要通過 imClient.getQuery() 得到一個(gè) AVIMConversationQuery 實(shí)例,然后調(diào)用 conversationQuery.withMembers() 來限定成員列表,調(diào)用 conversationQuery.whereEqualTo() 來限定額外的 attr 屬性。按照 AVQuery 的慣例,限定額外的 type 條件的時(shí)候需要指定的屬性名是 attr.type。
發(fā)送消息
建立好對(duì)話之后,要發(fā)送消息是很簡(jiǎn)單的:
AVIMMessage message = new AVIMMessage();
message.setContent("hello");
conversation.sendMessage(message, new AVIMConversationCallback() {
@Override
public void done(AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else {
}
}
});
好了,這樣一條消息就發(fā)送過去了。但是問題來了,對(duì)于「Bob」而言,他怎么才能收到別人發(fā)給他的消息呢?
消息接收
在 Bob 這一端,要能接收到消息,需要如下幾步:
1,進(jìn)行初始化和登錄,代碼與發(fā)送端并無二致;
2,實(shí)現(xiàn)自己的 AVIMMessageHandler,響應(yīng)新消息到達(dá)通知,主要是如下函數(shù):
public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client);
對(duì)于 Tom 發(fā)過來的消息,要顯示出來,我們只需實(shí)現(xiàn) onMessage 即可,示例代碼如下:
class CustomMessageHandler extends AVIMMessageHandler {
@Override
public void onMessage(AVIMMessage message, AVIMConversation conversation, AVIMClient client) {
// 新消息到來了。在這里增加你自己的處理代碼。
...
}
}
AVIMMessageManager.registerDefaultMessageHandler(new CustomMessageHandler());
幾個(gè)主要的回調(diào)接口
從上面的例子中可以看到,要接收到別人給你發(fā)送的消息,需要實(shí)現(xiàn)自己的 AVIMMessageHandler 類。從 v2 版開始,LeanCloud IM SDK 大量采用回調(diào)來反饋操作結(jié)果,但是對(duì)于一些被動(dòng)的消息通知,則還是采用接口來實(shí)現(xiàn)的,包括:
當(dāng)前網(wǎng)絡(luò)出現(xiàn)變化
對(duì)話中有新的消息
對(duì)話中有新成員加入
對(duì)話中有成員離開
被邀請(qǐng)加入某對(duì)話
被踢出對(duì)話
LeanCloud IM SDK 內(nèi)部使用了三種接口來響應(yīng)這些事件。
網(wǎng)絡(luò)事件響應(yīng)接口(AVIMClientEventHandler)
主要用來處理網(wǎng)絡(luò)變化事件,主要函數(shù)為:
/**
* 實(shí)現(xiàn)本方法以處理網(wǎng)絡(luò)斷開事件
*
* @param client
* @since 3.0
*/
public abstract void onConnectionPaused(AVIMClient client);
/**
* 實(shí)現(xiàn)本方法以處理網(wǎng)絡(luò)恢復(fù)事件
*
* @since 3.0
* @param client
*/
public abstract void onConnectionResume(AVIMClient client);
在網(wǎng)絡(luò)中斷的情況下,所有的消息收發(fā)和對(duì)話操作都會(huì)出現(xiàn)問題。
通過 AVIMClient.setClientEventHandler(AVIMClientEventHandler handler) 可以設(shè)定全局的 ClientEventHandler。
對(duì)話成員變化響應(yīng)接口(AVIMConversationEventHandler)
主要用來處理對(duì)話中成員變化的事件,主要函數(shù)為:
/**
* 實(shí)現(xiàn)本方法以處理聊天對(duì)話中的參與者離開事件
*
* @param client
* @param conversation
* @param members 離開的參與者
* @param kickedBy 離開事件的發(fā)動(dòng)者,有可能是離開的參與者本身
* @since 3.0
*/
public abstract void onMemberLeft(AVIMClient client,
AVIMConversation conversation, List<String> members, String kickedBy);
/**
* 實(shí)現(xiàn)本方法以處理聊天對(duì)話中的參與者加入事件
*
* @param client
* @param conversation
* @param members 加入的參與者
* @param invitedBy 加入事件的邀請(qǐng)人,有可能是加入的參與者本身
* @since 3.0
*/
public abstract void onMemberJoined(AVIMClient client,
AVIMConversation conversation, List<String> members, String invitedBy);
/**
* 實(shí)現(xiàn)本方法來處理當(dāng)前用戶被踢出某個(gè)聊天對(duì)話事件
*
* @param client
* @param conversation
* @param kickedBy 踢出你的人
* @since 3.0
*/
public abstract void onKicked(AVIMClient client, AVIMConversation conversation,
String kickedBy);
/**
* 實(shí)現(xiàn)本方法來處理當(dāng)前用戶被邀請(qǐng)到某個(gè)聊天對(duì)話事件
*
* @param client
* @param conversation 被邀請(qǐng)的聊天對(duì)話
* @param operator 邀請(qǐng)你的人
* @since 3.0
*/
public abstract void onInvited(AVIMClient client, AVIMConversation conversation,
String operator);
通過 AVIMMessageManager.setConversationEventHandler(AVIMConversationEventHandler handler) 可以設(shè)置全局的 ConversationEventHandler。
消息響應(yīng)接口(MessageHandler)
主要用來處理新消息到達(dá)事件,主要的函數(shù)為:
// 收到新的消息
@Override
public abstract void onMessage(AVIMMessage message, AVIMConversation conversation);
// 自己發(fā)送的消息已經(jīng)被對(duì)方接收
@Override
public abstract void onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client);
通過 AVIMMessageManager.registerDefaultMessageHandler(MessageHandler handler) 可以設(shè)置全局的 MessageHandler。
我們實(shí)現(xiàn)這三類接口,就可以處理所有的通知消息了(注意:LeanCloud IM SDK 內(nèi)部實(shí)現(xiàn)了一個(gè)空的 AVIMMessageHandler,你可以從這里派生出進(jìn)行實(shí)際處理的 handler)。
支持富媒體的聊天消息
上面的代碼演示了如何發(fā)送簡(jiǎn)單文本信息,但是現(xiàn)在的交互方式已經(jīng)越來越多樣化,圖片、語音、視頻已是非常普遍的消息類型。v2 版的 LeanCloud IM SDK 已經(jīng)可以很好地支持這些富媒體消息,具體說明如下:
基類:AVIMTypedMessage
所有富媒體消息的基類,其聲明為
//SDK定義的消息類型,LeanCloud SDK 自身使用的類型是負(fù)數(shù),所有正數(shù)留給開發(fā)者自定義擴(kuò)展類型使用,0 作為「沒有類型」被保留起來。
enum AVIMReservedMessageType {
UnsupportedMessageType(0),
TextMessageType(-1),
ImageMessageType(-2),
AudioMessageType(-3),
VideoMessageType(-4),
LocationMessageType(-5),
FileMessageType(-6);
};
public abstract class AVIMTypedMessage extends AVIMMessage {
public AVIMTypedMessage();
public int getMessageType();
@Override
public final String getContent();
@Override
public final void setContent(String content);
}
文本消息(AVIMTextMessage)
AVIMTypedMessage 子類,表示一般的文本消息,其聲明為
@AVIMMessageType(type = -1)
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);
}
要發(fā)送文本消息,示例代碼為:
AVIMTextMessage message = new AVIMTextMessage();
message.setText("hello");
conversation.sendMessage(message, new AVIMConversationCallback() {
@Override
public void done(AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else {
}
}
});
圖像消息(AVIMImageMessage)
AVIMTypedMessage 子類,支持發(fā)送圖片和附帶文本的混合消息,其聲明為:
public class AVIMImageMessage extends AVIMFileMessage {
public AVIMImageMessage();
public AVIMImageMessage(String localPath) throws FileNotFoundException, IOException;
public AVIMImageMessage(File localFile) throws FileNotFoundException, IOException;
public AVIMImageMessage(AVFile file);
/**
* 獲取文件的metaData
*
* @return
*/
@Override
public Map<String, Object> getFileMetaData();
/**
* 獲取圖片的高
*
* @return
*/
public int getHeight();
/**
* 獲取圖片的寬度
*
* @return
*/
public int getWidth();
}
發(fā)送圖片消息的示例代碼為:
String localImagePath;
try {
AVIMImageMessage message = new AVIMImageMessage(localImagePath);
conversation.sendMessage(message, new AVIMConversationCallback() {
@Override
public void done(AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else {
}
}
});
} catch (Exception ex) {
}
接收到這樣消息之后,開發(fā)者可以獲取到若干圖片元數(shù)據(jù)(width,height,圖片 size,圖片 format)和一個(gè)包含圖片數(shù)據(jù)的 AVFile 對(duì)象。
音頻消息(AVIMAudioMessage)
AVIMTypedMessage 子類,支持發(fā)送語音和附帶文本的混合消息,其聲明為:
public class AVIMAudioMessage extends AVIMFileMessage {
public AVIMAudioMessage();
public AVIMAudioMessage(String localPath) throws FileNotFoundException, IOException;
public AVIMAudioMessage(File localFile) throws FileNotFoundException, IOException;
public AVIMAudioMessage(AVFile file);
/**
* 獲取文件的metaData
*
* @return
*/
@Override
public Map<String, Object> getFileMetaData();
/**
* 獲取音頻的時(shí)長(zhǎng)
*
* @return
*/
public double getDuration();
}
發(fā)送音頻消息的示例代碼為:
String localAudioPath;
try {
AVIMAudioMessage message = new AVIMAudioMessage(localAudioPath);
conversation.sendMessage(message, new AVIMConversationCallback() {
@Override
public void done(AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else {
}
}
});
} catch (Exception ex) {
}
接收到這樣消息之后,開發(fā)者可以獲取到若干音頻元數(shù)據(jù)(時(shí)長(zhǎng) duration、音頻 size,音頻 format)和一個(gè)包含音頻數(shù)據(jù)的 AVFile 對(duì)象。
視頻消息(AVIMVideoMessage)
AVIMTypedMessage 子類,支持發(fā)送視頻和附帶文本的混合消息,其聲明為:
public class AVIMVideoMessage extends AVIMFileMessage {
public AVIMVideoMessage();
public AVIMVideoMessage(String localPath) throws FileNotFoundException, IOException;
public AVIMVideoMessage(File localFile) throws FileNotFoundException, IOException;
public AVIMVideoMessage(AVFile file);
/**
* 獲取文件的metaData
*
* @return
*/
@Override
public Map<String, Object> getFileMetaData();
/**
* 獲取時(shí)長(zhǎng)
*
* @return
*/
public double getDuration();
}
發(fā)送視頻消息的示例代碼為:
String localVideoPath;
try {
AVIMVideoMessage message = new AVIMVideoMessage(localVideoPath);
conversation.sendMessage(message, new AVIMConversationCallback() {
@Override
public void done(AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else {
}
}
});
} catch (Exception ex) {
}
接收到這樣消息之后,開發(fā)者可以獲取到若干視頻元數(shù)據(jù)(時(shí)長(zhǎng) duration、視頻 size,視頻 format)和一個(gè)包含視頻數(shù)據(jù)的 AVFile 對(duì)象。
地理位置消息(AVIMLocationMessage)
AVIMTypedMessage 子類,支持發(fā)送地理位置信息和附帶文本的混合消息,其聲明為:
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);
}
要發(fā)送位置消息的示例代碼為:
AVIMLocationMessage message = new AVIMLocationMessage();
message.setText("快點(diǎn)過來!");
message.setLocation(new AVGeoPoint(15.9, 56.4));
conversation.sendMessage(message, new AVIMConversationCallback() {
@Override
public void done(AVException e) {
if (null != e) {
// 出錯(cuò)了。。。
} else {
}
}
});
接收到這樣的消息之后,開發(fā)者可以獲取到具體的地理位置數(shù)據(jù)。
如何接收富媒體消息
新版 LeanCloud IM SDK 內(nèi)部封裝了對(duì)富媒體消息的支持,所有富媒體消息都是從 AVIMTypedMessage 派生出來的。發(fā)送的時(shí)候可以直接調(diào)用 conversation.sendMessage 函數(shù)。在接收端,我們也專門增加了一類回調(diào)接口:
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);
}
開發(fā)者可以編寫自己的消息處理 handler,然后調(diào)用 AVIMMessageManager.registerMessageHandler(Class<? extends AVIMMessage> clazz, MessageHandler<?> handler) 函數(shù)來注冊(cè)目標(biāo) handler。
LeanCloud IM SDK 內(nèi)部消息分發(fā)的邏輯是這樣的:對(duì)于收到的任一新消息,SDK 內(nèi)部都會(huì)先解析消息的類型,根據(jù)類型找到開發(fā)者為這一類型注冊(cè)的處理 handler,然后逐一調(diào)用這些 handler 的 onMessage 函數(shù)。如果沒有找到專門處理這一類型消息的 handler,就會(huì)轉(zhuǎn)交給 defaultHandler 處理。
這樣一來,在開發(fā)者為 TypedMessage(及其子類) 指定了專門的 handler,也指定了全局的 defaultHandler 了的時(shí)候,如果發(fā)送端發(fā)送的是通用的 AVIMMessage 消息,那么接受端就是 AVIMMessageManager.registerDefaultMessageHandler()中指定的 handler 被調(diào)用;如果發(fā)送的是 AVIMTypedMessage(及其子類)的消息,那么接受端就是 AVIMMessageManager.registerMessageHandler()中指定的 handler 被調(diào)用。
接收端對(duì)于富媒體消息的通知處理代碼片段如下:
class MsgHandler extends AVIMTypedMessageHandler {
@Override
public void onMessage(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) {
getInstance().onMessage(conversation, message);
}
@Override
public void onMessageReceipt(AVIMTypedMessage message, AVIMConversation conversation, AVIMClient client) {
getInstance().onMessageDelivered(message);
}
}
MsgHandler msgHandler = new MsgHandler();
AVIMMessageManager.registerMessageHandler(AVIMTypedMessage.class, msgHandler);
如何擴(kuò)展自己的富媒體消息
繼承于 AVIMTypedMessage,開發(fā)者也可以擴(kuò)展自己的富媒體消息,這一部分屬于高階內(nèi)容,大家有興趣可以參看這篇文檔,這里不再贅述。
群組聊天
與前面的單聊類似,群組聊天也需要先建立一個(gè)對(duì)話(AVIMConversation),然后發(fā)送、接收新的消息。
創(chuàng)建群組
和單聊類似,建立一個(gè)多人聊天的群組也是很簡(jiǎn)單的。例如:
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) {
// 成功了!
}
}
});
成功之后,我們就可以進(jìn)入聊天界面了。
往群組發(fā)送消息
發(fā)送消息非常簡(jiǎn)單,與前面單聊的場(chǎng)景一樣。
我們會(huì)注意到,AVIMConversation 還有一個(gè)發(fā)送消息的方法:
public void sendMessage(final AVIMMessage message, final int messageFlag,
final AVIMConversationCallback callback)
而這里 flag 的定義有如下三種類型:
暫態(tài)消息(AVIMConversation.TRANSIENT_MESSAGE_FLAG)。這種消息不會(huì)被自動(dòng)保存(以后在歷史消息中無法找到它),也不支持延遲接收,離線用戶更不會(huì)收到推送通知,所以適合用來做控制協(xié)議。譬如聊天過程中「某某正在輸入中...」這樣的狀態(tài)信息,就適合通過暫態(tài)消息來發(fā)送。
普通消息(AVIMConversation.NONTRANSIENT_MESSAGE_FLAG)。這種消息就是我們最常用的消息類型,在 LeanCloud 云端會(huì)自動(dòng)保存起來,支持延遲接收和離線推送,以后在歷史消息中可以找到它。
待回執(zhí)消息(AVIMConversation.RECEIPT_MESSAGE_FLAG)。這也是一種普通消息,只是消息被對(duì)方收到之后 LeanCloud 服務(wù)端會(huì)發(fā)送一個(gè)回執(zhí)通知給發(fā)送方(這就是 AVIMMessageHandler 中 public void onMessageReceipt(AVIMMessage message, AVIMConversation conversation, AVIMClient client) 函數(shù)被調(diào)用的時(shí)機(jī))。
接收群組消息
接收一個(gè)群組的消息,與接收單聊的消息也是一樣的。
成員管理
在查詢到聊天室成員之后,可以讓用戶邀請(qǐng)一些自己的朋友加入,作為管理員也可以剔除一些「可怕」的成員。
加入新成員的 API 如下:
List<String> userIds = new ArrayList<String>();
userIds.add("A");
userIds.add("B");
userIds.add("C");
conversation.addMembers(userIds, new AVIMConversationCallback() {
@Override
public void done(AVException error) {
if (null != error) {
// 加入失敗,報(bào)錯(cuò).
} else {
// 發(fā)出邀請(qǐng),此后新成員就可以看到這個(gè)對(duì)話中的所有消息了。
}
}
});
邀請(qǐng)成功以后,通知的流程是這樣的:
操作者(管理員) 被邀請(qǐng)者 其他人
1, 發(fā)出請(qǐng)求 addMembers
2, 收到 onInvited 通知
3, 收到 onMemberJoined 通知 收到 onMemberJoined 通知 收到 onMemberJoined 通知
相應(yīng)地,踢人時(shí)的調(diào)用 API 是:
List<String> userIds = new ArrayList<String>();
userIds.add("A");
conversation.kickMembers(userIds, new AVIMConversationCallback() {
@Override
public void done(AVException error) {
if (null != error) {
// 失敗,報(bào)錯(cuò).
} else {
// 成功。
}
}
});
踢人的通知流程如下:
操作者(管理員) 被踢者 其他人
1, 發(fā)出請(qǐng)求 kickMembers
2, 收到 onKicked 通知
3, 收到 onMemberLeft 通知 收到 onMemberLeft 通知
注意!
如果邀請(qǐng)、踢人操作發(fā)生的時(shí)候,被邀請(qǐng)者/被踢者當(dāng)前不在線,那么通知消息并不會(huì)被離線緩存,所以他們?cè)偕暇€的時(shí)候?qū)⒉粫?huì)收到通知。
獲取歷史消息
LeanMessage 會(huì)將非暫態(tài)消息自動(dòng)保存在云端,之后開發(fā)者可以通過 AVIMConversation 來獲取該對(duì)話的所有歷史消息。獲取歷史消息的 API 如下:
String oldestMsgId;
long oldestMsgTimestamp;
conversation.queryMessages(oldestMsgId,oldestMsgTimestamp, limit, new AVIMHistoryMessageCallback(){
@Override
public void done(List<AVIMMessage> messages, AVException e) {
if (null != e) {
// 出錯(cuò)了:(
} else {
// 成功
}
}
});
注意:
獲取歷史消息的時(shí)候,LeanCloud 云端是從某條消息開始,往前查找開發(fā)者指定的 N 條消息,返回給客戶端。為此,獲取歷史消息需要傳入三個(gè)參數(shù):起始消息的 msgId,起始消息的發(fā)送時(shí)間戳,需要獲取的消息條數(shù)。
通過這一 API 拿到的消息就是 AVIMMessage 或者 AVIMTypedMessage 實(shí)例數(shù)組,開發(fā)者可以像之前收到新消息通知一樣處理。
啟用離線消息推送(僅對(duì) iOS 平臺(tái)用戶有效)
不管是單聊還是群聊,當(dāng)用戶 A 發(fā)出消息后,如果目標(biāo)對(duì)話中有部分用戶當(dāng)前不在線,LeanCloud 云端可以提供離線推送的方式來提醒用戶。這一功能默認(rèn)是關(guān)閉的,你可以在 LeanCloud 應(yīng)用控制臺(tái)中開啟它。開啟方法如下:
登錄 LeanCloud 應(yīng)用控制臺(tái),選擇正確的應(yīng)用進(jìn)入;
選擇最頂端的「消息」服務(wù),依次點(diǎn)擊左側(cè)菜單「實(shí)時(shí)消息」->「設(shè)置」;
在右側(cè)「iOS 用戶離線時(shí)的推送內(nèi)容」下填好你要推送出去的消息內(nèi)容,保存;
這樣 iOS 平臺(tái)上的用戶就可以收到 Push Notification 了(當(dāng)然,前提是應(yīng)用本身申請(qǐng)到了 RemoteNotification 權(quán)限,也將正確的推送證書上傳到了 LeanCloud 控制臺(tái))。
群組消息免打擾(僅對(duì) iOS 平臺(tái)用戶有效)
不管是單聊還是群聊,對(duì)于發(fā)往普通的 Conversation 的普通消息,如果接收方當(dāng)前不在線,LeanCloud 云端支持通過 Push Notification 的方式進(jìn)行提醒。一般情況下這都是很好的,但是如果某個(gè)群組特別活躍,那離線用戶就會(huì)收到過多的推送,會(huì)形成不小的干擾。
對(duì)此 LeanCloud IM 服務(wù)也允許單個(gè)用戶來關(guān)閉/打開某個(gè)對(duì)話的離線推送功能。調(diào)用 API 如下:
if (open) {
[_conversation muteWithCallback:^(BOOL succeeded, NSError *error) {
...
}];
} else {
[_conversation unmuteWithCallback:^(BOOL succeeded, NSError *error) {
...
}];
}
搜索群組
不管是單聊,還是群聊,在 LeanCloud IM SDK 里面都是對(duì)話(Conversation)。我們給對(duì)話設(shè)置了如下幾種屬性:
conversationId,字符串,對(duì)話 id,只讀,對(duì)話創(chuàng)建之后由 LeanCloud 云端賦予一個(gè)全局唯一的 id。
creator,字符串,對(duì)話創(chuàng)建者 id,只讀,標(biāo)識(shí)對(duì)話創(chuàng)建者信息
members,數(shù)組,對(duì)話參與者,這里記錄了所有的參與者
name,字符串,對(duì)話的名字,optional,可用來對(duì)于群組命名
attributes,Map/Dict,自定義屬性,optional,供開發(fā)者自己擴(kuò)展用。
我們提供了專門的類,來搜索特定的群組。例如要搜索當(dāng)前登錄用戶參與的所有群聊對(duì)話,其代碼為
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) {
// 出錯(cuò)了。。。
} else {
// done!
}
}
});
AVIMConversationQuery 中設(shè)置條件的方法與 AVQuery 類似,具體可以參看其頭文件。這里要強(qiáng)調(diào)的一點(diǎn)是,對(duì)于自定義屬性的約束條件,屬性名一定要以 attr 開頭。
開放聊天室
開放聊天室(也叫暫態(tài)對(duì)話)可以用于很多地方,譬如彈幕、直播等等。在 LeanCloud IM SDK 中,開放聊天室是一類特殊的群組,它也支持創(chuàng)建、加入/踢出成員等操作,消息記錄會(huì)被保存并可供獲??;與普通群組不一樣的地方具體體現(xiàn)為:
不支持查詢成員列表,你可以通過相關(guān) API 查詢?cè)诰€人數(shù);
不支持離線消息、離線推送通知等功能;
沒有成員加入、離開的通知;
一個(gè)用戶一次登錄只能加入一個(gè)開放聊天室,加入新的開放聊天室后會(huì)自動(dòng)離開原來的聊天室;
加入后半小時(shí)內(nèi)斷網(wǎng)重連會(huì)自動(dòng)加入原聊天室,超過這個(gè)時(shí)間則需要重新加入;
創(chuàng)建開放聊天室
和普通的群組類似,建立一個(gè)開放聊天室也是很簡(jiǎn)單的,只是在 AVIMClient.createConversation(conversationMembers, name, attributes, isTransient, callback) 中我們需要傳入 isTransient=true 選項(xiàng)。例如:
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) {
// 成功了!
}
}
});
加入成功之后,我們就可以進(jìn)入聊天界面了。開放聊天室的其他操作,都與普通群組操作一樣。
查詢?cè)诰€人數(shù)
通過 AVIMConversation.getMemberCount() 方法可以實(shí)時(shí)查詢開放聊天室的在線人數(shù)。示例代碼如下:
conversation.getMemberCount(new AVIMConversationMemberCountCallback(){
@Override
public void done(Integer memberCount, AVException e) {
if (null != e) {
// 出錯(cuò)了:(
} else {
// 成功,此時(shí) memberCount 的數(shù)值就是實(shí)時(shí)在線人數(shù)
}
}
});
簽名和安全
為了滿足開發(fā)者對(duì)權(quán)限和認(rèn)證的要求,LeanCloud 還設(shè)計(jì)了操作簽名的機(jī)制。我們可以在 LeanCloud 應(yīng)用控制臺(tái)中的「設(shè)置」->「應(yīng)用選項(xiàng)」->「聊天推送」下面勾選「聊天服務(wù)簽名認(rèn)證」來啟用簽名(強(qiáng)烈推薦這樣做)。啟用后,所有的用戶登錄、對(duì)話創(chuàng)建/加入、邀請(qǐng)成員、踢出成員等操作都需要驗(yàn)證簽名,這樣開發(fā)者就可以對(duì)消息進(jìn)行充分的控制。
客戶端這邊究竟該如何使用呢?我們只需要實(shí)現(xiàn) SignatureFactory 接口,然后在用戶登錄之前,把這個(gè)接口的實(shí)例賦值給 AVIMClient 即可(AVIMClient.setSignatureFactory(factory))。
設(shè)定了 signatureFactory 之后,對(duì)于需要鑒權(quán)的操作,LeanCloud IM SDK 與服務(wù)器端通訊的時(shí)候都會(huì)帶上應(yīng)用自己生成的 Signature 信息,LeanCloud 云端會(huì)使用 app 的 masterKey 來驗(yàn)證信息的有效性,保證聊天渠道的安全。
對(duì)于 SignatureFactory 接口,我們只需要實(shí)現(xiàn)這兩個(gè)函數(shù)即可:
/**
* 實(shí)現(xiàn)一個(gè)基礎(chǔ)簽名方法 其中的簽名算法會(huì)在SessionManager和AVIMClient(V2)中被使用
*
* @param peerId
* @param watchIds
* @return
* @throws SignatureException 如果簽名計(jì)算中間發(fā)生任何問題請(qǐng)拋出本異常
*/
public Signature createSignature(String peerId, List<String> watchIds) throws SignatureException;
/**
* 實(shí)現(xiàn)AVIMConversation相關(guān)的簽名計(jì)算
*
* @param conversationId
* @param clientId
* @param targetIds 操作所對(duì)應(yīng)的數(shù)據(jù)
* @param action 操作
* @return
* @throws SignatureException 如果簽名計(jì)算中間發(fā)生任何問題請(qǐng)拋出本異常
*/
public Signature createConversationSignature(String conversationId, String clientId,
List<String> targetIds, String action) throws SignatureException;
createSignature 函數(shù)會(huì)在用戶登錄的時(shí)候被調(diào)用,createConversationSignature 會(huì)在對(duì)話創(chuàng)建/加入、邀請(qǐng)成員、踢出成員等操作時(shí)被調(diào)用。
你需要做的就是按照前文所述的簽名算法實(shí)現(xiàn)簽名,其中 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);
}
其中四個(gè)屬性分別是:
signature 簽名
timestamp 時(shí)間戳,單位秒
nonce 隨機(jī)字符串 nonce
signedPeerIds 放行的 clientId 列表,v2 中已經(jīng)廢棄不用
下面的代碼展示了基于 LeanCloud 云代碼進(jìn)行簽名時(shí),客戶端的實(shí)現(xiàn)片段:
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 專注做好底層的通訊服務(wù),有更多可以定制化的地方,譬如說:
賬戶系統(tǒng)和 IM 系統(tǒng)是分離的;
消息變成離線推送的時(shí)候,推送內(nèi)容開發(fā)者是可以定制的;
通過 web hook,開發(fā)者可以對(duì)消息進(jìn)行更多處理;
聊天過程中通過消息鑒權(quán)機(jī)制,開發(fā)者可以有更多控制;
因?yàn)槿鄙?UI 組件,實(shí)事求是地講在新用戶接入成本可能稍高,但是在業(yè)務(wù)規(guī)模擴(kuò)大、產(chǎn)品需求變多之后,相信大家會(huì)越來越喜歡 LeanCloud 這種自由靈活的使用體驗(yàn),以及穩(wěn)定迅捷的服務(wù)質(zhì)量。
以上所述就是本文的全部?jī)?nèi)容了,希望大家能夠喜歡。
請(qǐng)您花一點(diǎn)時(shí)間將文章分享給您的朋友或者留下評(píng)論。我們將會(huì)由衷感謝您的支持!
- Android Studio和阿里云數(shù)據(jù)庫實(shí)現(xiàn)一個(gè)遠(yuǎn)程聊天程序
- android Socket實(shí)現(xiàn)簡(jiǎn)單聊天功能以及文件傳輸
- Android高仿微信聊天界面代碼分享
- android 仿微信聊天氣泡效果實(shí)現(xiàn)思路
- Android如何獲取QQ與微信的聊天記錄并保存到數(shù)據(jù)庫詳解
- 詳解Android 獲取手機(jī)中微信聊天記錄方法
- Android藍(lán)牙通信聊天實(shí)現(xiàn)發(fā)送和接受功能
- Android中基于XMPP協(xié)議實(shí)現(xiàn)IM聊天程序與多人聊天室
- Android實(shí)現(xiàn)聊天界面
- Android?Studio實(shí)現(xiàn)智能聊天
相關(guān)文章
Android實(shí)現(xiàn)漂亮的Gallery畫廊
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)漂亮的Gallery畫廊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05Android 判斷網(wǎng)絡(luò)狀態(tài)及開啟網(wǎng)路
這篇文章主要介紹了Android 判斷網(wǎng)絡(luò)狀態(tài)及開啟網(wǎng)路的相關(guān)資料,在開發(fā)網(wǎng)路狀態(tài)的時(shí)候需要先判斷是否開啟之后在提示用戶進(jìn)行開啟操作,需要的朋友可以參考下2017-08-08EditText監(jiān)聽方法,實(shí)時(shí)的判斷輸入多少字符
在EditText提供了一個(gè)方法addTextChangedListener實(shí)現(xiàn)對(duì)輸入文本的監(jiān)控。本文分享了EditText監(jiān)聽方法案例,需要的朋友一起來看下吧2016-12-12android開發(fā)教程之framework增加字符串資源和圖片等resource資源
這篇文章主要介紹了android開發(fā)中framework增加字符串資源和圖片等resource資源方法,需要的朋友可以參考下2014-02-02解決django 多個(gè)APP時(shí) static文件的問題
這篇文章主要介紹了解決django 多個(gè)APP時(shí) static文件的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android實(shí)現(xiàn)復(fù)制Assets文件到SD卡
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)復(fù)制Assets文件到SD卡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12Android編程之語音識(shí)別實(shí)現(xiàn)方法
這篇文章主要介紹了Android編程語音識(shí)別實(shí)現(xiàn)方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android語音識(shí)別的操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-10-10