您的位置:首页 > 移动开发 > Android开发

Android基于XMPP Smack openfire 开发的聊天室

2015-02-10 10:33 309 查看


Android基于XMPP Smack openfire 开发的聊天室

分类: android2013-03-07
17:36 5237人阅读 评论(0) 收藏 举报

聊天smackxmppstringandroid

目录(?)[+]

公司刚好让做即时通讯模块,服务器使用openfire,偶然看到有位仁兄的帖子,拷贝过来细细研究,感谢此仁兄的无私,期待此仁兄的下次更新

转自/article/2813353.html


Android基于XMPP Smack openfire 开发的聊天室(一)【会议服务、聊天室列表、加入】

废话少说,公司没事干,组长叫我写Demo,我狂晕....

先把先说说这个什么聊天室吧,服务器就别说了,典型的拿来主义。用的是成品的openfire。说道smack,这个是一个jar文件,库,是用在javae的。要想在android端使用这个,就要用到asmack包,这个可以在官网下。其实里边跟smack包没什么区别,具体差异目前我没发现。

可能有的朋友说这个smack是什么用的,我个人一句话,一个已经包装了XMPP协议的架包。提供了API的操作...好了进入正题

1,首先想进入聊天室,那必须要先知道聊天室所在的会议服务(下图,用spark截图)



[java] view
plaincopy

/**

* 初始化聊服务会议列表

*/

private void initHostRoom() {

Collection<HostedRoom> hostrooms;

try {

hostrooms = MultiUserChat.getHostedRooms(Constants.conn,

Constants.conn.getServiceName());

for (HostedRoom entry : hostrooms) {

roominfos.add(entry);

Log.i(TAG, "名字:" + entry.getName() + " - ID:" + entry.getJid());

}

Log.i(TAG, "服务会议数量:" + roominfos.size());

} catch (XMPPException e) {

e.printStackTrace();

}

}

2,获取了那个服务的JID后才能进入会议列表:



[java] view
plaincopy

/**

* 初始化房间列表

*/

public void init() {

listDiscoverItems = new ArrayList<DiscoverItems.Item>();

// 获得与XMPPConnection相关的ServiceDiscoveryManager

ServiceDiscoveryManager discoManager = ServiceDiscoveryManager

.getInstanceFor(Constants.conn);

// 获得指定XMPP实体的项目

// 这个例子获得与在线目录服务相关的项目

DiscoverItems discoItems;

try {

discoItems = discoManager.discoverItems(jid);

// 获得被查询的XMPP实体的要查看的项目

Iterator it = discoItems.getItems();

// 显示远端XMPP实体的项目

while (it.hasNext()) {

DiscoverItems.Item item = (DiscoverItems.Item) it.next();

System.out.println(item.getEntityID());

System.out.println(item.getName());

listDiscoverItems.add(item);

}

} catch (XMPPException e) {

e.printStackTrace();

}

}

3,同样,要进入会议房间(聊天室)也要获取它的ID,才能进入

[java] view
plaincopy

// 后面服务名称必需是创建房间的那个服务

jid = getIntent().getStringExtra("jid");

try {

muc = new MultiUserChat(Constants.conn, jid);

// 创建聊天室,进入房间后的nickname(昵称)

muc.join("痞子测试");

Log.v(TAG, "join success");

} catch (XMPPException e) {

e.printStackTrace();

}

简单吧!


Android基于XMPP Smack openfire 开发的聊天室(二) 【聊天信息、成员】

上一篇呢说了怎么进入这个聊天室,这次呢,咱就说聊天室里的功能吧,聊天信息、成员变动什么的。还是少说废话,正题:

1,说要聊天呢,简单就是一个文本信息,当然我们不能时时去服务器获取信息。要充分发挥即时推送嘛。

(1)首先要添加一个监听,muc.addMessageListener(chatListener); 要是muc不知道是什么请看上一篇吧,这就不多解释。chatListener就是我们的监听器,看代码,其实下边的代码有些啰嗦。我只是懒得改了。最近有点烦躁。这里我们主要获取的就是Packet,这个是一个XMPP包装好的XML流,里边有你想要的东西。有兴趣深入的朋友可以上XMPP中文翻译组去看看,挺犀利的。

