Android开发之基于MINA框架的聊天通信功能实现

博主近端时间在做一个项目,里面有个需求需要实现点对点可选择聊天,就像一个QQ一样。但是需求又没那么高大尚。似乎就只是一个简单的聊天。网上找了很多信息,最后决定使用MINA框架来实现。现在的IM通讯协议有一个叫XMPP的,博主一看,好像项目需求不需要那么复杂,于是博主根据实际需要自己定了一个简单的传输协议来实现。项目之前用的是openfire,但是聊天只是一个其中一个基础功能,还有其他需求,而且openfire有各种限制,不支持JDK1.7等。于是只能放弃使用它,自己来写一个通信服务器了。博主是做Android开发的,服务器端理应该是做服务器的人负责,无奈公司人手不够,项目又催的紧,服务器端还有很多其他的业务需要那个专业的人去做,于是,博主只好硬着头皮上了。查看了一下好像OPENFIRE好像就是在MINA的框架上基于XMPP协议写出来的,于是博主果断就去研究了MINA框架,搞了那么2天,还真被博主搞出来了。好了,废话不说了,言归正传。


MINA框架,是Apache组织应用程序(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。在这不在详细描述MINA框架的底层工作原理,直接进入实例开发,如果想看MINA框架的底层实现原理,请转接(博主就是看这篇文章学的):http://blog.csdn.net/w13770269691/article/details/8614584

一、MINA框架服务端开发

博主的项目的服务端是一个web项目,还有的业务,在这里博主将MINA框架部分剥离了出来,重新新建了一个项目。

1、MINA服务器编写:

package com.freedom.mina;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

/**
 * @ClassName: Server
 * @author victor_freedom ([email protected])
 * @createddate Dec 14, 2014 11:25:40 PM
 * @Description: TODO
 */
public class Server implements Runnable {
	private final Logger logger = Logger.getLogger(Server.class);
	public Thread start;

	public Server() {
		start = new Thread(this);
	}

	public void startService() {
		System.out.println("MINA启动");
		start.start();
	}
	
	public void stopService(){
		System.out.println("MINA停止");
		start.interrupt();
		
	}

	public void run() {
		IoAcceptor acceptor = new NioSocketAcceptor();

		acceptor.getFilterChain().addLast(
				"codec",
				new ProtocolCodecFilter(new TextLineCodecFactory(Charset
						.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(),
						LineDelimiter.WINDOWS.getValue())));
		acceptor.setHandler(new ServerHandler());
		acceptor.getSessionConfig().setReadBufferSize(1024);
		acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);

		try {
			acceptor.bind(new InetSocketAddress(9090));
		} catch (IOException e) {
			logger
					.error("Server start failed, Please check the network or port!");
		}
		logger.info("Server start up!");
	}
}

2、MINA服务消息Handler编写(这里需要大家注意看,在android配合MINA框架使用的时候,在中文消息的处理上,需要特别的注意,详情请注意看代码注释)

package com.freedom.mina;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Date;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;

/**
 * @ClassName: ServerHandler
 * @author victor_freedom ([email protected])
 * @createddate Dec 15, 2014 12:34:44 AM
 * @Description:
 */
public class ServerHandler extends IoHandlerAdapter {

	/**
	 * 用于存放用户信息的集合
	 */
	private final UserSessionMap userSessionMap = UserSessionMap.getInstance();

	@Override
	public void messageSent(IoSession session, Object message) throws Exception {
		super.messageSent(session, message);
	}

