作者:empty 出版社:empty |
在android里面用的smack包其实叫做asmack,该包提供了两种不同的连接方式:socket和httpclient。该并且提供了很多操作xmpp协议的API,也方便各种不同自定义协议的扩展。我们不需要自己重新去定义一套接收机制来扩展新的协议,只需继承然后在类里处理自己的协议就可以了。而本文今天主要说两点,一点就是消息是如何接收的,另一点就是消息是如何通知事件的。
总的思路
1.使用socket连接服务器
2.将XmlPullParser的数据源关联到socket的InputStream
3.启动线程不断循环处理消息
4.将接收到的消息解析xml处理封装好成一个Packet包
5.将包广播给所有注册事件监听的类
逐步击破
(声明在看下面的文章时,最好先理解一下smack的使用,这样才能达到深入的理解)
(谨记:上图只显示本文章解释所要用到的类和方法,减缩了一些跟本文主题无关的代码,只留一条贯穿着从建立连接到接收消息的线。)
解析这块东西打算从最初的调用开始作为入口,抽丝剥茧,逐步揭开。
1.
PacketListener packetListener = new PacketListener() {
@Override
public void processPacket(Packet packet) {
System.out
.println( Activity----processPacket + packet.toXML());
}
};
PacketFilter packetFilter = new PacketFilter() {
@Override
public booleanaccept(Packet packet) {
System.out.println( Activity----accept + packet.toXML());
return true;
}
};
解释:创建包的监听以及包的过滤,当有消息到时就会广播到所有注册的监听,当然前提是要通过packetFilter的过滤。
2.
connection = new XMPPConnection();
XMPPConnection在这构造函数里面主要配置ip地址和端口(super(new ConnectionConfiguration( 169.254.141.109 , 9991));)
3.
connection.addPacketListener(packetListener, packetFilter);
connection.connect();
注册监听,开始初始化连接。
4.
public voidconnect() {
// Stablishes the connection, readers and writers
connectUsingConfiguration(config);
}
5.
private void connectUsingConfiguration(ConnectionConfiguration config) {
String host = config.getHost();
intport = config.getPort();
try{
this.socket= newSocket(host, port);
} catch(UnknownHostException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
initConnection();
}
通过之前设置的ip和端口,建立socket对象
6.
protected void initDebugger() {
Class ?> debuggerClass = null;
try{
debuggerClass = Class.forName( com.simualteSmack.ConsoleDebugger );
Constructor ?> constructor = debuggerClass.getConstructor(
Connection.class, Writer.class, Reader.class);
debugger= (SmackDebugger) constructor.newInstance(this, writer,
reader);
reader= debugger.getReader();
} catch(ClassNotFoundException e1) {
//TODOAuto-generated catch block
e1.printStackTrace();
} catch(Exception e) {
throw newIllegalArgumentException(
Can't initialize the configured debugger! , e);
}
}
private void initReaderAndWriter() {
try{
reader = newBufferedReader(newInputStreamReader(socket
.getInputStream(), UTF-8 ));
} catch(UnsupportedEncodingException e) {
//TODOAuto-generated catch block
e.printStackTrace();
} catch(IOException e) {
//TODOAuto-generated catch block
e.printStackTrace();
}
initDebugger();
}
private void initConnection() {
// Set the reader and writer instance variables
initReaderAndWriter();
packetReader = new PacketReader(this);
addPacketListener(debugger.getReaderListener(), null);
// Start the packet reader. The startup() method will block until we
// get an opening stream packet back from server.
packetReader.startup();
}
从三个方法可以看出,建立reader和writer的对象关联到socket的InputStream,实例化ConsoleDebugger,该类主要是打印出接收到的消息,给reader设置了一个消息的监听。接着建立PacketReader对象,并启动。PacketReader主要负责消息的处理和通知
7.
public class PacketReader {
Private ExecutorService listenerExecutor;
private boolean done;
Private XMPPConnection connection;
Private XmlPullParser parser;
Private Thread readerThread;
Protected PacketReader(finalXMPPConnection connection) {
this.connection= connection;
this.init();
}
/**
* Initializes the reader in order to be used. The reader is initialized
* during the first connection and when reconnecting due to an abruptly
* disconnection.
*/
protected void init() {
done= false;
readerThread= newThread() {
public voidrun() {
parsePackets(this);
}
};
readerThread.setName( Smack Packet Reader );
readerThread.setDaemon(true);
// create an executor to deliver incoming packets to listeners.
// we will use a single thread with an unbounded queue.
listenerExecutor= Executors
.newSingleThreadExecutor(newThreadFactory() {
@Override
publicThread newThread(Runnable r) {
Thread thread = newThread(r,
smack listener processor );
thread.setDaemon(true);
returnthread;
}
});
resetParser();
}
/**
* Starts the packet reader thread and returns once a connection to the
* server has been established. A connection will be attempted for a maximum
* of five seconds. An XMPPException will be thrown if the connection fails.
*
*/
public voidstartup() {
readerThread.start();
}
/**
* Shuts the packet reader down.
*/
public voidshutdown() {
done= true;
// Shut down the listener executor.
listenerExecutor.shutdown();
}
private voidresetParser() {
try{
parser= XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(connection.reader);
} catch(XmlPullParserException xppe) {
xppe.printStackTrace();
}
}
/**
* Parse top-level packets in order to process them further.
*
*@paramthread
* the thread that is being used by the reader to parse incoming
* packets.
*/
private void parsePackets(Thread thread) {
try{
Int eventType = parser.getEventType();
do{
if(eventType == XmlPullParser.START_TAG) {
if(parser.getName().equals( message )) {
processPacket(PacketParserUtils.parseMessage(parser));
}
System.out.println( START_TAG );
} else if(eventType == XmlPullParser.END_TAG) {
System.out.println( END_TAG );
}
eventType = parser.next();
} while(!done&& eventType != XmlPullParser.END_DOCUMENT
&& thread == readerThread);
} catch(Exception e) {
e.printStackTrace();
if(!done) {
}
}
}
private void processPacket(Packet packet) {
if(packet == null) {
return;
}
// Loop through all collectors and notify the appropriate ones.
for(PacketCollector collector : connection.getPacketCollectors()) {
collector.processPacket(packet);
}
// Deliver the incoming packet to listeners.
listenerExecutor.submit(newListenerNotification(packet));
}
/**
* A runnable to notify all listeners of a packet.
*/
private class ListenerNotification implements Runnable {
Private Packet packet;
Public ListenerNotification(Packet packet) {
this.packet= packet;
}
public void run() {
for(ListenerWrapper listenerWrapper : connection.recvListeners
.values()) {
listenerWrapper.notifyListener(packet);
}
}
}
}
创建该类时就初始化线程和ExecutorService ,接着调用resetParser() 方法为parser设置输入源(这里是重点,parser的数据都是通过这里获取),调用startup启动线程,循环监听parser,如果接收到消息根据消息协议的不同将调用PacketParserUtils类里的不同方法,这里调用parseMessage()该方法主要处理message的消息,在该方法里分析message消息并返回packet包。返回的包将调用processPacket方法,先通知所有注册了
PacketCollector的监听,接着消息(listenerExecutor.submit(newListenerNotification(packet)); )传递给所有注册了PacketListener的监听。这样在activity开始之前注册的那个监听事件就会触发,从而完成了整个流程。
7以上.
剩下的就是一些辅助包,很简单。比如PacketCollector 这个类,它的用处主要用来处理一些需要在发送后需要等待一个答复这样的请求。
protected synchronized void processPacket(Packet packet) {
System.out.println( PacketCollector---processPacket );
if(packet == null) {
return;
}
if(packetFilter== null|| packetFilter.accept(packet)) {
while(!resultQueue.offer(packet)) {
resultQueue.poll();
}
}
}
Public Packet nextResult(longtimeout) {
longendTime = System.currentTimeMillis() + timeout;
System.out.println( nextResult );
do{
try{
returnresultQueue.poll(timeout, TimeUnit.MILLISECONDS);
} catch(InterruptedException e) { /* ignore */
}
} while(System.currentTimeMillis() endTime);
return null;
}
该方法就是将获取到的包,先过滤然后放到队列里,最后通过nextResult来获取包,这样就完成一个请求收一个答复。
这样整个流程就完成了,最后总结一下,如图(就这么简单^0^):
项目下载(只有客户端的,服务端的就是一个简单的socket接受,为了锻炼一下大家的编写代码的能力,服务器那个只能自己写咯^0^,其实是懒得上传了,代码很简单的)
http://files.cnblogs.com/not-code/simualteSmack.zip
android asmack 注册 登陆 聊天 多人聊天室 文件传输
XMPP协议简介
XMPP协议(Extensible Messaging and PresenceProtocol,可扩展消息处理现场协议)是一种基于XML的协议,目的是为了解决及时通信标准而提出来的,最早是在Jabber上实现的。它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。并且XML很易穿过防火墙,所以用XMPP构建的应用不易受到防火墙的阻碍。利用XMPP作为通用的传输机制,不同组织内的不同应用都可以进行有效的通信。
这篇文章有基本的介绍,http://blog.csdn.net/xutaozero21/article/details/4873439
IM
Instant Messenger,及时通信软件,就是大家使用的QQ、MSN Messenger和Gtalk等等。其中Gtalk 就是基于XMPP 协议的一个实现,其他的则不是。当前IM 几乎作为每个上网者必然使用的工具,在国外的大型企业中有一些企业级的IM应用,但是其商业价值还没完全发挥出来。设想既然XMPP 协议是一个公开的协议,那么每个企业都可以利用它来开发适合本身企业工作,提高自身生产效率的IM;甚至,你还可以在网络游戏中集成这种通信软件,不但让你可以边游戏边聊天,也可以开发出适合游戏本身的IM 应用,比如说一些游戏关键场景提醒功能,团队语音交流等等都可以基于IM来实现。
本文主要讲解在android使用xmpp协议进行即时通信,所涉及3个主要的东西,它们是openfire、smack和spark,这个三个东东结合起来就是完整的xmpp IM实现,这里简单介绍一下这3个东东在下文的作用:
openfire主要是作为服务器,负责管理客户端的通信连接,以及提供客户端一些通信信息和连接信息。
Smack主要是xmpp协议的实现,提供了一套很好的api,所以下面操作xmpp都是通过使用smack的api来实现,当然因为是在android里,所以使用的是asmack这个包,里面方法跟smack包差不多。
Spark 是IM客户端的实现,其实就是使用了smack 的api实现的。
下图展示了三者之间的关系:(很明显这个图是偷别人的,具体是哪里我忘了,因为资料都是复制到文档后慢慢研究看的)
从图上可以了解到,client 端和server端都可以通过插件的方式来进行扩展,smack是二者传递数据的媒介。
配置openfire服务器
具体步骤请移步:http://javatech.blog.163.com/blog/static/1766322992010111725339587/
配置成功如果以后ip地址变了,那肯定又是开不了,解决办法请移步:http://blog.csdn.net/HappySheepherder/article/details/4707124
配置成功后,在服务器创建一个简单的用户来测试,然后安装spark,设置好服务器的ip与端口,使用刚才创建的用户登录,登录OK说明服务器成功搭建。
Android IM功能(因为是测试demo,因此界面超级简陋,代码都是给出重要的一部分,剩余的可以在最后下面项目查看)
配置要求
android 2.2、 asmack-jse.jar、myeclipse
连接服务器
在打开软件后会开始初始化,完成与openfire服务器的连接,设置一些配置
static{
XMPPConnection.DEBUG_ENABLED=true;
finalConnectionConfiguration connectionConfig =newConnectionConfiguration(
host, 5222, );
// Google talk
// ConnectionConfiguration connectionConfig = new
// ConnectionConfiguration(
// talk.google.com , 5222, gmail.com );
// connectionConfig.setSASLAuthenticationEnabled(false);
ActivityMain.connection=newXMPPConnection(connectionConfig);
ActivityMain.connection.DEBUG_ENABLED=true;
ProviderManager pm =ProviderManager.getInstance();
configure(pm);
}
注册模块
注册有两种方法:一种是用createAccount ,不过我测试了一下发现不能创建用户,具体原因不详,下面介绍第二种。
如上图:注册成功后服务器将多了ggg用户。
具体实现如下:
Registration reg = newRegistration();
reg.setType(IQ.Type.SET);
reg.setTo(ConnectionSingleton.getInstance().getServiceName());
reg.setUsername(username.getText().toString());
reg.setPassword(password.getText().toString());
reg.addAttribute( android , geolo_createUser_android );
System.out.println( reg: + reg);
PacketFilter filter = newAndFilter(newPacketIDFilter(reg
.getPacketID()), newPacketTypeFilter(IQ.class));
PacketCollector collector = ConnectionSingleton.getInstance()
.createPacketCollector(filter);
ConnectionSingleton.getInstance().sendPacket(reg);
result= (IQ) collector.nextResult(SmackConfiguration
.getPacketReplyTimeout());
// Stop queuing results
collector.cancel(); if(result== null) {
Toast.makeText(getApplicationContext(), 服,
Toast.LENGTH_SHORT).show();
} else if(result.getType() == IQ.Type.ERROR) {
if(result.getError().toString().equalsIgnoreCase(
conflict(409) )) {
Toast.makeText(getApplicationContext(), 这,
Toast.LENGTH_SHORT).show();
} else{
Toast.makeText(getApplicationContext(), 注,
Toast.LENGTH_SHORT).show();
}
} else if(result.getType() == IQ.Type.RESULT) {
Toast.makeText(getApplicationContext(), 恭,
Toast.LENGTH_SHORT).show();
}
使用注册类,设置好注册的用户名密码和一些属性字段,直接设置包过滤,根据这个过滤创建一个结果集合,发送注册信息包,等待获取结果,剩余就是判断结果内容.
登录模块
登录比较简单
ConnectionSingleton.getInstance().connect();// connect
String account = etUsername.getText().toString();
String password = etPassword.getText().toString();
// 保存用户和密码
ActivityLogin.util.saveString(ACCOUNT_KEY, account);
ActivityLogin.util.saveString(PASSWORD_KEY, password);
ConnectionSingleton.getInstance().login(account, password);// login
// login success
System.out.println( login success );
ActivityLogin.mCurrentAccount= account;
System.out.println(ConnectionSingleton.getInstance()
.getUser());
// 登录成功后发现在线状态
Presence presence = newPresence(Presence.Type.available);
ConnectionSingleton.getInstance().sendPacket(presence);
// 开始主界面
Intent intent = newIntent(ActivityLogin.this,
ActivityMain.class);
startActivity(intent);
获取联系人模块(ActivityMain 主界面)
获取联系人并将相关信息保存到一个list数组里,最后通知listview更新界面
roster= ActivityMain.connection.getRoster();
public voidupdateRoster() {
Collection RosterEntry> entries = roster.getEntries();
for(RosterEntry entry : entries) {
System.out.print(entry.getName() + - + entry.getUser() + -
+ entry.getType() + - + entry.getGroups().size());
Presence presence = roster.getPresence(entry.getUser());
System.out.println( - + presence.getStatus() + -
+ presence.getFrom());
User user = newUser();
user.setName(entry.getName());
user.setUser(entry.getUser());
user.setType(entry.getType());
user.setSize(entry.getGroups().size());
user.setStatus(presence.getStatus());
user.setFrom(presence.getFrom());
userinfos.add(user);
}
rosterAdapter.notifyDataSetChanged();
}
单人聊天模块
第一次修改:
在主界面点击选择一个用户,进入聊天Activity,ActivityChat先获取传过来的用户,创建聊天类并对该用户设置消息监听
ChatManager chatmanager = ConnectionSingleton.getInstance()
.getChatManager();
// get user
Intent intent = getIntent();
String user = intent.getStringExtra( user );
System.out.println( user: + user);
// new a session
newChat= chatmanager.createChat(user, null);
// 监听聊天消息
chatmanager.addChatListener(newChatManagerListenerEx());
// send message
try{
newChat.sendMessage( im bird man );
} catch(XMPPException e) {
//TODOAuto-generated catch block
e.printStackTrace();
}
监听类
public classChatManagerListenerEx implementsChatManagerListener {
@Override
public voidchatCreated(Chat chat, booleanarg1) {
//TODOAuto-generated method stub
chat.addMessageListener(ml);
}
}
public classMessageListenerEx implementsMessageListener {
@Override
public voidprocessMessage(Chat arg0, Message message) {
String result = message.getFrom() + : + message.getBody();
System.out.println(result);
android.os.Message msg = newandroid.os.Message();
msg.what= 0;
Bundle bd = newBundle();
bd.putString( msg , result);
msg.setData(bd);
handler.sendMessage(msg);
}
}
所获取到的消息都是通过handler来更新UI
publicHandler handler= newHandler() {
@Override
public voidhandleMessage(android.os.Message msg) {
switch(msg.what) {
case0: {
String result = msg.getData().getString( msg );
record.setText(record.getText() + n + result);
}
break;
default:
break;
}
}
};
aaa跟bbb 的聊天
第二次修改:
第一次的测试,发现如果多个人之间都成为好友,那么他们之间的聊天就出现了接收不到的信息,当然在跟spark测试时,spark可以收到android端的信息,不过android客户端是收到这个信息,不过却没有显示出来,具体原因还不太清楚。因此在第二次修改我改成监听所有聊天信息包,然后再分析包的归属,分发到对应的聊天窗口。
这里就是监听到包后打印的消息,打印出了jid和消息内容
public classXmppMessageManager implementsChatManagerListener {
privateXMPPConnection _connection;
privateChatManager manager = null;
public voidinitialize(XMPPConnection connection) {
_connection = connection;
manager = _connection.getChatManager();
manager.addChatListener(this);
}
@Override
public voidchatCreated(Chat chat, booleanarg1) {
//TODOAuto-generated method stub
chat.addMessageListener(newMessageListener() {
public voidprocessMessage(Chat newchat, Message message) {
// 若是聊天窗口存在,将消息转往目前窗口
// 若窗口不存在,创建新的窗口
System.out
.println(message.getFrom() + : + message.getBody());
if(!ActivityMain.chats.containsKey(message.getFrom())) {
ActivityMain.chats.put(message.getFrom(), newchat);
} else{
}
}
});
}
}
主要就是重写了ChatManagerListener类的监听,分发处理暂时没有想好怎么写。接着在后台启动service就可以开始监听,行了第一次修改那些可以去掉了^0^。
多人聊天模块
也是在主界面的菜单进入ActivityMultiChat,该界面显示所创建的房间,点击就跳转到ActivityMultiRoom 。
获取所有房间比较简单,只需执行下面这段代码
hostrooms = MultiUserChat.getHostedRooms(ActivityMain.connection,
conference.zhanghaitao-pc );
跳转到后获取要加入的房间的jid,并创建监听。
jid = getIntent().getStringExtra( jid );
//后面服务名称必需是创建房间的那个服务
String multiUserRoom = jid;
try{
muc = newMultiUserChat(ActivityMain.connection, multiUserRoom);
// 创建聊天室,进入房间后的nickname
muc.join(ActivityLogin.mCurrentAccount);
Log.v(TAG, join success );
} catch(XMPPException e) {
//TODOAuto-generated catch block
e.printStackTrace();
}
ChatPacketListener chatListener= newChatPacketListener(muc);
muc.addMessageListener(chatListener);
监听大概的流程跟单人聊天差不多,都是handler来操作。不过多人聊天是重写了PacketListener。具体如下(不过该方法是监听房间的信息,也就是说显示的是以房间为名字的消息):
classChatPacketListener implementsPacketListener {
privateString _number;
privateDate _lastDate;
privateMultiUserChat _muc;
privateString _roomName;
publicChatPacketListener(MultiUserChat muc) {
_number= 0 ;
_lastDate= newDate(0);
_muc= muc;
_roomName= muc.getRoom();
}
@Override
public voidprocessPacket(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 = newDate();
}
Log.w(TAG, Receive old message: date=
+ sentDate.toLocaleString() + ; message=
+ message.getBody());
android.os.Message msg = newandroid.os.Message();
msg.what= RECEIVE;
Bundle bd = newBundle();
bd.putString( from , from);
bd.putString( body , message.getBody());
msg.setData(bd);
handler.sendMessage(msg);
}
}
}
下载模块
在主界面对着用户名长按,进入下载activity。进入activityFileTransfer,点击传输按钮即可将文件传输给之前选择的用户,当然这里做得比较简单,并没有拒绝功能,一旦发现有文件就接受。
FileTransferManager transfer =newFileTransferManager(
ActivityMain.connection);
String destination =user;
OutgoingFileTransfer out = transfer
.createOutgoingFileTransfer(destination + /Smack );
那用户是如何监听到有文件并且接受呢?在进入主界面的时候就已经开始了一个service(fileListenerService),该服务创建文件的监听类(XmppFileManager),监听类主要继承FileTransferListener 重写里面的fileTransferRequest方法。
File saveTo;
// set answerTo for replies and send()
answerTo= request.getRequestor();
if(!Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())) {
send( External Media not mounted read/write );
return;
} else if(!landingDir.isDirectory()) {
send( The directory + landingDir.getAbsolutePath()
+ is not a directory );
return;
}
saveTo = newFile(landingDir, request.getFileName());
if(saveTo.exists()) {
send( The file + saveTo.getAbsolutePath() + already exists );
// delete
saveTo.delete();
// return;
}
IncomingFileTransfer transfer = request.accept();
send( File transfer: + saveTo.getName() + -
+ request.getFileSize() / 1024 + KB );
try{
transfer.recieveFile(saveTo);
send( File transfer: + saveTo.getName() + -
+ transfer.getStatus());
doublepercents = 0.0;
while(!transfer.isDone()) {
if(transfer.getStatus().equals(Status.in_progress)) {
percents = ((int) (transfer.getProgress() * 10000)) / 100.0;
send( File transfer: + saveTo.getName() + -
+ percents + % );
} else if(transfer.getStatus().equals(Status.error)) {
send(returnAndLogError(transfer));
return;
}
Thread.sleep(1000);
}
if(transfer.getStatus().equals(Status.complete)) {
send( File transfer complete. File saved as
+ saveTo.getAbsolutePath());
} else{
send(returnAndLogError(transfer));
}
} catch(Exception ex) {
String message = Cannot receive the file because an error occured during the process.
+ ex;
Log.e(TAG, message, ex);
send(message);
}
网上说文件的传输遇到几个比较重要的问题,我也查看了很多资料(国内的基本是寥寥无几,对此我感到挺无奈的,只能看国外,这样每次我的英语水平都提高了太无奈了^0^)。在这个android asmack的最新版本好像是解决了几个比较重要的问题,剩下一个传输文件没反应的问题我在初始化连接时调用了configure方法解决。不过不知道是不是这个原因,后面出现了一个神奇的问题,就是有时可以成功传输有时传输时客户端没响应(如果有人完美解决了这个问题,那就………赶紧将代码放出来一起共享^-^,以提高我国程序员的整体水平,多伟大遥远的理想啊~~)。
项目下载
http://files.cnblogs.com/not-code/ASmack.zip
XMPP协议简介
XMPP协议(Extensible Messaging and PresenceProtocol,可扩展消息处理现场协议)是一种基于XML的协议,目的是为了解决及时通信标准而提出来的,最早是在Jabber上实现的。它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。并且XML很易穿过防火墙,所以用XMPP构建的应用不易受到防火墙的阻碍。利用XMPP作为通用的传输机制,不同组织内的不同应用都可以进行有效的通信。
这篇文章有基本的介绍,http://blog.csdn.net/xutaozero21/article/details/4873439
IM
Instant Messenger,及时通信软件,就是大家使用的QQ、MSN Messenger和Gtalk等等。其中Gtalk 就是基于XMPP 协议的一个实现,其他的则不是。当前IM 几乎作为每个上网者必然使用的工具,在国外的大型企业中有一些企业级的IM应用,但是其商业价值还没完全发挥出来。设想既然XMPP 协议是一个公开的协议,那么每个企业都可以利用它来开发适合本身企业工作,提高自身生产效率的IM;甚至,你还可以在网络游戏中集成这种通信软件,不但让你可以边游戏边聊天,也可以开发出适合游戏本身的IM 应用,比如说一些游戏关键场景提醒功能,团队语音交流等等都可以基于IM来实现。
本文主要讲解在android使用xmpp协议进行即时通信,所涉及3个主要的东西,它们是openfire、smack和spark,这个三个东东结合起来就是完整的xmpp IM实现,这里简单介绍一下这3个东东在下文的作用:
openfire主要是作为服务器,负责管理客户端的通信连接,以及提供客户端一些通信信息和连接信息。
Smack主要是xmpp协议的实现,提供了一套很好的api,所以下面操作xmpp都是通过使用smack的api来实现,当然因为是在android里,所以使用的是asmack这个包,里面方法跟smack包差不多。
Spark 是IM客户端的实现,其实就是使用了smack 的api实现的。
下图展示了三者之间的关系:(很明显这个图是偷别人的,具体是哪里我忘了,因为资料都是复制到文档后慢慢研究看的)
从图上可以了解到,client 端和server端都可以通过插件的方式来进行扩展,smack是二者传递数据的媒介。
配置openfire服务器
具体步骤请移步:http://javatech.blog.163.com/blog/static/1766322992010111725339587/
配置成功如果以后ip地址变了,那肯定又是开不了,解决办法请移步:http://blog.csdn.net/HappySheepherder/article/details/4707124
配置成功后,在服务器创建一个简单的用户来测试,然后安装spark,设置好服务器的ip与端口,使用刚才创建的用户登录,登录OK说明服务器成功搭建。
Android IM功能(因为是测试demo,因此界面超级简陋,代码都是给出重要的一部分,剩余的可以在最后下面项目查看)
配置要求
android 2.2、 asmack-jse.jar、myeclipse
连接服务器
在打开软件后会开始初始化,完成与openfire服务器的连接,设置一些配置
static{
XMPPConnection.DEBUG_ENABLED=true;
finalConnectionConfiguration connectionConfig =newConnectionConfiguration(
host, 5222, );
// Google talk
// ConnectionConfiguration connectionConfig = new
// ConnectionConfiguration(
// talk.google.com , 5222, gmail.com );
// connectionConfig.setSASLAuthenticationEnabled(false);
ActivityMain.connection=newXMPPConnection(connectionConfig);
ActivityMain.connection.DEBUG_ENABLED=true;
ProviderManager pm =ProviderManager.getInstance();
configure(pm);
}
注册模块
注册有两种方法:一种是用createAccount ,不过我测试了一下发现不能创建用户,具体原因不详,下面介绍第二种。
如上图:注册成功后服务器将多了ggg用户。
具体实现如下:
Registration reg = newRegistration();
reg.setType(IQ.Type.SET);
reg.setTo(ConnectionSingleton.getInstance().getServiceName());
reg.setUsername(username.getText().toString());
reg.setPassword(password.getText().toString());
reg.addAttribute( android , geolo_createUser_android );
System.out.println( reg: + reg);
PacketFilter filter = newAndFilter(newPacketIDFilter(reg
.getPacketID()), newPacketTypeFilter(IQ.class));
PacketCollector collector = ConnectionSingleton.getInstance()
.createPacketCollector(filter);
ConnectionSingleton.getInstance().sendPacket(reg);
result= (IQ) collector.nextResult(SmackConfiguration
.getPacketReplyTimeout());
// Stop queuing results
collector.cancel(); if(result== null) {
Toast.makeText(getApplicationContext(), 服,
Toast.LENGTH_SHORT).show();
} else if(result.getType() == IQ.Type.ERROR) {
if(result.getError().toString().equalsIgnoreCase(
conflict(409) )) {
Toast.makeText(getApplicationContext(), 这,
Toast.LENGTH_SHORT).show();
} else{
Toast.makeText(getApplicationContext(), 注,
Toast.LENGTH_SHORT).show();
}
} else if(result.getType() == IQ.Type.RESULT) {
Toast.makeText(getApplicationContext(), 恭,
Toast.LENGTH_SHORT).show();
}
使用注册类,设置好注册的用户名密码和一些属性字段,直接设置包过滤,根据这个过滤创建一个结果集合,发送注册信息包,等待获取结果,剩余就是判断结果内容.
登录模块
登录比较简单
ConnectionSingleton.getInstance().connect();// connect
String account = etUsername.getText().toString();
String password = etPassword.getText().toString();
// 保存用户和密码
ActivityLogin.util.saveString(ACCOUNT_KEY, account);
ActivityLogin.util.saveString(PASSWORD_KEY, password);
ConnectionSingleton.getInstance().login(account, password);// login
// login success
System.out.println( login success );
ActivityLogin.mCurrentAccount= account;
System.out.println(ConnectionSingleton.getInstance()
.getUser());
// 登录成功后发现在线状态
Presence presence = newPresence(Presence.Type.available);
ConnectionSingleton.getInstance().sendPacket(presence);
// 开始主界面
Intent intent = newIntent(ActivityLogin.this,
ActivityMain.class);
startActivity(intent);
获取联系人模块(ActivityMain 主界面)
获取联系人并将相关信息保存到一个list数组里,最后通知listview更新界面
roster= ActivityMain.connection.getRoster();
public voidupdateRoster() {
Collection RosterEntry> entries = roster.getEntries();
for(RosterEntry entry : entries) {
System.out.print(entry.getName() + - + entry.getUser() + -
+ entry.getType() + - + entry.getGroups().size());
Presence presence = roster.getPresence(entry.getUser());
System.out.println( - + presence.getStatus() + -
+ presence.getFrom());
User user = newUser();
user.setName(entry.getName());
user.setUser(entry.getUser());
user.setType(entry.getType());
user.setSize(entry.getGroups().size());
user.setStatus(presence.getStatus());
user.setFrom(presence.getFrom());
userinfos.add(user);
}
rosterAdapter.notifyDataSetChanged();
}
单人聊天模块
第一次修改:
在主界面点击选择一个用户,进入聊天Activity,ActivityChat先获取传过来的用户,创建聊天类并对该用户设置消息监听
ChatManager chatmanager = ConnectionSingleton.getInstance()
.getChatManager();
// get user
Intent intent = getIntent();
String user = intent.getStringExtra( user );
System.out.println( user: + user);
// new a session
newChat= chatmanager.createChat(user, null);
// 监听聊天消息
chatmanager.addChatListener(newChatManagerListenerEx());
// send message
try{
newChat.sendMessage( im bird man );
} catch(XMPPException e) {
//TODOAuto-generated catch block
e.printStackTrace();
}
监听类
public classChatManagerListenerEx implementsChatManagerListener {
@Override
public voidchatCreated(Chat chat, booleanarg1) {
//TODOAuto-generated method stub
chat.addMessageListener(ml);
}
}
public classMessageListenerEx implementsMessageListener {
@Override
public voidprocessMessage(Chat arg0, Message message) {
String result = message.getFrom() + : + message.getBody();
System.out.println(result);
android.os.Message msg = newandroid.os.Message();
msg.what= 0;
Bundle bd = newBundle();
bd.putString( msg , result);
msg.setData(bd);
handler.sendMessage(msg);
}
}
所获取到的消息都是通过handler来更新UI
publicHandler handler= newHandler() {
@Override
public voidhandleMessage(android.os.Message msg) {
switch(msg.what) {
case0: {
String result = msg.getData().getString( msg );
record.setText(record.getText() + n + result);
}
break;
default:
break;
}
}
};
aaa跟bbb 的聊天
第二次修改:
第一次的测试,发现如果多个人之间都成为好友,那么他们之间的聊天就出现了接收不到的信息,当然在跟spark测试时,spark可以收到android端的信息,不过android客户端是收到这个信息,不过却没有显示出来,具体原因还不太清楚。因此在第二次修改我改成监听所有聊天信息包,然后再分析包的归属,分发到对应的聊天窗口。
这里就是监听到包后打印的消息,打印出了jid和消息内容
public classXmppMessageManager implementsChatManagerListener {
privateXMPPConnection _connection;
privateChatManager manager = null;
public voidinitialize(XMPPConnection connection) {
_connection = connection;
manager = _connection.getChatManager();
manager.addChatListener(this);
}
@Override
public voidchatCreated(Chat chat, booleanarg1) {
//TODOAuto-generated method stub
chat.addMessageListener(newMessageListener() {
public voidprocessMessage(