[java] view
plaincopy

/**

* PacketListener 通过一个规定的过滤器提供一个机制来监听数据包

*

* @author liaonaibo

*

*/

class ChatPacketListener implements PacketListener {

private String _number;

private Date _lastDate;

private MultiUserChat _muc;

private String _roomName;

public ChatPacketListener(MultiUserChat muc) {

_number = "0";

_lastDate = new Date(0);

_muc = muc;

_roomName = muc.getRoom();

}

@Override

public void processPacket(Packet packet) {

Message message = (Message) packet;

String from = message.getFrom();

if (message.getBody() != null) {

DelayInformation inf = (DelayInformation) message.getExtension(

"x", "jabber:x:delay");

Date sentDate;

if (inf != null) {

sentDate = inf.getStamp();

} else {

sentDate = new Date();

}

Log.i(TAG, "Receive old message: date="

+ sentDate.toLocaleString() + " ; message="

+ message.getBody());

android.os.Message msg = new android.os.Message();

msg.what = RECEIVE;

Bundle bd = new Bundle();

bd.putString("from", from);

bd.putString("body", message.getBody());

msg.setData(bd);

handler.sendMessage(msg);

}

}

}

2,下边就是成员了,一个聊天室没成员多离谱的事情啊,我们主要做的是把成员列表逃出来。其实有几种方法可以弄出来的,我只是简单的获取成员的昵称。可能有朋友会问为什么不获取成员的信息呢。这个下一篇我会告诉你。

[java] view
plaincopy

/**

* 获取聊天室的所有成员

*/

private void getAllMember() {

Log.i(TAG, "获取聊天室的所有成员");

affiliates.clear();

new Thread(new Runnable() {

@Override

public void run() {

try {

Iterator<String> it = muc.getOccupants();

while (it.hasNext()) {

String name = it.next();

name = name.substring(name.indexOf("/") + 1);

affiliates.add(name);

Log.i(TAG, "成员名字;" + name);

}

} catch (Exception e) {

e.printStackTrace();

}

android.os.Message msg = new android.os.Message();

msg.what = MEMBER;

handler.sendMessage(msg);

}

}).start();

}

这篇结束,下篇写写聊天室的一些权限的变更、成员的变更、主题变更什么的。


Android基于XMPP Smack openfire 开发的聊天室(三) 【新旧记录、踢人】

1,聊天室新旧消息的记录,先看看一段服务器返回的XML吧

[html] view
plaincopy

<message

from='darkcave@chat.shakespeare.lit/firstwitch'

to='hecate@shakespeare.lit/broom'

type='groupchat'>

<body>Thrice the brinded cat hath mew'd.</body>

<delay xmlns='urn:xmpp:delay'

from='crone1@shakespeare.lit/desktop'

stamp='2002-10-13T23:58:37Z'/>

</message>

<message

from='darkcave@chat.shakespeare.lit/secondwitch'

to='hecate@shakespeare.lit/broom'

type='groupchat'>

<body>Thrice and once the hedge-pig whined.</body>

<delay xmlns='urn:xmpp:delay'

from='wiccarocks@shakespeare.lit/laptop'

stamp='2002-10-13T23:58:43Z'/>

</message>

<message

from='darkcave@chat.shakespeare.lit/thirdwitch'

to='hecate@shakespeare.lit/broom'

type='groupchat'>

<body>Harpier cries 'Tis time, 'tis time.</body>

<delay xmlns='urn:xmpp:delay'

from='hag66@shakespeare.lit/pda'

stamp='2002-10-13T23:58:49Z'/>

</message>

上边这段是一个聊天室返回的XML数据,是历史消息。就在这里要告诉大家的是,如果是新消息的话就没有<delay>节点。所以我们以这个为根据来判断。下边贴些我的代码,因为服务器被我们给改了,所以返回的有些出入。但八九不离十。原理一样。

[java] view
plaincopy

