基于openfire+smack开发Android即时聊天应用[四]-单人聊天、群聊、发送接收文件等

这篇文章主要介绍如何实现点对点单人聊天、多人的群聊、以及如何给对方发送文件,如何发送图片消息和语音消息等功能。

1.单人聊天

首先创建聊天对象

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 创建聊天窗口
* @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理解等)给好友发送文本消息

1
chat.sendMessage(message);

2.获取聊天对象管理器

1
2
3
4
5
6
7
8
9
10
11
/**
* 获取聊天对象管理器
* @return
*/
public ChatManager getChatManager() {
if(isConnected()) {
ChatManager chatManager = ChatManager.getInstanceFor(connection);
return chatManager;
}
throw new NullPointerException("服务器连接失败,请先连接服务器");
}

3.接收文本消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建聊天对象管理器监听
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.创建群聊聊天室

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 创建群聊聊天室
* @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.加入群聊聊天室

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 加入一个群聊聊天室
* @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;
}
}

在实现加入群聊聊天室的这段代码中有这么一段代码:

1
getMultiUserChat(roomName + "@conference." + connection.getServiceName());

在@与ServiceName中间必须加上conference这个字符串,我也不知道为什么,我最开始时不知道没有加,然后无论如何都加入失败,后来在网上查资料查了半天,有人说是要加上这个,然后我加上就成功了,暂时没搞明白为什么,先把程序跑通会用了再研究其他的原因。

6.群聊发送消息

当你创建或是加入群聊聊天室后,即可获得群聊对象MultiUserChat,通过该对象即可发送群聊消息:

1
multiUserChat.sendMessage(msg);//发送群聊消息

7.接收群聊消息

1
2
3
4
5
6
7
8
9
//聊天室消息监听
private MessageListener messageListener = new MessageListener() {
@Override
public void processMessage(Message message) {
//与单聊接收处理消息类似,聊天室里所有人(包括发送人自己)发送的消息都会通过此方法进行回调处理
}
};
//设置聊天室消息监听
multiUserChat.addMessageListener(messageListener);

群聊接收消息与单聊接收消息还是很像的,只是监听对象,监听方式稍稍有点区别,整个来说,消息接收还是很简单的。

8.获取文件传输对象

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 获取发送文件的发送器
* @param jid 一个完整的jid(如:laohu@192.168.0.108/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.发送文件

1
2
3
4
5
6
//获取文件传输对象
OutgoingFileTransfer transfer = getSendFileTransfer(jid);
//发送文件
transfer.sendFile(File file, String description);
//此处执行文件发送状态监听
以上代码为发送文件file,参数description为对这次文件传输的描述

10.文件传输(包括文件发送与接收)过程监听(传输开始、完成、进度百分比)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//文件传输过程中的状态监听分析
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.接收文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 添加文件接收的监听
* @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倍,所以这种方式对于实现群聊是行不通的。

对于群聊发送语音和图片消息,我的思路是这样的:

  1. 自己写一个上传文件的服务。
  2. 发送语音或图片消息时,将图片或语音通过上述上传服务上传到服务器上。
  3. 在上传完语音或图片后,再向聊天室里发送一个文本消息,发送内容为文件的类似下载地址这样的信息,同时还要告诉群成员这个文件是图片还是语音。
  4. 群成员接收到这样的特殊文本消息后去自动下载这个文件然后进行展示或是其他处理。
  5. 群聊天里发送图片或语音消息的这个实现方式我没有验证,但我觉得应该是可行的。至于单聊发送语音或图片消息的思路我是实现验证成功了的,是可行的。

write by laohu

2015-10-30


原创文章,本文采用知识共享署名 2.5(中国大陆许可协议)进行许可,欢迎转载,但转载请注明来自ittiger.cn,并保证转载后文章内容的完整性。本人(laohu)保留所有版权相关权利。



评论