這篇文章主要介紹如何實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)單人聊天隅很、多人的群聊、以及如何給對(duì)方發(fā)送文件率碾,如何發(fā)送圖片消息和語音消息等功能叔营。
1.單人聊天
1.首先創(chuàng)建聊天對(duì)象
/**
* 創(chuàng)建聊天窗口
* @param jid 好友的JID
* @return
*/
public Chat createChat(String jid) {
if(isConnected()) {
ChatManager chatManager = ChatManager.getInstanceFor(connection);
return chatManager.createChat(jid);
}
throw new NullPointerException("服務(wù)器連接失敗,請(qǐng)先連接服務(wù)器");
}
創(chuàng)建聊天對(duì)象時(shí)所宰,參數(shù)JID記得傳聊天JID(解釋請(qǐng)參考我的系列文章之基于openfire+smack開發(fā)Android即時(shí)聊天應(yīng)用[三]-賬號(hào)信息绒尊、添加好友、JID理解等)給好友發(fā)送文本消息
chat.sendMessage(message);
2.獲取聊天對(duì)象管理器
/**
* 獲取聊天對(duì)象管理器
* @return
*/
public ChatManager getChatManager() {
if(isConnected()) {
ChatManager chatManager = ChatManager.getInstanceFor(connection);
return chatManager;
}
throw new NullPointerException("服務(wù)器連接失敗仔粥,請(qǐng)先連接服務(wù)器");
}
3.接收文本消息
//創(chuàng)建聊天對(duì)象管理器監(jiān)聽
private ChatManagerListener chatManagerListener = new ChatManagerListener() {
@Override
public void chatCreated(Chat chat, boolean createdLocally) {
chat.addMessageListener(new ChatMessageListener() {
@Override
public void processMessage(Chat chat, Message message) {
//接收到消息Message之后進(jìn)行消息展示處理婴谱,這個(gè)地方可以處理所有人的消息
}
});
}
};
//設(shè)置聊天對(duì)象管理器處理監(jiān)聽
getChatManager().addChatListener(chatManagerListener);
上述代碼會(huì)在你創(chuàng)建聊天對(duì)象時(shí)對(duì)該聊天對(duì)象設(shè)置消息處理監(jiān)聽,當(dāng)接收到消息之后躯泰,會(huì)自動(dòng)調(diào)用processMessage方法進(jìn)行處理谭羔,我們可以在該方法中對(duì)接收到的消息進(jìn)行展示或其他處理,所有好友發(fā)送過來的消息都會(huì)通過該方法處理麦向。所以該監(jiān)聽最好在登陸之后進(jìn)行設(shè)置瘟裸,同時(shí)在斷開連接或是注銷時(shí)移除該監(jiān)聽。
2.群聊
1.創(chuàng)建群聊聊天室
/**
* 創(chuàng)建群聊聊天室
* @param roomName 聊天室名字
* @param nickName 創(chuàng)建者在聊天室中的昵稱
* @param password 聊天室密碼
* @return
*/
public MultiUserChat createChatRoom(String roomName, String nickName, String password) {
if(!isConnected()) {
throw new NullPointerException("服務(wù)器連接失敗诵竭,請(qǐng)先連接服務(wù)器");
}
MultiUserChat muc = null;
try {
// 創(chuàng)建一個(gè)MultiUserChat
muc = MultiUserChatManager.getInstanceFor(connection).getMultiUserChat(roomName + "@conference." + connection.getServiceName());
// 創(chuàng)建聊天室
boolean isCreated = muc.createOrJoin(nickName);
if(isCreated) {
// 獲得聊天室的配置表單
Form form = muc.getConfigurationForm();
// 根據(jù)原始表單創(chuàng)建一個(gè)要提交的新表單话告。
Form submitForm = form.createAnswerForm();
// 向要提交的表單添加默認(rèn)答復(fù)
List fields = form.getFields();
for(int i = 0; fields != null && i < fields.size(); i++) {
if(FormField.Type.hidden != fields.get(i).getType() &&
fields.get(i).getVariable() != null) {
// 設(shè)置默認(rèn)值作為答復(fù)
submitForm.setDefaultAnswer(fields.get(i).getVariable());
}
}
// 設(shè)置聊天室的新?lián)碛姓?
List owners = new ArrayList();
owners.add(connection.getUser());// 用戶JID
submitForm.setAnswer("muc#roomconfig_roomowners", owners);
// 設(shè)置聊天室是持久聊天室,即將要被保存下來
submitForm.setAnswer("muc#roomconfig_persistentroom", true);
// 房間僅對(duì)成員開放
submitForm.setAnswer("muc#roomconfig_membersonly", false);
// 允許占有者邀請(qǐng)其他人
submitForm.setAnswer("muc#roomconfig_allowinvites", true);
if(password != null && password.length() != 0) {
// 進(jìn)入是否需要密碼
submitForm.setAnswer("muc#roomconfig_passwordprotectedroom", true);
// 設(shè)置進(jìn)入密碼
submitForm.setAnswer("muc#roomconfig_roomsecret", password);
}
// 能夠發(fā)現(xiàn)占有者真實(shí) JID 的角色
// submitForm.setAnswer("muc#roomconfig_whois", "anyone");
// 登錄房間對(duì)話
submitForm.setAnswer("muc#roomconfig_enablelogging", true);
// 僅允許注冊(cè)的昵稱登錄
submitForm.setAnswer("x-muc#roomconfig_reservednick", true);
// 允許使用者修改昵稱
submitForm.setAnswer("x-muc#roomconfig_canchangenick", false);
// 允許用戶注冊(cè)房間
submitForm.setAnswer("x-muc#roomconfig_registration", false);
// 發(fā)送已完成的表單(有默認(rèn)值)到服務(wù)器來配置聊天室
muc.sendConfigurationForm(submitForm);
}
} catch (XMPPException | SmackException e) {
e.printStackTrace();
return null;
}
return muc;
}
上面這段創(chuàng)建群聊聊天室設(shè)置表單屬性的那段代碼引用于網(wǎng)上的代碼段卵慰。
2.加入群聊聊天室
/**
* 加入一個(gè)群聊聊天室
* @param roomName 聊天室名字
* @param nickName 用戶在聊天室中的昵稱
* @param password 聊天室密碼
* @return
*/
public MultiUserChat joinChatRoom(String roomName, String nickName, String password) {
if(!isConnected()) {
throw new NullPointerException("服務(wù)器連接失敗超棺,請(qǐng)先連接服務(wù)器");
}
try {
// 使用XMPPConnection創(chuàng)建一個(gè)MultiUserChat窗口
MultiUserChat muc = MultiUserChatManager.getInstanceFor(connection).
getMultiUserChat(roomName + "@conference." + connection.getServiceName());
// 聊天室服務(wù)將會(huì)決定要接受的歷史記錄數(shù)量
DiscussionHistory history = new DiscussionHistory();
history.setMaxChars(0);
// history.setSince(new Date());
// 用戶加入聊天室
muc.join(nickName, password);
return muc;
} catch (XMPPException | SmackException e) {
e.printStackTrace();
return null;
}
}
在實(shí)現(xiàn)加入群聊聊天室的這段代碼中有這么一段代碼:
getMultiUserChat(roomName + "@conference." + connection.getServiceName());
在@與ServiceName中間必須加上conference這個(gè)字符串,我也不知道為什么呵燕,我最開始時(shí)不知道沒有加,然后無論如何都加入失敗件相,后來在網(wǎng)上查資料查了半天再扭,有人說是要加上這個(gè),然后我加上就成功了夜矗,暫時(shí)沒搞明白為什么泛范,先把程序跑通會(huì)用了再研究其他的原因。
3.群聊發(fā)送消息
當(dāng)你創(chuàng)建或是加入群聊聊天室后紊撕,即可獲得群聊對(duì)象MultiUserChat罢荡,通過該對(duì)象即可發(fā)送群聊消息:
multiUserChat.sendMessage(msg);//發(fā)送群聊消息
4.接收群聊消息
//聊天室消息監(jiān)聽
private MessageListener messageListener = new MessageListener() {
@Override
public void processMessage(Message message) {
//與單聊接收處理消息類似,聊天室里所有人(包括發(fā)送人自己)發(fā)送的消息都會(huì)通過此方法進(jìn)行回調(diào)處理
}
};
//設(shè)置聊天室消息監(jiān)聽
multiUserChat.addMessageListener(messageListener);
群聊接收消息與單聊接收消息還是很像的,只是監(jiān)聽對(duì)象区赵,監(jiān)聽方式稍稍有點(diǎn)區(qū)別惭缰,整個(gè)來說,消息接收還是很簡單的笼才。
3.文件傳輸
1.獲取文件傳輸對(duì)象
/**
* 獲取發(fā)送文件的發(fā)送器
* @param jid 一個(gè)完整的jid(如:laohu@192.168.0.108/Smack
* 后面的Smack應(yīng)該客戶端類型漱受,不加這個(gè)會(huì)出錯(cuò))
* @return
*/
public OutgoingFileTransfer getSendFileTransfer(String jid) {
if(isConnected()) {
return FileTransferManager.getInstanceFor(connection).createOutgoingFileTransfer(jid);
}
throw new NullPointerException("服務(wù)器連接失敗,請(qǐng)先連接服務(wù)器");
}
獲取文件傳輸對(duì)象時(shí)的參數(shù)JID記得為文件傳輸JID:解釋請(qǐng)參考我的系列文章之基于openfire+smack開發(fā)Android即時(shí)聊天應(yīng)用[三]-賬號(hào)信息骡送、添加好友昂羡、JID理解等
2.發(fā)送文件
//獲取文件傳輸對(duì)象
OutgoingFileTransfer transfer = getSendFileTransfer(jid);
//發(fā)送文件
transfer.sendFile(File file, String description);
//此處執(zhí)行文件發(fā)送狀態(tài)監(jiān)聽
以上代碼為發(fā)送文件file,參數(shù)description為對(duì)這次文件傳輸?shù)拿枋?
3.文件傳輸(包括文件發(fā)送與接收)過程監(jiān)聽(傳輸開始摔踱、完成虐先、進(jìn)度百分比)
//文件傳輸過程中的狀態(tài)監(jiān)聽分析
if(transfer.getProgress() < 1) {//開始傳輸
//傳輸進(jìn)度,值為0~1
}
while(!transfer.isDone()) {//判斷傳輸是否完成派敷,傳輸取消蛹批、傳輸完成、傳輸發(fā)生錯(cuò)誤都會(huì)返回true
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(FileTransfer.Status.complete.equals(transfer.getStatus())) {
//傳輸完成
} else if(FileTransfer.Status.cancelled.equals(transfer.getStatus())) {
//傳輸取消
} else if(FileTransfer.Status.error.equals(transfer.getStatus())) {
//傳輸錯(cuò)誤
} else if(FileTransfer.Status.refused.equals(transfer.getStatus())) {
//傳輸拒絕
}
以上代碼需在子線程執(zhí)行膀息,可以在文件傳輸(發(fā)送般眉、接收)開始時(shí)設(shè)置進(jìn)度條,傳輸完成時(shí)去掉進(jìn)度條潜支,同時(shí)可以通過getProgress()方法獲得文件傳輸?shù)木唧w進(jìn)度百分比甸赃。
4.接收文件
/**
* 添加文件接收的監(jiān)聽
* @param fileTransferListener
*/
public void addFileTransferListener(FileTransferListener fileTransferListener) {
if(isConnected()) {
FileTransferManager.getInstanceFor(connection).addFileTransferListener(fileTransferListener);
return;
}
throw new NullPointerException("服務(wù)器連接失敗,請(qǐng)先連接服務(wù)器");
}
addFileTransferListener(new FileTransferListener() {
@Override
public void fileTransferRequest(FileTransferRequest request) {
// Accept it
IncomingFileTransfer transfer = request.accept();
try {
String description = request.getDescription();
//在目錄fileDir目錄下新建一個(gè)名字為request.getFileName()的文件
File file = new File(fileDir ,request.getFileName());
//開始接收文件(將傳輸過來的文件內(nèi)容輸出到file中)
transfer.recieveFile(file);
//此處執(zhí)行文件傳輸監(jiān)聽
} catch (SmackException | IOException e) {
e.printStackTrace();
}
}
});
上面代碼為設(shè)置文件接收監(jiān)聽
4.發(fā)送語音冗酿、圖片消息
我查看了半天的Smack的API埠对,但是沒有找到直接發(fā)送語音、圖片消息的API裁替,我說說我的實(shí)現(xiàn)思路项玛。
其實(shí)圖片、語音都是文件弱判,我們可以把它們當(dāng)做文件發(fā)送給好友襟沮。
在發(fā)送文件的同時(shí),用描述字段進(jìn)行標(biāo)記傳輸過來的是圖片還是語音昌腰。
然后在接收到該文件后通過描述字段進(jìn)行區(qū)分當(dāng)前接收的是圖片文件還是語音文件开伏,然后進(jìn)行區(qū)分展示即可,這樣就可以達(dá)到發(fā)送圖片消息和語音消息遭商。
但是我的這種實(shí)現(xiàn)方式還是有問題的固灵,因?yàn)檫@種方式對(duì)于單聊還是可以實(shí)現(xiàn)的。但是如果是群聊的話劫流,我就必須給每個(gè)人都發(fā)一個(gè)相同的文件巫玻,這樣的話一條語音或圖片消息丛忆,其實(shí)是要發(fā)送N次的,對(duì)于發(fā)送人來說流量就多消耗了N-1倍仍秤,所以這種方式對(duì)于實(shí)現(xiàn)群聊是行不通的熄诡。
對(duì)于群聊發(fā)送語音和圖片消息,我的思路是這樣的:
- 自己寫一個(gè)上傳文件的服務(wù)徒扶。
- 發(fā)送語音或圖片消息時(shí)粮彤,將圖片或語音通過上述上傳服務(wù)上傳到服務(wù)器上。
- 在上傳完語音或圖片后姜骡,再向聊天室里發(fā)送一個(gè)文本消息导坟,發(fā)送內(nèi)容為文件的類似下載地址這樣的信息,同時(shí)還要告訴群成員這個(gè)文件是圖片還是語音圈澈。
- 群成員接收到這樣的特殊文本消息后去自動(dòng)下載這個(gè)文件然后進(jìn)行展示或是其他處理惫周。
- 群聊天里發(fā)送圖片或語音消息的這個(gè)實(shí)現(xiàn)方式我沒有驗(yàn)證,但我覺得應(yīng)該是可行的康栈。至于單聊發(fā)送語音或圖片消息的思路我是實(shí)現(xiàn)驗(yàn)證成功了的递递,是可行的。