DelayInformation inf = (DelayInformation) message.getExtension(

"x", "jabber:x:delay");

System.out.println("判断消息");

if (inf == null && count >= 1) {

System.out.println("新消息来了");

isHistory = true;

} else {

System.out.println("这是旧的消息");

}

这段代码写哪里呢,就是写在消息的监听里头

[java] view
plaincopy

/**

* PacketListener 通过一个规定的过滤器提供一个机制来监听数据包

*

* @author liaonaibo

*

*/

class ChatPacketListener implements PacketListener

重写它的方法

[java] view
plaincopy

@Override

public void processPacket(Packet packet)

好了,下边讲踢人吧

2,踢人:

踢人很简单,只要你是房主,或管理员什么的,主持人也行。这些高权限的才能有踢人的权限。

[java] view
plaincopy

// 踢人

try {

String nickName = affiliates.get(id);

//得到的昵称包含的房间名和服务名的一段很长数据。好像是吧。哈哈,忘记了。所以截取吧

muc.kickParticipant(nickName

.substring(nickName.indexOf("]") + 1), "看你不爽就 踢了你");

android.os.Message msg = new android.os.Message();

msg.what = MEMBER;

handler.sendMessage(msg);

Toast.makeText(this, "哈哈,踹了你", Toast.LENGTH_LONG).show();

} catch (XMPPException e) {

e.printStackTrace();

Toast.makeText(this, "你没有权利踢人", Toast.LENGTH_LONG).show();

}

有的哥们会疑惑,说如果根据昵称来踢人,那会不会把重名的人都一起踢了。这个问题我也想过,这个也是测试的一个弊端。但是如果通过spark来看,即便你用同样的名字加入了房间,要么会报错,要么就是系统自动在你的名字后边加个2,呵呵,够2吧。如果有人坚持要用自己的昵称,这个可以的,具体怎么样做,下次再阐述。


Android基于XMPP Smack openfire 开发的聊天室(四) 【创建房间、表单;报文】

这篇就主要讲创建房间和报文吧。

1,创建房间:

其实创建房间很简单,两句话就搞定了。但你要知道,简单的同时,服务器可没帮你配置房间的信息什么的。所以一般用下边的方法来创建房间别人是进不去的。

[java] view
plaincopy

// 使用XMPPConnection创建一个MultiUserChat

MultiUserChat muc = new MultiUserChat(conn1, "myroom@conference.jabber.org");

//创建聊天室

muc.create("testbot");

// 发送一个空表单配置这显示我们想要一个instant room

muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));

所以咱们还是手动配置一些信息吧,免得留后患。不过在这里要给朋友们提个醒,我觉得不论是哪种方法都很狗屎,为什么狗屎。你看看那MUC,本来就是一个聊天室了,还要让你在聊天室创建另个聊天室,老妈生孩子,然后孩子和老妈并排。狗屎啊,不过不排除我理解错误。希望高人能给与指点。

[java] view
plaincopy

try {

// 创建聊天室

muc.create(Constants.vCard.getNickName().toString());

// 获得聊天室的配置表单

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("liaonaibo2\\40slook.cc");

// owners.add("liaonaibo1\\40slook.cc");

// submitForm.setAnswer("muc#roomconfig_roomowners", owners);

// 设置聊天室是持久聊天室,即将要被保存下来

submitForm.setAnswer("muc#roomconfig_persistentroom", true);

// 房间仅对成员开放

submitForm.setAnswer("muc#roomconfig_membersonly", false);

// 允许占有者邀请其他人

submitForm.setAnswer("muc#roomconfig_allowinvites", true);

// 能够发现占有者真实 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();

}

还有个细节,有朋友估计看到我注释的那里有\\40的代码。原因是在XMPP中的唯一标示JID不允许出现@,所以要转义。OK?

2,报文

有朋友说报文干嘛,其实我开始也觉得有点多余,但想想类似QQ群那样,人下线了,头像名字变灰色。如果是openfire服务器的话,你下线了就直接把你去除。所以我们要给服务器发送个广播。具体处理那是以后的是了。看看这段报文:

[html] view
plaincopy

