1. 开发环境
- smack-4.3.4
- openfire-4.6.3
- 开发工具:AndroidStudio
openfire下载及安装:
- 官网:https://igniterealtime.org/projects/openfire/
- CSDN:https://download.csdn.net/download/JieZhongBa/20015330
- 安装教程:<>
本文项目地址:https://blog.csdn.net/JieZhongBa/article/details/118460210
2. 依赖
// UI 可选
implementation 'com.github.stfalcon-studio:Chatkit:0.4.1'
// Smack
implementation "org.igniterealtime.smack:smack-tcp:4.3.4"
// 下面两个二选一,带extensions的包大一点,顾名思义。
// implementation "org.igniterealtime.smack:smack-android:4.3.4"
implementation "org.igniterealtime.smack:smack-android-extensions:4.3.4"
其中UI是github的一个开源框架,用起来还行,有兴趣的可以试试,就是文档比较少,官方是纯英文的。
官方地址:https://github.com/stfalcon-studio/ChatKit
教程博客:https://blog.csdn.net/JieZhongBa/article/details/118459924
3. 连接服务
Config,供全局使用,注意服务器名称和主机名不要混淆了,其实就是初始化时的域名,避免错误就直接设成一样的就好了。
//Config
public static final String C_HOST = "IP";//可访问到openfire服务的IP地址
public static final int C_PORT = 5222;//端口,默认5222
public static final String C_DOMAIN = "up55";//服务器名称
public static final String C_SOURCE = "YunShang";//源,自己随便写,也可以不加这个
连接服务器
try {
System.out.println("正在连接Chat服务。。。");
XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder()
.setResource(Resourcepart.from(C_SOURCE))//固定源
.setHostAddress(InetAddress.getByName(C_HOST))//IP
.setPort(C_PORT)//端口
.setXmppDomain(C_DOMAIN)//服务器名称
.setSendPresence(false)//以离线方式登录,以便获取离线消息
.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
.build();
connection = new XMPPTCPConnection(configuration);
connection.connect();
System.out.println("Chat服务连接成功");
} catch (Exception e) {
System.out.println("Chat服务连接失败" + e.getLocalizedMessage());
e.printStackTrace();
}
4. 登录
connection.login(user, pass);
5. 登出
connection.sendStanza(new Presence(Presence.Type.unavailable));//设为下线
connection.disconnect();//断开连接
6. 注册
其中map里的信息是选填,key对应openfire的用户属性,account和password为必填项。
HashMap<String, String> map = new HashMap<>();
map.put("name", "张三");//可选
map.put("email", "zs@csdn.com");//可选
AccountManager instance = AccountManager.getInstance(connection);
instance.sensitiveOperationOverInsecureConnection(true);
instance.createAccount(Localpart.from(account), password, map);//map为可选
7. 注销(慎用)
删除当前用户的所有信息,慎用!
AccountManager.getInstance(connection).deleteAccount();
8. 搜索用户
之前网上找的,注释也比较多,原文不知道在哪了
try {
UserSearchManager usm = new UserSearchManager(connection);
//本例用的smack:4.3.4版本,getSearchForm方法传的是DomainBareJid类型,而之前的版本是String类型,大家在使用的时候需要特别注意
//而转换DomainBareJid的方式如下面的例子所示:JidCreate.domainBareFrom("search." + getConnection().getXMPPServiceDomain())
Form searchForm = usm.getSearchForm(JidCreate.domainBareFrom("search." + connection.getXMPPServiceDomain()));
if (searchForm == null) {
return;
}
//这里设置了Username为true代码是根据用户名查询用户,search代表查询字段
//smack:4.3.4版本是下面的字段,但之前的版本会有些不一样,所以在用的时候最好看下xmpp交互的log,里面有相应的字段值
Form answerForm = searchForm.createAnswerForm();
answerForm.setAnswer("Username", true);
answerForm.setAnswer("Name", true);
answerForm.setAnswer("search", "张三");
ReportedData data = usm.getSearchResults(answerForm, JidCreate.domainBareFrom("search." + connection.getXMPPServiceDomain()));
List<ReportedData.Row> rowList = data.getRows();
//此处返回的字段名如下所示,之前的版本可能有所变化,使用的时候需要注意
for (ReportedData.Row row : rowList) {
String jid = row.getValues("jid").toString();
String username = row.getValues("Username").toString();
String name = row.getValues("Name").toString();
String email = row.getValues("Email").toString();
System.out.println(jid + "," + username + "," + name + "," + email);
// 若存在,则有返回,UserName一定非空,其他两个若是有设,一定非空
}
} catch (Exception e) {
e.printStackTrace();
}
9. 连接状态
主要是供外部调用,获取连接状态。
//是否连接服务器
public boolean isConnected() {
return connection.isConnected();
}
//是否登录
public boolean isLogin() {
return connection.isAuthenticated();
}
10. 接收离线消息
try {
OfflineMessageManager offlineMessageManager = new OfflineMessageManager(connection);
List<Message> messageList = offlineMessageManager.getMessages();
System.out.println("离线消息:" + messageList.size()+"条");
for (Message message : messageList) {
System.out.println(message.getFrom() + ",," + message + ":" + message.getBody());
}
offlineMessageManager.deleteMessages();//通知服务器删除离线消息,否则下次还在
connection.sendStanza(new Presence(Presence.Type.available));//上线
} catch (Exception e) {
e.printStackTrace();
}
11. 发送消息
为了方便其实是用idea写的,Android的话测试比较麻烦,后面会吧我写的Android项目发出来。
简单的从控制台输入。
ChatManager chatManager = ChatManager.getInstanceFor(connection);
try {
EntityBareJid jid = JidCreate.entityBareFrom("540@up55");//对方的jid(账号+@+服务器名称)
Chat chat = chatManager.chatWith(jid);
Scanner sc = new Scanner(System.in);
if (chat != null) {
while (true) {
String mes = sc.next();
chat.send(mes);
if ("exit".equals(mes))
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
12. 消息收发监听
ChatManager chatManager = ChatManager.getInstanceFor(connection);
//消息发送监听
chatManager.addOutgoingListener((entityBareJid, message, chat1) -> {
System.out.println("发送成功:" + message.getBody());
});
//接收消息监听
chatManager.addIncomingListener((entityBareJid, message, chat) -> {
System.out.println(message.getFrom().getLocalpartOrThrow() + ":" + message.getBody());
});
13. Android实现的思路
我是用一个service保持和服务器的通信,然后自定义了回调接口,在activity中实现,service监听到消息则调用回调,activity刷新页面,同时service提供了发送消息功能,具体实现当然会比这个复杂点,而且要规范也不能这样写,有兴趣可以去看看我的项目,不过写的比较乱,写完也觉得很多地方写的不太好,比如应该继承AppCompatActivity写一个自己的,方便activity和service的通信和即时通讯功能的管理,另外也不一定要用回调的形式,也可以用广播之类的实现消息传递。
14. 踩坑记录
- 连局域网的时候不加端口号也行,因为默认5222,后来部署到远程,一直连接超时,用idea测试也没问题(不知道为什么idea不加端口号也行),Android里一直不行,后来加上端口号就好了。。。
- smack版本变化不少,而且网上教程比较少,很多是老版本的,只能稍微参考,得找相近版本的。
- openfire的搜索服务有时候会出毛病,把服务器缓存清掉就好了,再不行重启服务器,千万不要在插件里重新启动,会出问题,之后就只能删掉这个插件在重新安装了。