基於openfire+smack開發Android即時聊天應用[四]-單人聊天、羣聊、發送接收文件等
1.單人聊天
首先創建聊天對象
/**
* 創建聊天窗口
* @param jid 好友的JID
* @return
*/
public Chat createChat(String jid) {
if(isConnected()) {
ChatManager chatManager = ChatManager.getInstanceFor(connection);
return chatManager.createChat(jid);
}
throw new NullPointerException("服務器連接失敗,請先連接服務器");
}
創建聊天對象時,參數JID記得傳聊天JID(解釋請參考我的系列文章之基於openfire+smack開發Android即時聊天應用[三]-賬號信息、添加好友、JID理解等)給好友發送文本消息
2.獲取聊天對象管理器
/**
* 獲取聊天對象管理器
* @return
*/
public ChatManager getChatManager() {
if(isConnected()) {
ChatManager chatManager = ChatManager.getInstanceFor(connection);
return chatManager;
}
throw new NullPointerException("服務器連接失敗,請先連接服務器");
}
3.接收文本消息
//創建聊天對象管理器監聽
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之後進行消息展示處理,這個地方可以處理所有人的消息
}
});
}
};
//設置聊天對象管理器處理監聽
getChatManager().addChatListener(chatManagerListener);
上述代碼會在你創建聊天對象時對該聊天對象設置消息處理監聽,當接收到消息之後,會自動調用processMessage方法進行處理,我們可以在該方法中對接收到的消息進行展示或其他處理,所有好友發送過來的消息都會通過該方法處理。所以該監聽最好在登陸之後進行設置,同時在斷開連接或是註銷時移除該監聽。
4.創建羣聊聊天室
/**
* 創建羣聊聊天室
* @param roomName 聊天室名字
* @param nickName 創建者在聊天室中的暱稱
* @param password 聊天室密碼
* @return
*/
public MultiUserChat createChatRoom(String roomName, String nickName, String password) {
if(!isConnected()) {
throw new NullPointerException("服務器連接失敗,請先連接服務器");
}
MultiUserChat muc = null;
try {
// 創建一個MultiUserChat
muc = MultiUserChatManager.getInstanceFor(connection).getMultiUserChat(roomName + "@conference." + connection.getServiceName());
// 創建聊天室
boolean isCreated = muc.createOrJoin(nickName);
if(isCreated) {
// 獲得聊天室的配置表單
Form form = muc.getConfigurationForm();
// 根據原始表單創建一個要提交的新表單。
Form submitForm = form.createAnswerForm();
// 向要提交的表單添加默認答覆
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) {
// 設置默認值作爲答覆
submitForm.setDefaultAnswer(fields.get(i).getVariable());
}
}
// 設置聊天室的新擁有者
List owners = new ArrayList();
owners.add(connection.getUser());// 用戶JID
submitForm.setAnswer("muc#roomconfig_roomowners", owners);
// 設置聊天室是持久聊天室,即將要被保存下來
submitForm.setAnswer("muc#roomconfig_persistentroom", true);
// 房間僅對成員開放
submitForm.setAnswer("muc#roomconfig_membersonly", false);
// 允許佔有者邀請其他人
submitForm.setAnswer("muc#roomconfig_allowinvites", true);
if(password != null && password.length() != 0) {
// 進入是否需要密碼
submitForm.setAnswer("muc#roomconfig_passwordprotectedroom", true);
// 設置進入密碼
submitForm.setAnswer("muc#roomconfig_roomsecret", password);
}
// 能夠發現佔有者真實 JID 的角色
// submitForm.setAnswer("muc#roomconfig_whois", "anyone");
// 登錄房間對話
submitForm.setAnswer("muc#roomconfig_enablelogging", true);
// 僅允許註冊的暱稱登錄
submitForm.setAnswer("x-muc#roomconfig_reservednick", true);
// 允許使用者修改暱稱
submitForm.setAnswer("x-muc#roomconfig_canchangenick", false);
// 允許用戶註冊房間
submitForm.setAnswer("x-muc#roomconfig_registration", false);
// 發送已完成的表單(有默認值)到服務器來配置聊天室
muc.sendConfigurationForm(submitForm);
}
} catch (XMPPException | SmackException e) {
e.printStackTrace();
return null;
}
return muc;
}
上面這段創建羣聊聊天室設置表單屬性的那段代碼引用於網上的代碼段。
5.加入羣聊聊天室
/**
* 加入一個羣聊聊天室
* @param roomName 聊天室名字
* @param nickName 用戶在聊天室中的暱稱
* @param password 聊天室密碼
* @return
*/
public MultiUserChat joinChatRoom(String roomName, String nickName, String password) {
if(!isConnected()) {
throw new NullPointerException("服務器連接失敗,請先連接服務器");
}
try {
// 使用XMPPConnection創建一個MultiUserChat窗口
MultiUserChat muc = MultiUserChatManager.getInstanceFor(connection).
getMultiUserChat(roomName + "@conference." + connection.getServiceName());
// 聊天室服務將會決定要接受的歷史記錄數量
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;
}
}
在實現加入羣聊聊天室的這段代碼中有這麼一段代碼:
getMultiUserChat(roomName + "@conference." + connection.getServiceName());
在@與ServiceName中間必須加上conference這個字符串,我也不知道爲什麼,我最開始時不知道沒有加,然後無論如何都加入失敗,後來在網上查資料查了半天,有人說是要加上這個,然後我加上就成功了,暫時沒搞明白爲什麼,先把程序跑通會用了再研究其他的原因。
6.羣聊發送消息
當你創建或是加入羣聊聊天室後,即可獲得羣聊對象MultiUserChat,通過該對象即可發送羣聊消息:
multiUserChat.sendMessage(msg);//發送羣聊消息
7.接收羣聊消息
//聊天室消息監聽
private MessageListener messageListener = new MessageListener() {
@Override
public void processMessage(Message message) {
//與單聊接收處理消息類似,聊天室裏所有人(包括髮送人自己)發送的消息都會通過此方法進行回調處理
}
};
//設置聊天室消息監聽
multiUserChat.addMessageListener(messageListener);
羣聊接收消息與單聊接收消息還是很像的,只是監聽對象,監聽方式稍稍有點區別,整個來說,消息接收還是很簡單的。
8.獲取文件傳輸對象
/**
* 獲取發送文件的發送器
* @param jid 一個完整的jid(如:[email protected]/Smack
* 後面的Smack應該客戶端類型,不加這個會出錯)
* @return
*/
public OutgoingFileTransfer getSendFileTransfer(String jid) {
if(isConnected()) {
return FileTransferManager.getInstanceFor(connection).createOutgoingFileTransfer(jid);
}
throw new NullPointerException("服務器連接失敗,請先連接服務器");
}
獲取文件傳輸對象時的參數JID記得爲文件傳輸JID:解釋請參考我的系列文章之基於openfire+smack開發Android即時聊天應用[三]-賬號信息、添加好友、JID理解等
9.發送文件
//獲取文件傳輸對象
OutgoingFileTransfer transfer = getSendFileTransfer(jid);
//發送文件
transfer.sendFile(File file, String description);
//此處執行文件發送狀態監聽
以上代碼爲發送文件file,參數description爲對這次文件傳輸的描述
10.文件傳輸(包括文件發送與接收)過程監聽(傳輸開始、完成、進度百分比)
//文件傳輸過程中的狀態監聽分析
if(transfer.getProgress() < 1) {//開始傳輸
//傳輸進度,值爲0~1
}
while(!transfer.isDone()) {//判斷傳輸是否完成,傳輸取消、傳輸完成、傳輸發生錯誤都會返回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())) {
//傳輸錯誤
} else if(FileTransfer.Status.refused.equals(transfer.getStatus())) {
//傳輸拒絕
}
以上代碼需在子線程執行,可以在文件傳輸(發送、接收)開始時設置進度條,傳輸完成時去掉進度條,同時可以通過getProgress()方法獲得文件傳輸的具體進度百分比。
11.接收文件
/**
* 添加文件接收的監聽
* @param fileTransferListener
*/
public void addFileTransferListener(FileTransferListener fileTransferListener) {
if(isConnected()) {
FileTransferManager.getInstanceFor(connection).addFileTransferListener(fileTransferListener);
return;
}
throw new NullPointerException("服務器連接失敗,請先連接服務器");
}
addFileTransferListener(new FileTransferListener() {
@Override
public void fileTransferRequest(FileTransferRequest request) {
// Accept it
IncomingFileTransfer transfer = request.accept();
try {
String description = request.getDescription();
//在目錄fileDir目錄下新建一個名字爲request.getFileName()的文件
File file = new File(fileDir ,request.getFileName());
//開始接收文件(將傳輸過來的文件內容輸出到file中)
transfer.recieveFile(file);
//此處執行文件傳輸監聽
} catch (SmackException | IOException e) {
e.printStackTrace();
}
}
});
上面代碼爲設置文件接收監聽
12.發送語音、圖片消息
我查看了半天的Smack的API,但是沒有找到直接發送語音、圖片消息的API,我說說我的實現思路。
其實圖片、語音都是文件,我們可以把它們當做文件發送給好友。
在發送文件的同時,用描述字段進行標記傳輸過來的是圖片還是語音。
然後在接收到該文件後通過描述字段進行區分當前接收的是圖片文件還是語音文件,然後進行區分展示即可,這樣就可以達到發送圖片消息和語音消息。
但是我的這種實現方式還是有問題的,因爲這種方式對於單聊還是可以實現的。但是如果是羣聊的話,我就必須給每個人都發一個相同的文件,這樣的話一條語音或圖片消息,其實是要發送N次的,對於發送人來說流量就多消耗了N-1倍,所以這種方式對於實現羣聊是行不通的。
對於羣聊發送語音和圖片消息,我的思路是這樣的:
自己寫一個上傳文件的服務。
發送語音或圖片消息時,將圖片或語音通過上述上傳服務上傳到服務器上。
在上傳完語音或圖片後,再向聊天室裏發送一個文本消息,發送內容爲文件的類似下載地址這樣的信息,同時還要告訴羣成員這個文件是圖片還是語音。
羣成員接收到這樣的特殊文本消息後去自動下載這個文件然後進行展示或是其他處理。
羣聊天裏發送圖片或語音消息的這個實現方式我沒有驗證,但我覺得應該是可行的。至於單聊發送語音或圖片消息的思路我是實現驗證成功了的,是可行的。