<iq id="902U0-48" type="set">

<query xmlns="jabber:iq:private">

<storage xmlns="storage:bookmarks">

<conference name="ccc" autojoin="true" jid="ccc@conference.xmpp.chaoboo.com" ></conference>

</storage>

</query>

</iq>

报文可以根据需要来改服务器的插件。

下边发送的报文:

[java] view
plaincopy

/**

* 用户加入时向服务器发送的报文

* @return

*/

public IQ joinXml(){

IQ iq = new IQ() {

public String getChildElementXML() {

StringBuilder buf = new StringBuilder();

buf.append("<query xmlns=\"jabber:iq:private\">");

buf.append("<storage xmlns=\"storage:bookmarks\">");

buf.append("<").append("conference").append(" name=\"ccc\"").append(" autojoin=\"false\"").append("");

buf.append("</storage>");

buf.append("</query>");

return buf.toString();

}

};

iq.setType(IQ.Type.SET);

//方法如名,这里是设置这份报文来至那个JID,后边的/smack是这段信息来至哪个端,如spark端就是/spark,android就是/Smack

iq.setFrom(Constants.USERNAME+"@naibo.liao.com/Smack");

return iq;

}

怎么发送呢?可以通过连接,就是XMPPCONNECTION来发送:

[java] view
plaincopy

Constants.conn.sendPacket(leaveXml());

OK,停一会,手酸了


Android基于XMPP Smack openfire 开发的聊天室(五) 【邀请、被邀请】

1,邀请:

很简单的一句话,邀请的时候要传入被邀请者的JID,邀请信息可以为空。

[java] view
plaincopy

muc.invite(userjid, "进来我们聊妹子");

简单的同时我们要知道它发生了什么,当这句话执行会向服务器发送一段报文,如下:

[html] view
plaincopy

<message

from='crone1@shakespeare.lit/desktop'

to='darkcave@chat.shakespeare.lit'>

<x xmlns='http://jabber.org/protocol/muc#user'>

<invite to='hecate@shakespeare.lit'>

<reason>

进来我们聊妹子

</reason>

</invite>

</x>

</message>

2,被邀请

被邀请呢,我们可以开个监听吧,要不要进房间先不判断了,我这里如果有邀请就直接进房间。有需要的朋友可以自己琢磨:

[java] view
plaincopy

MultiUserChat.addInvitationListener(Constants.conn,

new InvitationListener() {

// 对应参数:连接、 房间JID、房间名、附带内容、密码、消息

@Override

public void invitationReceived(Connection conn,

String room, String inviter, String reason,

String password, Message message) {

Log.i(TAG, "收到来自 " + inviter + " 的聊天室邀请。邀请附带内容:"

+ reason);

Intent intent = new Intent(MucService.this,

ActivityMultiRoom.class);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

intent.putExtra("jid", room);

intent.putExtra("action", "join");

startActivity(intent);

}

});


Android基于XMPP Smack openfire 开发的聊天室(六) 【加入房间、权限错误】

1,加入房间错误,通常一个就是密码错误。此时服务器会返回以下报文:

[html] view
plaincopy

<presence

from='darkcave@chat.shakespeare.lit'

to='hag66@shakespeare.lit/pda'

type='error'>

<x xmlns='http://jabber.org/protocol/muc'/>

<error type='auth'>

<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

</error>

</presence>

官网意思:如果房间要求密码验证而用户不能提供(或密码错误), 服务必须 MUST 拒绝访问这个房间并且通知该用户它们是未被授权的; 具体方法是返回一个类型为"error"的出席信息节并标明 <not-authorized/> 错误

解决如下:

[java] view
plaincopy

// 使用XMPPConnection创建一个MultiUserChat

MultiUserChat muc2 = new MultiUserChat(conn1,

"myroom@chat.shakespeare.lit/thirdwitch");

// 用户2使用密码加入新聊天室

// 聊天室服务将会决定要发送的历史记录数量

muc2.join("testbot2", "password");

报文如下:

[html] view
plaincopy

<presence

from='hag66@shakespeare.lit/pda'