	@Override
	public void sessionOpened(IoSession session) throws Exception {
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
	}

	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		super.exceptionCaught(session, cause);
	}

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		// 将收到的信息解码
		String data = URLDecoder.decode(message.toString(), "UTF-8").trim();
		// 这是博主自定义的一个传输协议,采用json数据格式传输
		ChatMessage receiveData = GsonUtils.jsonToBean(data, ChatMessage.class);
		if ("LOGIN".equals(receiveData.getType())) {
			userSessionMap.put(receiveData.getSender(), session);
			for (String key : userSessionMap.keySet()) {
				if (key.equals(receiveData.getSender()))
					continue;
				IoSession ioSession = userSessionMap.get(key);
				ChatMessage sendData = new ChatMessage();
				sendData.setContent(receiveData.getSender() + "上线了");
				sendData.setSender(receiveData.getSender());
				sendData.setType("LOGIN");
				sendData.setReceiver(key);
				sendData.setSendTime(new Date().getTime());
				sendData.setReceiveTime(new Date().getTime());
				String datas = GsonUtils.beanToJson(sendData).trim();
//				System.out.println(datas);
				//在Android端,涉及到中文传输的时候,总会出现乱码或者是收不到消息的问题,试了各种办法,最后只能通过URLEncoder转码的方式来实现中文传输,
				//其他方式统统都不行,在服务端,记得转码完了之后加上换行符,不然android端收不到消息。
				ioSession.write(URLEncoder.encode(datas, "UTF-8") + "\r\n");
			}
		} else if ("OFFLINE".equals(receiveData.getType())) {
			userSessionMap.remove(receiveData.getSender());
			for (String key : userSessionMap.keySet()) {
				IoSession ioSession = userSessionMap.get(key);
				ChatMessage sendData = new ChatMessage();
				sendData.setContent(receiveData.getSender() + "下线了");
				sendData.setSender(receiveData.getSender());
				sendData.setType("OFFLINE");
				sendData.setReceiver(key);
				sendData.setSendTime(new Date().getTime());
				sendData.setReceiveTime(new Date().getTime());
				String datas = GsonUtils.beanToJson(sendData).trim();
//				System.out.println(datas);
				ioSession.write(URLEncoder.encode(datas, "UTF-8") + "\r\n");
			}

		} else {
			System.out.println(data);
			IoSession ioSession = userSessionMap.get(receiveData.getReceiver());
			ioSession.write(URLEncoder.encode(data, "UTF-8") + "\r\n");
		}
	}

	@Override
	public void sessionCreated(IoSession session) throws Exception {
		super.sessionCreated(session);
	}

	@Override
	public void sessionIdle(IoSession session, IdleStatus status)
			throws Exception {
		super.sessionIdle(session, status);
	}

}

WEB端设置主要信息讲解完毕,详情可以下载DEMO查看测试,接下来讲解Android端

二、MINA框架客户端开发

1、Android后台接受消息服务编写:

/**   
 * @Company: Batways
 * @Project:Tnt 
 * @Title: UpdateService.java
 * @Package com.batways.tnt.service
 * @Description: TODO
 * @author victor_freedom ([email protected])
 * @date 2014年9月6日 下午2:16:08
 * @version V1.0   
 */

package com.example.minatest.service;

import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Date;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;

import com.example.minatest.LoggerUtils;
import com.example.minatest.bean.ChatMessage;
import com.example.minatest.gloable.ConstantValues;
import com.example.minatest.utils.GsonUtils;

/**
 * @ClassName: UpdateService
 * @author victor_freedom ([email protected])
 * @createddate 2014年9月6日 下午2:16:08
 * @Description: TODO
 * 
 */
public class UpdateService extends Service {
	private DataHandler dataHandler;
	private IoSession session;
	private String TAG = "UpdateService";
	private BroadcastReceiver sendMessage = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			try {
				ChatMessage messge = new ChatMessage();
				messge.setType("NORMAL");
				String data = intent.getStringExtra("data");
				messge.setContent(data);
				messge.setSendTime(new Date().getTime());
				messge.setReceiveTime(new Date().getTime());
				//发送者
				messge.setSender("justice");
				//接受者
				messge.setReceiver("freedom");
				//这里发送消息的时候需要特别注意,这里和服务端有点不同,换行符需要和实体内容一起进行编码,不然服务端接受不到消息。
				String info = GsonUtils.beanToJson(messge) + "\r\n";
				session.write(URLEncoder.encode(info, "UTF-8"));

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		IntentFilter filter = new IntentFilter();
		filter.addAction(ConstantValues.SENDMESSGE);
		filter.addCategory(Intent.CATEGORY_DEFAULT);
		dataHandler = new DataHandler();
		registerReceiver(sendMessage, filter);
		new Thread(new Runnable() {

			@Override
			public void run() {
				IoConnector connector = new NioSocketConnector();
				connector.getFilterChain().addLast(
						"textProtocol",
						new ProtocolCodecFilter(new TextLineCodecFactory(
								Charset.forName("UTF-8"), LineDelimiter.WINDOWS
										.getValue(), LineDelimiter.WINDOWS
										.getValue())));

				connector.getSessionConfig().setReadBufferSize(1024);
				connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE,
						10);

				connector.setHandler(dataHandler);
				// 这里是异步操作 连接后立即返回
				ConnectFuture future = connector.connect(new InetSocketAddress(
						"192.168.1.111", 9090));
				future.awaitUninterruptibly();// 等待连接创建完成
				session = future.getSession();

				session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
				connector.dispose();
			}
		}).start();

