Android使用XMPP协议、Openfire服务器和Smack类库实现即时通信

效果

Android使用XMPP协议、Openfire服务器和Smack类库实现即时通信_第1张图片   Android使用XMPP协议、Openfire服务器和Smack类库实现即时通信_第2张图片

介绍

XMPP(Extensible Messaging and Presence Protocal,可扩展通讯和表示协议)是一种基于XML的网络即时通信协议,它继承了在XML环境中灵活的发展性,因此,基于XMPP的应用具有超强的可扩展性。
XMPP的核心XML流传输协议的定义使得XMPP能够在一个比以往网络通信协议更规范的平台上,借助于XML易于解析和阅读的特性,使得XMPP协议能够更加漂亮。
XMPP中定义了三个角色:客户端,服务器和网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构通信系统的互联互通。基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML流。
简单来讲,它就是一个发送、接收、处理消息的协议,并使用XML作为消息传递的媒介。


Openfire基于XMPP协议,采用Java开发,可用于构建高效的即时通信服务器端,单台服务器可支持上万并发用户。由于是采用开放的XMPP协议,因此可以使用各种支持XMPP协议的IM客户端软件登录服务。

Smack是一个开源的、易于使用的XMPP客户端Java类库,提供了一套可扩展的API。
值得一提的是,Smack类库有一个基于Android平台的移植版本aSmack,曾经一度被广泛应用而今网上找的大多数例子也是基于aSmack进行开发的。但Smack类库在4.1.0版之后已经直接支持Android系统了,相应的,aSmack就不再进行更新和维护了,因此建议开发者们及早改用Smack类库。


实现
1.Openfire服务器的安装和配置
可参考:http://www.cnblogs.com/hoojo/archive/2012/05/17/2506769.html
需要注意的是,在第7项数据库设置里我选择的是标准数据库连接,对应使用的是我电脑上安装的MySQL数据库,如果你们电脑上没有安装MySQL,推荐使用一款叫phpStudy的一键集成PHP开发环境的软件,其中就包含了MySQL数据库。一键简单安装,无需任何配置。

在数据库驱动选项选择MySQL之后,第2、3个选项会自动帮你填写,我们还需要修改【数据库URL】选项中[host-name]和[database-name],分别是主机名和数据库名(记得修改时把[]去掉)。如果是在本机上调试,则主机名直接写localhost或127.0.0.1即可,数据库名需要在MySQL新建一个数据库后填上。然后,用户名和密码选项指的是MySQL数据库的登录用户和密码,默认登录名和密码都为root。

Android使用XMPP协议、Openfire服务器和Smack类库实现即时通信_第3张图片

2.Smack类库的下载和引用
下载地址:http://download.csdn.net/detail/alfred_c/9297103

3.首先需要初始化与OpenFire服务器的连接配置,设置域名,主机号和端口号

	private static void init(Context context) {
		// TODO 自动生成的方法存根
		mContext = context;
		try {
			XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
			builder.setServiceName(SERVER_NAME);
			builder.setHost(SERVER_HOST);
			builder.setPort(SERVER_PORT);
			builder.setConnectTimeout(5000);// 设置连接OpenFire服务器超时时间
			builder.setDebuggerEnabled(false);// 设置是否启用调试模式,默认开启
			TLSUtils.acceptAllCertificates(builder);// 防止安全证书验证不通过的情况java.security.cert.CertificateException

			connection = new XMPPTCPConnection(builder.build());
		} catch (KeyManagementException e) {
			// TODO 自动生成的 catch 块
			Log.e("init", Log.getStackTraceString(e));
		} catch (NoSuchAlgorithmException e) {
			// TODO 自动生成的 catch 块
			Log.e("init", Log.getStackTraceString(e));
		}

	}
域名是你配置OpenFire时指定的服务器名称

主机号即IP地址

端口号为客户端连接到服务器的标准端口,一般固定为5222

Android使用XMPP协议、Openfire服务器和Smack类库实现即时通信_第4张图片


4.一行代码完成新用户的注册。但因为可能出现诸如连接服务器超时,账号已存在等特殊情况,需要对异常进行捕获。由于涉及网络操作,所以需要另开线程进行,操作完成后再执行方法回调。

	public void register(final String account, final String password, final OnStateListener listener) {
		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					if (!connection.isConnected())
						connection.connect();
					AccountManager.getInstance(connection).createAccount(account, password);
					listener.onSuccess();
				} catch (NoResponseException e) {
					Log.e("register", Log.getStackTraceString(e));
				} catch (XMPPErrorException e) {
					if (e.toString().contains("conflict"))
						listener.onFail(mContext.getString(R.string.account_exist));
					Log.e("register", Log.getStackTraceString(e));
				} catch (NotConnectedException e) {
					Log.e("register", Log.getStackTraceString(e));
				} catch (SmackException e) {
					if (e.toString().contains("after 5000ms"))
						listener.onFail(mContext.getString(R.string.connect_timeout));
					Log.e("register", Log.getStackTraceString(e));
				} catch (IOException e) {
					Log.e("register", Log.getStackTraceString(e));
				} catch (XMPPException e) {
					Log.e("register", Log.getStackTraceString(e));
				}
			}
		}).start();
	}