to='myroom@chat.shakespeare.lit/thirdwitch'>

<x xmlns='http://jabber.org/protocol/muc'>

<password>password</password>

</x>

</presence>

2,权限错误:

这个没文本,当然如果你加入一个房间后,要注意的是,如果第一次加入别人的房间,没有被授予成员权限的时候,你仅仅是一个游客。除了说话。也不能检索聊天室成员的信息,仅可以获取成员的昵称,除此别无其他。通常不注意会用这样贸然去获取成员信息是,会照成错误,返回402(进入房间后获取超越权限错误:权限不足)等信息。以下是其他错误信息

401ErrorPresence 进入一个房间通知用户需要密码
403ErrorPresence 进入一个房间通知用户他或她被房间禁止了
404ErrorPresence 进入一个房间通知用户房间不存在
405ErrorPresence 进入一个房间通知用户限制创建房间
406ErrorPresence 进入一个房间通知用户必须使用保留的房间昵称
407ErrorPresence 进入一个房间通知用户他或她不在成员列表中
409ErrorPresence 进入一个房间通知用户他或她的房间昵称正在使用或被别的用户注册了
503ErrorPresence 进入一个房间通知用户已经达到最大用户数
用户聊天的使用一个 <status/> 元素(特指, <status/> 元素的的 'code' 属性 ) 来传达关于用户在一个房间里的状态的信息.


Android基于XMPP Smack openfire 开发的聊天室(七) 【成员状态、自身状态】

1,聊天是成员的监听,加到MUC里边就行

[java] view
plaincopy

/**

* 聊天室成员的监听器

*

* @author 廖乃波

*

*/

class MyParticipantStatusListener implements ParticipantStatusListener {

@Override

public void adminGranted(String arg0) {

Log.i(TAG, "授予管理员权限" + arg0);

}

@Override

public void adminRevoked(String arg0) {

Log.i(TAG, "移除管理员权限" + arg0);

}

@Override

public void banned(String arg0, String arg1, String arg2) {

Log.i(TAG, "禁止加入房间(拉黑,不知道怎么理解,呵呵)" + arg0);

}

@Override

public void joined(String arg0) {

Log.i(TAG, "执行了joined方法:" + arg0 + "加入了房间");

// 更新成员

getAllMember();

android.os.Message msg = new android.os.Message();

msg.what = MEMBER;

handler.sendMessage(msg);

}

@Override

public void kicked(String arg0, String arg1, String arg2) {

Log.i(TAG, "踢人" + arg0 + "被踢出房间");

}

@Override

public void left(String arg0) {

String lefter = arg0.substring(arg0.indexOf("/") + 1);

Log.i(TAG, "执行了left方法:" + lefter + "离开的房间");

// 更新成员

getAllMember();

android.os.Message msg = new android.os.Message();

msg.what = MEMBER;

handler.sendMessage(msg);

}

@Override

public void membershipGranted(String arg0) {

Log.i(TAG, "授予成员权限" + arg0);

}

@Override

public void membershipRevoked(String arg0) {

Log.i(TAG, "成员权限被移除" + arg0);

}

@Override

public void moderatorGranted(String arg0) {

Log.i(TAG, "授予主持人权限" + arg0);

}

@Override

public void moderatorRevoked(String arg0) {

Log.i(TAG, "移除主持人权限" + arg0);

}

@Override

public void nicknameChanged(String arg0, String arg1) {

Log.i(TAG, "昵称改变了" + arg0);

}

@Override

public void ownershipGranted(String arg0) {

Log.i(TAG, "授予所有者权限" + arg0);

}

@Override

public void ownershipRevoked(String arg0) {

Log.i(TAG, "移除所有者权限" + arg0);

}

@Override

public void voiceGranted(String arg0) {

Log.i(TAG, "给" + arg0+"授权发言");

}

@Override

public void voiceRevoked(String arg0) {

Log.i(TAG, "禁止" + arg0+"发言");

}

}

2,自身状态监听

[java] view
plaincopy

class MyUserStatusListener implements UserStatusListener {

/*这里实现默认方法即可*/

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