昨天学习了smack的相关代码和API是因为在项目当中要使用聊天功能,今天早上一到公司就开始写相关的代码了。这里就将我写代码时的感想写一下。
聊天功能无非就是人与人互相之间进行信息的传送,我首先写了一个UserShip类用来对聊天状态和聊天列表进行封装。
private static final long serialVersionUID = 1L;
boolean status = true;
List<String> userShip = new ArrayList<String>();
/**
* @return the status
*/
public boolean isStatus() {
return status;
}
/**
* @param status the status to set
*/
public void setStatus(boolean status) {
this.status = status;
}
/**
* @return the userShip
*/
public List<String> getUserShip() {
return userShip;
}
/**
* @param userShip the userShip to set
*/
public void setUserShip(List<String> userShip) {
this.userShip = userShip;
}
因为在聊天系统当中会使用到多人聊天,我们使用MultiUserChat这个类来实现的。在聊天过程中会遇到人进入和离开的情况,这时我们就要用到上面写的那个UserShip类了:
final UserShip userShip = new UserShip();
MultiUserChat mUserChat = null;
public final void setChatMap(String key, List<String> value) {
chatMap.put(key, value);
}
public final void addUser(String userName) {
userShip.setStatus(true);
if (userShip.getUserShip() == null) {
userShip.setUserShip(new ArrayList<String>());
}
userShip.getUserShip().add(userName);
}
public final void removeUser(String userName) {
userShip.setStatus(true);
if (userShip.getUserShip() == null) {
userShip.setUserShip(new ArrayList<String>());
}
userShip.getUserShip().remove(userName);
}
看过我前面几篇转载的文章的同学都知道聊天是需要进行连接的,使用XMPPConnection类即可。进行连接之前我们需要构造一个XMPPConnection,XMPPConnection的构造方法有很多,public XMPPConnection(String serviceName, CallbackHandler callbackHandler),public XMPPConnection(String serviceName),public XMPPConnection(ConnectionConfiguration config),public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler),我这里是使用public XMPPConnection(ConnectionConfiguration config)这个的,因为考虑到ConnectionConfiguration 这个类中有host和port这两个字段,刚好代表我们所要连接远程的主机地址和端口,ConnectionConfiguration类也有很多的构造的方法:public ConnectionConfiguration(String serviceName),public ConnectionConfiguration(String serviceName, ProxyInfo proxy),public ConnectionConfiguration(String host, int port, String serviceName),public ConnectionConfiguration(String host, int port, String serviceName, ProxyInfo proxy),public ConnectionConfiguration(String host, int port),public ConnectionConfiguration(String host, int port, ProxyInfo proxy),我使用的自然是public ConnectionConfiguration(String host, int port)这个构造方法,其他的像serviceName,proxy的都是差不多的,主要的还是连接到服务器上,这几个构造方法中最终都会调用下面的这个init方法:
private void init(String host, int port, String serviceName, ProxyInfo proxy)
{
this.host = host;
this.port = port;
this.serviceName = serviceName;
this.proxy = proxy;
String javaHome = System.getProperty("java.home");
StringBuilder buffer = new StringBuilder();
buffer.append(javaHome).append(File.separator).append("lib");
buffer.append(File.separator).append("security");
buffer.append(File.separator).append("cacerts");
truststorePath = buffer.toString();
truststoreType = "jks";
truststorePassword = "changeit";
keystorePath = System.getProperty("javax.net.ssl.keyStore");
keystoreType = "jks";
pkcs11Library = "pkcs11.config";
socketFactory = proxy.getSocketFactory();
}
接下来就是具体连接的代码了,很简单的:
private ConnectionConfiguration connectionConfig;
private XMPPConnection connection;
public ChatServiceImpl() {
}
/**
* 构造 IMServer(host,port)
*/
public ChatServiceImpl(String host, int port) {
connectionConfig = new ConnectionConfiguration(host, port);
// connectionConfig.setSASLAuthenticationEnabled(false);
connection = new XMPPConnection(connectionConfig);
try {
connection.connect();
logger.info("链接成功,服务名:" + connection.getServiceName());
} catch (XMPPException e) {
e.printStackTrace();
}
}
有了这些基本工作之后,我们就可以开始进行互相聊天了,具体的功能视具体项目需求来写,像我这里是需要有多人聊天的,那么就会发生进入房间和离开房间的情况,我们需要对这两种情况进行监听,这时用到的API是DefaultParticipantStatusListener,这个类可以监听人的聊天状态。这个类当中有很多的关于状态的方法,我们不需要全部实现这些方法,所以这里我是继承了DefaultParticipantStatusListener这个类来实现特定的方法的:
private static class DefParticipantStatusListener extends DefaultParticipantStatusListener {
private final ChatServiceImpl chatService;
public DefParticipantStatusListener(ChatServiceImpl chatService) {
this.chatService = chatService;
}
/**
* @see org.jivesoftware.smackx.muc.DefaultParticipantStatusListener#joined(java.lang.String)
*/
@Override
public void joined(String participant) {
chatService.addUser(participant);
chatService.userShip.setStatus(true);
logger.info(participant + " - 进入了房间");
}
/**
* @see org.jivesoftware.smackx.muc.DefaultParticipantStatusListener#left(java.lang.String)
*/
@Override
public void left(String participant) {
chatService.removeUser(participant);
chatService.userShip.setStatus(true);
logger.info(participant + " - 离开了房间");
}
}
接着需要对具体的某个人的聊天记录进行存储,我是实现PacketListener这个监听类来实现的,这个监听类只有processPacket这一个处理消息包的类,参数自然为一个消息包Packet了,可是我们处理消息是对消息体进行处理,在Packet中没有发现有消息体的相关属性,所以我就看Packet有没有什么子类,发现有一个Message类中有对body的相关操作:
final Map<String, List<String>> chatMap = new HashMap<String, List<String>>();//chatMap里存放的就是某一个具体的人的所有聊天记录列表
private static class DefaultPacketListener implements PacketListener {
private final ChatServiceImpl chatService;
private final String userName;
public DefaultPacketListener(ChatServiceImpl chatService, String userName) {
this.userName = userName;
this.chatService = chatService;
}
@Override
public void processPacket(Packet packet) {
List<String> list = null;
Message message = (Message) packet;
Iterator<Body> it = message.getBodies().iterator();
if (it != null) {
while (it.hasNext()) {
Body body = it.next();
logger.info(body.getMessage());
if (chatService.chatMap.containsKey(userName)) {
logger.info("已经存在 - " + userName);
list = chatService.chatMap.get(userName);
} else {
logger.info("不存在 - " + userName);
list = new ArrayList<String>();
}
String msg = body.getMessage();
list.add(message.getFrom() + " 说: " + msg);
chatService.setChatMap(userName, list);
}
}
}
}
有了上面两个listener,就可以进入房间进行聊天了,这里是使用MultiUserChat的public synchronized void join(String nickname, String password, DiscussionHistory history, long timeout)这个方法实现的,这个方法是同步的,保证了线程安全。
public boolean JoinRoom(final String userName, String password, String group, DiscussionHistory history, Long timeout, ChatServiceImpl chatService) {
MultiUserChat mChat = null;
try {
// SASLAuthentication.supportSASLMechanism("bfmp", 0);
connection.login(userName, password);
loginState = true;
mChat = new MultiUserChat(connection, group);
mUserChat = mChat;
mChat.join(userName, password, history, SmackConfiguration.getPacketReplyTimeout());
mChat.addMessageListener(new DefaultPacketListener(chatService, userName));
mChat.addParticipantStatusListener(new DefParticipantStatusListener(this));
} catch (XMPPException e) {
e.printStackTrace();
logger.error(e.fillInStackTrace());
}
return loginState;
}
接下来的就是具体的发送消息,接收消息,好友列表展示的实现了,这些代码就简单了,不再详解:
public void sendGroupMessage(String content) {
try {
mUserChat.sendMessage(content);//使用MultiUserChat类的sendMessage方法实现发送群信息
} catch (XMPPException e) {
e.printStackTrace();
}
}
/**
* @see com.dasongame.chat.ChatService#receiveGroupMessage(java.lang.String)
*/
@Override
public List<String> receiveGroupMessage(String userName, ChatServiceImpl chatService) {
List<String> list = null;
if (chatMap.containsKey(userName)) {
list = chatMap.get(userName);
setChatMap(userName, new ArrayList<String>());
}
return list;
}
/**
* @see com.dasongame.chat.ChatService#getUserShip()
*/
@Override
public List<String> getOccupants(ChatServiceImpl chatSerivce) {
List<String> list = new ArrayList<String>();
Iterator<String> it = null;
it = chatSerivce.mUserChat.getOccupants();//获得由多少个人在进行聊天
while (it != null && it.hasNext()) {
list.add(it.next());
}
userShip.setStatus(false);
return list;
}
/**
* @see com.dasongame.chat.ChatService#getUserShipStatus()
*/
@Override
public boolean isOccupantsChange(ChatServiceImpl chatSerivce) {
return chatSerivce.userShip.isStatus();
}
/**
* @see com.dasongame.chat.ChatService#sendMessage(java.lang.String, java.lang.String)
*/
@Override
public void sendMessage(String message, String to) {
Chat chat = connection.getChatManager().createChat(to, new MessageListener() {
@Override
public void processMessage(Chat chat, Message message) {
System.out.println(message.getFrom() + " - " + message.getBody() + " - " + new Date().getTime());
}
});
try {
chat.sendMessage(message);//实现单人信息的发送
} catch (XMPPException e) {
System.out.println("发送消息出错");
e.printStackTrace();
}
}
/**
* @see com.dasongame.chat.ChatService#displayBuddyList()
*/
@Override
public void displayBuddyList() {
roster = connection.getRoster();
if (roster != null) {
roster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
Collection<RosterEntry> entries = roster.getEntries();
System.out.println("好友个数:" + roster.getEntryCount());
System.out.println("好友列表:");
for (RosterEntry r : entries) {
System.out.println("\t" + r.getUser());
}
}
}
还有就是断开连接的方法:
public void disconnect() {
connection.disconnect();
}
这样基本上已经实现聊天的基本需求了。又过中午了,肚子早就叫了,吃饭去。