公司刚好让做即时通讯模块,服务器使用openfire,偶然看到有位仁兄的帖子,拷贝过来细细研究,感谢此仁兄的无私,期待此仁兄的下次更新
转自http://blog.csdn.net/lnb333666/article/details/7476177
Android基于XMPP Smack openfire 开发的聊天室(一)【会议服务、聊天室列表、加入】
废话少说,公司没事干,组长叫我写Demo,我狂晕....
先把先说说这个什么聊天室吧,服务器就别说了,典型的拿来主义。用的是成品的openfire。说道smack,这个是一个jar文件,库,是用在javae的。要想在android端使用这个,就要用到asmack包,这个可以在官网下。其实里边跟smack包没什么区别,具体差异目前我没发现。
可能有的朋友说这个smack是什么用的,我个人一句话,一个已经包装了XMPP协议的架包。提供了API的操作...好了进入正题
1,首先想进入聊天室,那必须要先知道聊天室所在的会议服务(下图,用spark截图)
-
-
-
- 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后才能进入会议列表:
-
-
-
- public void init() {
- listDiscoverItems = new ArrayList<DiscoverItems.Item>();
-
- ServiceDiscoveryManager discoManager = ServiceDiscoveryManager
- .getInstanceFor(Constants.conn);
-
-
-
- DiscoverItems discoItems;
- try {
- discoItems = discoManager.discoverItems(jid);
-
- Iterator it = discoItems.getItems();
-
- 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,才能进入
-
- jid = getIntent().getStringExtra("jid");
- try {
- muc = new MultiUserChat(Constants.conn, jid);
-
- 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中文翻译组去看看,挺犀利的。
-
-
-
-
-
-
- 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,下边就是成员了,一个聊天室没成员多离谱的事情啊,我们主要做的是把成员列表逃出来。其实有几种方法可以弄出来的,我只是简单的获取成员的昵称。可能有朋友会问为什么不获取成员的信息呢。这个下一篇我会告诉你。
-
-
-
- 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吧
上边这段是一个聊天室返回的XML数据,是历史消息。就在这里要告诉大家的是,如果是新消息的话就没有<delay>节点。所以我们以这个为根据来判断。下边贴些我的代码,因为服务器被我们给改了,所以返回的有些出入。但八九不离十。原理一样。
- 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("这是旧的消息");
- }
这段代码写哪里呢,就是写在消息的监听里头
-
-
-
-
-
-
-
- class ChatPacketListener implements PacketListener
重写它的方法
- @Override
- public void processPacket(Packet packet)
好了,下边讲踢人吧
2,踢人:
踢人很简单,只要你是房主,或管理员什么的,主持人也行。这些高权限的才能有踢人的权限。
-
- 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,创建房间:
其实创建房间很简单,两句话就搞定了。但你要知道,简单的同时,服务器可没帮你配置房间的信息什么的。所以一般用下边的方法来创建房间别人是进不去的。
-
- MultiUserChat muc = new MultiUserChat(conn1, "[email protected]");
-
-
- muc.create("testbot");
-
-
-
- muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
所以咱们还是手动配置一些信息吧,免得留后患。不过在这里要给朋友们提个醒,我觉得不论是哪种方法都很狗屎,为什么狗屎。你看看那MUC,本来就是一个聊天室了,还要让你在聊天室创建另个聊天室,老妈生孩子,然后孩子和老妈并排。狗屎啊,不过不排除我理解错误。希望高人能给与指点。
- 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());
- }
- }
-
-
-
-
-
-
- submitForm.setAnswer("muc#roomconfig_persistentroom", true);
-
- submitForm.setAnswer("muc#roomconfig_membersonly", false);
-
- submitForm.setAnswer("muc#roomconfig_allowinvites", true);
-
-
-
- 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服务器的话,你下线了就直接把你去除。所以我们要给服务器发送个广播。具体处理那是以后的是了。看看这段报文:
- <iq id="902U0-48" type="set">
- <query xmlns="jabber:iq:private">
- <storage xmlns="storage:bookmarks">
- <conference name="ccc" autojoin="true" jid="[email protected]" ></conference>
- </storage>
- </query>
- </iq>
报文可以根据需要来改服务器的插件。
下边发送的报文:
-
-
-
-
- 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);
-
- iq.setFrom(Constants.USERNAME+"@naibo.liao.com/Smack");
- return iq;
- }
怎么发送呢?可以通过连接,就是XMPPCONNECTION来发送:
- Constants.conn.sendPacket(leaveXml());
OK,停一会,手酸了
Android基于XMPP Smack openfire 开发的聊天室(五) 【邀请、被邀请】
1,邀请:
很简单的一句话,邀请的时候要传入被邀请者的JID,邀请信息可以为空。
- muc.invite(userjid, "进来我们聊妹子");
简单的同时我们要知道它发生了什么,当这句话执行会向服务器发送一段报文,如下:
2,被邀请
被邀请呢,我们可以开个监听吧,要不要进房间先不判断了,我这里如果有邀请就直接进房间。有需要的朋友可以自己琢磨:
- MultiUserChat.addInvitationListener(Constants.conn,
- new InvitationListener() {
-
- @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,加入房间错误,通常一个就是密码错误。此时服务器会返回以下报文:
- <presence
- from='[email protected]'
- to='[email protected]/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/> 错误
解决如下:
-
- MultiUserChat muc2 = new MultiUserChat(conn1,
- "[email protected]/thirdwitch");
-
-
-
- muc2.join("testbot2", "password");
报文如下:
2,权限错误:
这个没文本,当然如果你加入一个房间后,要注意的是,如果第一次加入别人的房间,没有被授予成员权限的时候,你仅仅是一个游客。除了说话。也不能检索聊天室成员的信息,仅可以获取成员的昵称,除此别无其他。通常不注意会用这样贸然去获取成员信息是,会照成错误,返回402(进入房间后获取超越权限错误:权限不足)等信息。以下是其他错误信息
401 |
Error |
Presence |
进入一个房间 |
通知用户需要密码 |
403 |
Error |
Presence |
进入一个房间 |
通知用户他或她被房间禁止了 |
404 |
Error |
Presence |
进入一个房间 |
通知用户房间不存在 |
405 |
Error |
Presence |
进入一个房间 |
通知用户限制创建房间 |
406 |
Error |
Presence |
进入一个房间 |
通知用户必须使用保留的房间昵称 |
407 |
Error |
Presence |
进入一个房间 |
通知用户他或她不在成员列表中 |
409 |
Error |
Presence |
进入一个房间 |
通知用户他或她的房间昵称正在使用或被别的用户注册了 |
503 |
Error |
Presence |
进入一个房间 |
通知用户已经达到最大用户数 |
用户聊天的使用一个 <status/> 元素(特指, <status/> 元素的的 'code' 属性 ) 来传达关于用户在一个房间里的状态的信息.
Android基于XMPP Smack openfire 开发的聊天室(七) 【成员状态、自身状态】
1,聊天是成员的监听,加到MUC里边就行
-
-
-
-
-
-
- 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,自身状态监听
- class MyUserStatusListener implements UserStatusListener {
-
- }
都一样,看不懂可以有道翻译,累了,睡觉先。