		super.onCreate();
	}

	@Override
	public void onDestroy() {
		unregisterReceiver(sendMessage);
		sendMessage = null;
		super.onDestroy();
	}

	private class DataHandler extends IoHandlerAdapter {

		@Override
		public void sessionCreated(IoSession session) throws Exception {
		}

		@Override
		public void sessionOpened(IoSession session) throws Exception {
			//上线与服务器建立连接之后,告诉服务器上线了
			ChatMessage messge = new ChatMessage();
			messge.setType("LOGIN");
			//发送者
			messge.setSender("justice");
			messge.setContent("login");
			//接受者(这里接受者可以不需要设置)
			messge.setReceiver("freedom");
			messge.setSendTime(new Date().getTime());
			messge.setReceiveTime(new Date().getTime());
			//这里发送消息的时候需要特别注意,这里和服务端有点不同,换行符需要和实体内容一起进行编码,不然服务端接受不到消息。
			session.write(URLEncoder.encode(GsonUtils.beanToJson(messge)
					+ "\r\n", "UTF-8"));
		}

		@Override
		public void sessionClosed(IoSession session) throws Exception {
		}

		@Override
		public void sessionIdle(IoSession session, IdleStatus status)
				throws Exception {
		}

		@Override
		public void exceptionCaught(IoSession session, Throwable cause)
				throws Exception {
		}

		@Override
		public void messageReceived(IoSession session, Object message)
				throws Exception {
			//处理从服务端接收到的消息
			String result = URLDecoder.decode(message.toString(), "UTF-8");
			LoggerUtils.i(TAG, result);
			BroadcastHelper.sendBroadCast(getBaseContext(),
					ConstantValues.RECEIVEMESSGE, "data", result);
		}

		@Override
		public void messageSent(IoSession session, Object message)
				throws Exception {

		}
	}

}


2、主Activity类编写:

package com.example.minatest;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.example.minatest.bean.ChatMessage;
import com.example.minatest.gloable.ConstantValues;
import com.example.minatest.service.BroadcastHelper;
import com.example.minatest.service.UpdateService;
import com.example.minatest.utils.GsonUtils;

/**
 * @ClassName: MainActivity
 * @author victor_freedom ([email protected])
 * @createddate 2014-12-15 上午12:49:29
 * @Description: TODO
 */
public class MainActivity extends Activity {
	private Button sendMessage;
	private TextView recevier;
	private EditText content;
	private Intent intent;
	protected String TAG = "MainActivity";
	// 接受消息广播
	private BroadcastReceiver contentRecevier = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			String data = intent.getStringExtra("data");
			ChatMessage receiveData = GsonUtils.jsonToBean(data,
					ChatMessage.class);
			// 如果接受到空消息时过滤掉
			if (null == receiveData) {
				return;
			}
			// 将消息展现出来。
			recevier.setText(receiveData.getSender() + ":"
					+ receiveData.getContent());
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		intent = new Intent(MainActivity.this, UpdateService.class);
		startService(intent);
		IntentFilter filter = new IntentFilter();
		filter.addAction(ConstantValues.RECEIVEMESSGE);
		filter.addCategory(Intent.CATEGORY_DEFAULT);
		registerReceiver(contentRecevier, filter);
		sendMessage = (Button) findViewById(R.id.send);
		content = (EditText) findViewById(R.id.content);
		recevier = (TextView) findViewById(R.id.recevier);
		recevier.setText("消息");
		sendMessage.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				String data = content.getText().toString();
				if (data.isEmpty())
					return;
				// 点击发送消息到服务器
				BroadcastHelper.sendBroadCast(MainActivity.this,
						ConstantValues.SENDMESSGE, "data", data);
				content.setText("");
			}
		});
	}

	@Override
	protected void onDestroy() {
		//退出处理,这里应该在发个下线的消息,时间太晚了,博主就不写了。
		unregisterReceiver(contentRecevier);
		contentRecevier = null;
		stopService(intent);
		intent = null;
		super.onDestroy();
	}
}

附上几张博主测试时候的测试图,博主测试的时候写死两个用户,一个freedom,一个justice:

终于写完了,今晚不错,写了两篇,前一篇JAVA文件IO流详解也写了两个小时,这个从11点开始写,到现在瞬间就到1点了。

希望这篇博客可以帮到所有运用MINA框架做Android开发的人。

DEMO下载地址(服务端加客户端):http://download.csdn.net/detail/victorfreedom/8259969


Android开发之基于MINA框架的聊天通信功能实现_第1张图片Android开发之基于MINA框架的聊天通信功能实现_第2张图片Android开发之基于MINA框架的聊天通信功能实现_第3张图片Android开发之基于MINA框架的聊天通信功能实现_第4张图片Android开发之基于MINA框架的聊天通信功能实现_第5张图片Android开发之基于MINA框架的聊天通信功能实现_第6张图片Android开发之基于MINA框架的聊天通信功能实现_第7张图片Android开发之基于MINA框架的聊天通信功能实现_第8张图片



你可能感兴趣的:(Android技术)