5.登录操作,需要先判断用户是否已登录。

	public void login(final String account, final String password, final OnStateListener listener) {
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO 自动生成的方法存根
				try {
					if (!connection.isConnected())
						connection.connect();
					
					if (connection.isAuthenticated()) {// 返回true则表示用户已登录成功,且与服务器保持连接
						listener.onFail(mContext.getString(R.string.logout_first));
						return;
					}
					
					connection.login(account, password);
					listener.onSuccess();

					if (chatManagerListener == null)
						chatManagerListener = new MyChatManagerListener();
					ChatManager.getInstanceFor(connection).addChatListener(chatManagerListener);

				} catch (SmackException e) {
					Log.e("login", Log.getStackTraceString(e));
				} catch (IOException e) {
					Log.e("login", Log.getStackTraceString(e));
				} catch (XMPPException e) {
					if (e.toString().contains("not-authorized")) {
						listener.onFail(mContext.getString(R.string.wrong_account_or_password));
						connection.disconnect();// 失败后还会保持原来的连接,必须先断开
					}
				}

			}

		}).start();
	}


6.通过关键字搜索用户。

	public List searchFriend(String key) {
		List friends = new ArrayList();
		try {
			UserSearchManager usManager = new UserSearchManager(connection);
			Form searchForm = usManager.getSearchForm("search." + connection.getServiceName());// 在最新版本的Openfire
																								// 3.10.3中调用时总是会报remote-server-not-found,如果出现,请降低OpenFire版本使用
			Form answerForm = searchForm.createAnswerForm();
			answerForm.setAnswer("Username", true);
			answerForm.setAnswer("search", key);
			ReportedData data = usManager.getSearchResults(answerForm, "search." + connection.getServiceName());

			List rows = data.getRows();
			for (int i = 0; i < rows.size(); i++) {
				Row row = rows.get(i);
				FriendBean friend = new FriendBean();
				friend.setUserName(row.getValues("Username").get(0));
				friends.add(friend);
			}
		} catch (NoResponseException e) {
			Log.e("searchFriend", Log.getStackTraceString(e));
		} catch (XMPPErrorException e) {
			Log.e("searchFriend", Log.getStackTraceString(e));
		} catch (NotConnectedException e) {
			Log.e("searchFriend", Log.getStackTraceString(e));
		}
		return friends;
	}


7.下面是实现点对点即时通信最关键的代码了。

a.要实现发送消息,需要通过userJID构建一个会话,userJID的组成是“用户名”+“@”+“域名”:

		String userJID = targetName + "@" + connection.getServiceName();
		Chat chat = ChatManager.getInstanceFor(connection).createChat(userJID);
   然后调用chat.sendMessage(text))即可发送文本消息了,我这里是构建了一个消息实体,再转换为Json字符串进行发送:

		Gson gson = new Gson();
		String msgJson = gson.toJson(msg);

		try {
			chat.sendMessage(msgJson);
			listener.onSuccess();
		} catch (NotConnectedException e) {
			Log.e("sendMsg", Log.getStackTraceString(e));
		}

b.而要实现接受消息,则需要继承ChatManagerListener并重写chatCreated方法:

public class MyChatManagerListener implements ChatManagerListener{

	@Override
	public void chatCreated(Chat chat, boolean arg1) {
		// TODO 自动生成的方法存根
		chat.addMessageListener(new ChatMessageListener(){

			@Override
			public void processMessage(Chat chat, Message msg) {
				// TODO 自动生成的方法存根
				String body = msg.getBody();//取得消息主体
				try {
					JSONObject jsonObj = new JSONObject(body);
					String from = jsonObj.getString("from");
					String to = jsonObj.getString("to");
					String content = jsonObj.getString("content");
					
					MsgBean newMsg = new MsgBean();
					newMsg.setFrom(from);
					newMsg.setTo(to);
					newMsg.setContent(content);
					
					EventBus.getDefault().post(new NewMsgEvent(newMsg));//发布事件,通知聊天界面进行消息更新
				} catch (JSONException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
			
		});
	}

}
    取得消息主体之后重新解析成Json,然后取得发送人、接收人和消息内容并重新构建一个消息实体,传递到聊天界面进行更新。


源码

http://download.csdn.net/detail/alfred_c/9297311

参考

http://my.oschina.net/cuitongliang/blog/194885?fromerr=GblfTBic

你可能感兴趣的:(Android)