文章最后附上Demo地址
XMPP协议(Extensible Messaging and PresenceProtocol,可扩展消息处理现场协议)是一种基于XML的协议,目的是为了解决及时通信标准而提出来的,最早是在Jabber上实现的。它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。并且XML很易穿过防火墙,所以用XMPP构建的应用不易受到防火墙的阻碍。利用XMPP作为通用的传输机制,不同组织内的不同应用都可以进行有效的通信。
IM(Instant Messenger,即时通信软件),比如QQ、微信、Gtalk(谷歌推出的一款IM软件),其中Gtalk 就是基于XMPP 协议的一个实现。
smack是一套很好的开源即时通讯api,是基于Xmpp协议的实现,而在Android,则提供了一个asmack的jar包,其功能以及用法跟smack一致。
服务器端——Openfire,Android客户端——基于asmack的使用
(注:openfire是基于XMPP 协议的IM 的服务器端的一个实现。搭建OpenFire服务器请看另外篇文章OpenFire服务器搭建)
aSmack是一个简单的,功能强大的类库。给用户发送信息只需三行代码便可完成
XMPPConnection connection = new XMPPTCPConnection(”jabber.org“);
connection.login(”mtucker”, “password”);
connection.createChat(”jsmith@jivesoftware.com“).sendMessage(”Howdy!”);
不会强迫你向其他类库那样,在信息包层面进行编码。它提供了更加智能化的类比如Chat,能使你的工作更富效率。
不需要你熟悉XMPP,甚至是XML格式。
易于实现机-机对话。
Apace License下的开源软件。你可以把它用于你的商业或非商业程序。
使用asmack之前本地或者远程必须有个服务器openFire支持,如何搭建请看另外篇文章OpenFire服务器搭建
搭建完开启openfire服务器(进入Openfire安装目录,点击bin目录下的openfired.exe)
测试是否开启服务器 在浏览器输入 127.0.0.1:9090
在Android客户端需要实现IM及时通讯的功能点有:
好友列表、群列表
好友聊天:消息发送与接收,图片发送与接收
下面将会介绍如何去使用asmack去实现各个功能点
在两人进行聊天时,需要实时的拿到消息,必须保证对服务器的实时访问,不断地拿数据,所有需要保持一个与服务器的长连接,进行数据交互。
所以asmack提供了一个XMPPConnection的类来保持一个与服务器的长连接(建议采用单例模式)。
具体使用如下:
XMPPConnection.DEBUG_ENABLED = true;
//配置文件 参数(服务地地址,端口号,域)
ConnectionConfiguration conConfig = new ConnectionConfiguration(
SERVER_HOST, SERVER_PORT, SERVER_NAME);
//设置断网重连 默认为true
conConfig.setReconnectionAllowed(true);
//设置登录状态 true-为在线
conConfig.setSendPresence(true);
//设置不需要SAS验证
conConfig.setSASLAuthenticationEnabled(true);
//开启连接
XMPPConnection connection = new XMPPConnection(conConfig);
connection.connect();
//添加额外配置信息
configureConnection();
用户必须注册一个账户,作为一个用户身份识别的作用。
asmack提供了一个Registration的类使用(XmppConnection是自定义的一个XMPPconnection配置工具类,下文将会经常用到),注册操作是一个网络耗时操作,要放在子线程中去执行。
Registration reg = new Registration();
//设置类型
reg.setType(IQ.Type.SET);
//发送到服务器
reg.setTo(XmppConnection.getConnection().getServiceName());
//设置用户名
reg.setUsername(account);
//设置密码
reg.setPassword(pass);
//设置其余属性 不填可能会报500异常 连接不到服务器 asmack一个Bug
//设置昵称(其余属性)
reg.addAttribute("name", name);
//设置邮箱(其余属性)
reg.addAttribute("email", email);
//设置android端注册
reg.addAttribute("android", "geolo_createUser_android");
//创建包过滤器
PacketFilter filter = new AndFilter(new PacketIDFilter(reg
.getPacketID()), new PacketTypeFilter(IQ.class));
//创建包收集器
PacketCollector collector = XmppConnection.getConnection()
.createPacketCollector(filter);
//发送包
XmppConnection.getConnection().sendPacket(reg);
//获取返回信息
IQ result = (IQ) collector.nextResult(SmackConfiguration
.getPacketReplyTimeout());
// 停止请求results(是否成功的结果)
collector.cancel();
//通过返回信息判断
if (result == null) { //无返回,连接不到服务器
} else if (result.getType() == IQ.Type.ERROR) { //错误状态
if (result.getError().toString()
.equalsIgnoreCase("conflict(409)")) { //账户存在 409判断
} else {
}
} else if (result.getType() == IQ.Type.RESULT) {//注册成功跳转登录
}
在OpenFire网页端上查看是否注册成功
asmack提供了Presence的一个类
Presence介绍:
Presence是继承自XMPP的基类Packet信息包,Presence主要有两个用途:
① 告诉服务器所有客户端当前所处的状态,
② 发出添加/删除好友请求;每个Presence信息包都有一个类型属性Presence.Type 如下:
available: 表示处于在线状态
unavailable: 表示处于离线状态
subscribe: 表示发出添加好友的申请
unsubscribe: 表示发出删除好友的申请
unsubscribed: 表示拒绝添加对方为好友
error: 表示presence信息报中包含了一个错误消息。
// 连接服务器,用户登录
XmppConnection.getConnection().login(userStr, passStr);
// 连接服务器成功,更改在线状态
Presence presence = new Presence(Presence.Type.available);
XmppConnection.getConnection().sendPacket(presence);
分为三部分:①自己发送好友添加请求,②监听发送过来的好友请求,③处理添加好友请求(拒绝和允许)。
1) 发起用户添加申请
使用Presence类,通过Presence.Type.subscribe表示发出添加好友的申请的状态,让服务器知道发送的数据是一个好友添加申请。
String name = et_name.getText().toString();
//设置添加好友请求
Presence subscription = new Presence(Presence.Type.subscribe);
//拼接好友全称
subscription.setTo(name + "@" + XmppConnection.SERVER_NAME);
//发送请求
XmppConnection.getConnection().sendPacket(subscription);
2) 监听添加好友申请
需要解决的是如何知道服务器发送过来的是好友添加的一个请求?通过asmack提供的一个PacketFilter筛选器和PacketListener数据监听类,发起一个addPacketListener(PacketListener,PacketListener),开启数据监听,通过PacketFilter筛选器筛选packet,监听是否是Presence.Type.subscribe添加好友申请的状态。
/**
* 添加一个监听,监听好友添加请求。
*/
private void addSubscriptionListener() {
//创建包过滤器
PacketFilter filter = new PacketFilter() {
@Override
public boolean accept(Packet packet) {
if (packet instanceof Presence) {
Presence presence = (Presence) packet;
//是好友邀请状态就返回true 向下执行
if (presence.getType().equals(Presence.Type.subscribe)) {
return true;
}
}
return false;
}
};
//开启监听
XmppConnection.getConnection().addPacketListener(subscriptionPacketListener, filter);
}
/**
* 好友监听
*/
private PacketListener subscriptionPacketListener = new PacketListener() {
@Override
public void processPacket(final Packet packet) {
//过滤自己加自己的状态
if (packet.getFrom().contains(((AppGlobal) getApplication()).getName()
+ "@" + XmppConnection.SERVER_NAME))
return;
//弹出好友添加对话框
Message msg = new Message();
msg.obj = packet;
msg.what = ADD_FRIEND;
handler.sendMessage(msg);
}
};
3)处理好友添加申请
asmack提供一个Roster类
Roster:表示一个用户的所有好友清单以及申请加好友的用户清单的一个类
①允许添加:添加好友进入自己的好友列表
//获取Roster对象
Roster roster = XmppConnection.getConnection().getRoster();
try {
//packet是上文PacketListener监听返回的packet
roster.createEntry(packet.getFrom(), name, null);
} catch (XMPPException e) {
e.printStackTrace();
}
②拒绝好友申请(注:虽然拒绝好友添加。但是对方仍可以把你添加进对方列表。asmack添加好友的方式是通过订阅的方式,只要订阅你,对方就可以添加进他的好友列表)
Presence presenceRes = new Presence(Presence.Type.unsubscribe);
presenceRes.setTo(packet.getFrom());
XmppConnection.getConnection().sendPacket(presenceRes);
通过Rooster中的removeEntry方法将好友移除
RosterEntry类:表示Roster中的每条记录.它包含了用户的JID,用户名,或用户分配的昵称.
XMPPConnection conn = XmppConnection.getConnection();
Roster roster = conn.getRoster();
RosterEntry entry = roster.getEntry(friendList.get(position).get("User"));
try {
roster.removeEntry(entry);
} catch (XMPPException e) {
e.printStackTrace();
}
RosterEntry类:表示Roster中的每条记录.它包含了用户的JID,用户名,或用户分配的昵称.
Roster roster = XmppConnection.getConnection().getRoster();
Collection entries = roster.getEntries();
//循环拿到所有好友信息
for (RosterEntry entry : entries) {
}
针对消息发送与接受的数据,asmack单独提供了一个ChatManager的聊天室工具类和Chat消息对象
1)获取好友消息
通过获取到ChatManager类,然后开启聊天室监听,通过获取到的chat对象,开启消息监听 MessageListener
//获取消息管理类
ChatManager chatMan = XmppConnection.getConnection().getChatManager();
//添加聊天室监听
chatMan.addChatListener(new ChatManagerListener() {
@Override
public void chatCreated(Chat chat, boolean able) {
// 添加消息监听
chat.addMessageListener(new MessageListener() {
@Override
public void processMessage(Chat chat, Message message) {
// 获取当前好友发来的信息
if (message.getFrom().contains(toUserID)) {
}
}
});
}
});
2)发送消息
//获取消息管理类
ChatManager chatMan = XmppConnection.getConnection().getChatManager();
//创建消息对象 参数(用户名称,MessageListener消息监听)
Chat newchat = chatMan.createChat(toUserID, null);
try {
Message msg = new Message();
String content = "消息内容"
msg.setBody(content);
// 发送消息
newchat.sendMessage(msg);
} catch (XMPPException e) {
e.printStackTrace();
}
针对发送图片,asmack提供了FileTransferManager文件传输的工具类,文件接收类IncomingFileTransfe
文件发送类OutgoingFileTransfer。我们可以通过把图片转换成数据的格式进行传输。
1)接收图片
文件消息监听,注册一个文件消息监听类FileTransferListener,接收到文件后先保存到本地,然后通过Handler传递保存路径的字符串,然后去刷新adapter,在adapter中再去加载下载到的本地图片。(只贴重要代码)
FileTransferManager manager = XmppConnection.getFileTransferManager();
manager.addFileTransferListener(new FileTransferListener() {
@Override
public void fileTransferRequest(final FileTransferRequest request) {
new Thread() {
@Override
public void run() {
//文件接收
IncomingFileTransfer transfer = request.accept();
//获取文件名字
String fileName = transfer.getFileName();
//本地创建文件
File sdCardDir = new File(ALBUM_PATH);
if (!sdCardDir.exists()) {//判断文件夹目录是否存在
sdCardDir.mkdir();//如果不存在则创建
}
String save_path = ALBUM_PATH + fileName;
File file = new File(save_path);
//接收文件
try {
transfer.recieveFile(file);
while (!transfer.isDone()) {
if (transfer.getStatus().equals(FileTransfer.Status.error)) {
System.out.println("ERROR!!! " + transfer.getError());
} else {
System.out.println(transfer.getStatus());
System.out.println(transfer.getProgress());
}
try {
Thread.sleep(1000L);
} catch (Exception e) {
}
}
//判断是否完全接收文件
if (transfer.isDone()) {
String[] args = new String[]{toUserName, fileName};
android.os.Message msg = handler.obtainMessage();
msg.what = 2;
msg.obj = args;
//发送msg,刷新adapter显示图片
msg.sendToTarget();
}
} catch (XMPPException e) {
e.printStackTrace();
}
}
;
}.start();
}
}
2)发送图片
①添加一个点击方法,打开本地图库
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, TUPIAN_RESULT);
②选择完图片在onActivityResult拿到图片路径
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//图片路径
String picPath;
if (data != null) {
Uri uri = data.getData();
if (!TextUtils.isEmpty(uri.getAuthority())) {
Cursor cursor = getContentResolver().query(uri,
new String[]{MediaStore.Images.Media.DATA}, null, null, null);
if (null == cursor) {
Toast.makeText(this, "图片没找到", Toast.LENGTH_SHORT).show();
return;
}
cursor.moveToFirst();
//图片路径
picPath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
} else {
//图片路径
picPath = uri.getPath();
}
} else {
Toast.makeText(this, "图片没找到", Toast.LENGTH_SHORT).show();
return;
}
// 开始发送图片 参数(图片路径,接收者账户)
new SendFileTask().execute(picPath, toUserID);
}
③通过异步的方式AsyncTask去发送图片
class SendFileTask extends AsyncTask<String, Integer, Integer> {
protected Integer doInBackground(String... params) {
if (params.length < 2) {
return Integer.valueOf(-1);
}
String img_path = params[0];
//asmack的一个坑,如果接收者名称后缀不加上/Smack,则会报503错误
String toId = params[1] + "/Smack";
FileTransferManager fileTransferManager = XmppConnection.getFileTransferManager();
File filetosend = new File(img_path);
if (filetosend.exists() == false) {
return -1;
}
OutgoingFileTransfer transfer = fileTransferManager
.createOutgoingFileTransfer(toId);// 创建一个输出文件传输对象
try {
transfer.sendFile(filetosend, "recv img");
while (!transfer.isDone()) {
if (transfer.getStatus().equals(FileTransfer.Status.error)) {
System.out.println("ERROR!!! " + transfer.getError());
} else {
System.out.println(transfer.getStatus());
System.out.println(transfer.getProgress());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//文件发送完毕
if (transfer.isDone()) {
String[] args = new String[]{mAppGlobal.getName(), img_path};
android.os.Message msg = handler.obtainMessage();
msg.what = 3;
msg.obj = args;
//刷新adapter
msg.sendToTarget();
}
} catch (XMPPException e1) {
e1.printStackTrace();
}
return 0;
}
}
针对群聊功能,asmack提供了创建聊天室以及获取聊天群信息的一个类——MultiUserChat
MultiUserChat.getHostedRooms方法遍历获取到整个服务器上的所有群聊列表。
try {
//遍历每个人所创建的群
for (HostedRoom host :MultiUserChat.getHostedRooms(XmppConnection.getConnection(), XmppConnection.getConnection().getServiceName())) {
//遍历某个人所创建的群
for (HostedRoom singleHost : MultiUserChat.getHostedRooms(XmppConnection.getConnection(), host.getJid())) {
if (singleHost.getJid().indexOf("@") > 0) {
Map map = new HashMap<>();
map.put("Gname", singleHost.getName());
groupList.add(map);
}
}
}
mHandler.sendEmptyMessage(0);
} catch (XMPPException e) {
e.printStackTrace();
}
public static boolean createRoom(String user, String roomName,
String password) {
if (getConnection() == null)
return false;
MultiUserChat muc = null;
try {
// 创建一个MultiUserChat 参数(XmppConnection,群全称)
muc = new MultiUserChat(getConnection(), roomName + "@conference."
+ getConnection().getServiceName());
// 创建聊天室
muc.create(roomName);
// 获得聊天室的配置表单
Form form = muc.getConfigurationForm();
// 根据原始表单创建一个要提交的新表单。
Form submitForm = form.createAnswerForm();
// 向要提交的表单添加默认答复
for (Iterator fields = form.getFields(); fields
.hasNext();) {
FormField field = (FormField) fields.next();
if (!FormField.TYPE_HIDDEN.equals(field.getType())
&& field.getVariable() != null) {
// 设置默认值作为答复
submitForm.setDefaultAnswer(field.getVariable());
}
}
// 设置聊天室的新拥有者
List owners = new ArrayList<>();
owners.add(getConnection().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.equals("")) {
// 进入是否需要密码
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 e) {
e.printStackTrace();
return false;
}
return true;
}
加入聊天室,得知道这个房间的名称,通过群列表那里可以获取到,然后创建MultiUserChat对象,调用join方法
public static void joinMultiUserChat(String user, String password, String roomsName) {
try {
// 使用XMPPConnection创建一个MultiUserChat窗口
MultiUserChat muc = new MultiUserChat(connection, roomsName
+ "@conference." + connection.getServiceName());
// 聊天室服务将会决定要接受的历史记录数量
DiscussionHistory history = new DiscussionHistory();
history.setMaxStanzas(0);
// 用户加入聊天室 参数()
muc.join(user, password, history, SmackConfiguration.getPacketReplyTimeout());
//保存multiUserChat对象
multiUserChat = muc;
System.out.println("会议室加入成功........");
} catch (XMPPException e) {
e.printStackTrace();
System.out.println("会议室加入失败........");
muc = null;
}
}
1)消息监听
MultiUserChat muc = XmppConnection.getMultiUserChat();
muc.addMessageListener(new PacketListener() {
@Override
public void processPacket(Packet packet) {
Message message = (Message) packet;
// 接收来自聊天室的聊天信息
String groupName = message.getFrom();
String[] nameOrGroup = groupName.split("/");
//判断是否是本人发出的消息 不是则显示
if (!nameOrGroup[1].equals(getUserName())) {
String[] args = new String[]{nameOrGroup[1], message.getBody()};
// 在handler里取出来显示消息
android.os.Message msg = handler.obtainMessage();
msg.what = 1;
msg.obj = args;
msg.sendToTarget();
}
}
});
2)发送群消息
try {
muc.sendMessage(content);
} catch (XMPPException e) {
e.printStackTrace();
}