这篇文章也是通过学习mina框架视频来的,网上也有很多类似的代码,这里通过自己敲一遍代码,熟悉mina框架的使用以及安卓编程。mina框架作为一个网络异步编程框架,它和netty一样,底层实现了nio。

核心类:

IoAcceptor:服务端接收器,负责创建socket服务,并监听客户端连接。

IoSession:连接会话,可以通过write方法向外发送消息。

IoHandlerAdapter:连接处理器,负责消息收发。

mina实现长连接,是构建基于tcp/ip的socket网络连接,他与http连接的本质区别是,客户端可以维持和服务端的长连接,因为连接一直存在,减少了由于轮询导致的服务器压力,适合做消息推送系统(push),服务器可以主动给客户端推送消息。

服务端代码:(很简单,就一个类)

MinaServer.java,这里采用的是mina-core-2.0.19版本,使用maven构建工程,可以考虑将group:org.apache.mina,artifact:mina-core,version:2.0.19加入到pom.xml文件中。

package com.xxx.mina;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Date;

import org.apache.mina.core.service.IoAcceptor;
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.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MinaServer {
	private static final Logger log = LoggerFactory.getLogger(MinaServer.class);

	public static void main(String[] args) {
		IoAcceptor acceptor = new NioSocketAcceptor();
		acceptor.getFilterChain().addLast("logger", new LoggingFilter());
		acceptor.getFilterChain().addLast("codec", 
				new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
		acceptor.setHandler(new ServerHandler());
		acceptor.getSessionConfig().setReadBufferSize(2048);
		acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
		try {
			acceptor.bind(new InetSocketAddress(9123));
			log.info("mina server listen at port 9123.");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
    private static class ServerHandler extends IoHandlerAdapter{
    	@Override
    	public void sessionCreated(IoSession session) throws Exception {
    		super.sessionCreated(session);
    	}
    	
    	@Override
    	public void sessionOpened(IoSession session) throws Exception {
    		super.sessionOpened(session);
    		log.info("one client connected.");
    	}
    	
    	@Override
    	public void messageReceived(IoSession session, Object message)
    			throws Exception {
    		super.messageReceived(session, message);
    		String res = message.toString();
    		Date date = new Date();
    		session.write(date.toString());
    		log.info("server receive message : "+res);
    	}
    	
    	@Override
    	public void messageSent(IoSession session, Object message)
    			throws Exception {
    		super.messageSent(session, message);
    	}
    }
}

客户端代码:(稍微复杂一点,毕竟涉及到UI,还需要考虑与服务端的连接)

客户端工程需要的jar包:

android tcp长连接库 android长连接框架_android tcp长连接库

AndroidMainfest.xml,这里关键就是将<uses-permission android:name="android.permission.INTERNET">加入进来

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xxx.mina"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="22"
        android:targetSdkVersion="27" />

    <uses-permission android:name="android.permission.INTERNET"/>
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <service android:name=".common.MinaService"></service>
    </application>

</manifest>

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.xxx.mina.MainActivity" >

    <TextView 
        android:id="@+id/datetxt"
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content"
        android:text="@string/showdate"
        />
    <Button
        android:id="@+id/connectBtn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="40dp"
        android:text="@string/connect" />
    
    <Button 
        android:id="@+id/sendBtn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="100dp"
        android:text="@string/send"/>

</RelativeLayout>

ConnectionConfig.java

package com.xxx.mina.common;

import android.content.Context;

public class ConnectionConfig {
	private Context context;
	private String ip;
	private int port;
	private int readBufferSize;
	private long connectionTimeout;
	public Context getContext() {
		return context;
	}
	public void setContext(Context context) {
		this.context = context;
	}
	public String getIp() {
		return ip;
	}
	public void setIp(String ip) {
		this.ip = ip;
	}
	public int getPort() {
		return port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	public int getReadBufferSize() {
		return readBufferSize;
	}
	public void setReadBufferSize(int readBufferSize) {
		this.readBufferSize = readBufferSize;
	}
	public long getConnectionTimeout() {
		return connectionTimeout;
	}
	public void setConnectionTimeout(long connectionTimeout) {
		this.connectionTimeout = connectionTimeout;
	}
	
	public static class Builder{
		private Context context;
		private String ip="123.207.70.98";
		private int port=9123;
		private int readBufferSize=10240;
		private long connectionTimeout=10000;
		
		public Builder(Context context){
			this.context = context;
		}
		
		public Builder setPort(int port){
			this.port = port;
			return this;
		}
		public Builder setIp(String ip){
			this.ip = ip;
			return this;
		}
		public Builder setReadBufferSize(int readBufferSize){
			this.readBufferSize = readBufferSize;
			return this;
		}
		public Builder setConnectionTimeout(int connectionTimeout){
			this.connectionTimeout = connectionTimeout;
			return this;
		}
		
		private void applyConfig(ConnectionConfig config){
			config.context = this.context;
			config.ip = this.ip;
			config.port = this.port;
			config.connectionTimeout = this.connectionTimeout;
			config.readBufferSize = this.readBufferSize;
		}
		
		public ConnectionConfig builder(){
			ConnectionConfig config = new ConnectionConfig();
			applyConfig(config);
			return config;
		}
	}

}

ConnectionManager.java

package com.xxx.mina.common;

import java.lang.ref.WeakReference;
import java.net.InetSocketAddress;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

public class ConnectionManager {
	public static final String BROADCAST_ACTION="com.xxx.mina.common";
	public static final String MESSAGE = "message";
	private ConnectionConfig config;
	private WeakReference<Context> context;
	private NioSocketConnector connection;
	private IoSession session;
	private InetSocketAddress address;
	
	public ConnectionManager(ConnectionConfig config){
		this.config = config;
		this.context = new WeakReference<Context>(config.getContext());
		init();
	}
	
	private void init(){
		address = new InetSocketAddress(config.getIp(),config.getPort());
		connection = new NioSocketConnector();
		connection.setDefaultRemoteAddress(address);
		connection.getSessionConfig().setReadBufferSize(config.getReadBufferSize());
		connection.getFilterChain().addLast("logger", new LoggingFilter());
		connection.getFilterChain().addLast("codec", new ProtocolCodecFilter(
				new ObjectSerializationCodecFactory()));
		connection.setHandler(new DefaultHandler(context.get()));
	}
	
	public boolean connect(){
		try {
			ConnectFuture future = connection.connect();
			future.awaitUninterruptibly();
			session = future.getSession();
		} catch (Exception e) {
			e.printStackTrace();
			Log.e("tag",e.getMessage());
			return false;
		}
		return session == null ? false:true;
	}
	
	public void disConnect(){
		connection.dispose();
		connection = null;
		session = null;
		address = null;
		context = null;
	}
	private static class DefaultHandler extends IoHandlerAdapter{
		private Context context;
		public DefaultHandler(Context context) {
			this.context = context;
		}
		
		@Override
		public void sessionOpened(IoSession session) throws Exception {
			//put session to sessionmanager for send message
			SessionManager.getInstance().setSession(session);
		}
		
		@Override
		public void messageReceived(IoSession session, Object message)
				throws Exception {
			if(context != null){
				Intent intent = new Intent(BROADCAST_ACTION);
				intent.putExtra(MESSAGE, message.toString());
				LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
			}
		}
	}
}

MinaService.java

package com.xxx.mina.common;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.HandlerThread;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

public class MinaService extends Service {

	private ConnectionThread thread;
	@Override
	public void onCreate() {
		super.onCreate();
		thread = new ConnectionThread("mina", getApplicationContext());
		thread.start();
		Log.e("tag", "start thread to connect");
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		thread.disConnect();
		thread = null;
	}
	@Nullable
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	class ConnectionThread extends HandlerThread {
		boolean isConnected;
		ConnectionManager manager;

		public ConnectionThread(String name, Context context) {
			super(name);
			ConnectionConfig config = new ConnectionConfig.Builder(context)
					.setIp("123.207.70.98").setPort(9123)
					.setReadBufferSize(10240).setConnectionTimeout(10000)
					.builder();
			manager = new ConnectionManager(config);
		}

		@Override
		protected void onLooperPrepared() {
			while(true) {
				isConnected = manager.connect();
				if (isConnected){
					Log.e("tag", "connect successfully.");
					break;
				}
				try {
					Log.e("tag", "connect fail.");
					Thread.sleep(3000);
				} catch (Exception e) {
					Log.e("tag", "fail with error");
				}
			}
		}
		
		public void disConnect(){
			manager.disConnect();
		}
	}
}

SessionManager.java

package com.xxx.mina.common;

import org.apache.mina.core.session.IoSession;

import android.util.Log;

public class SessionManager {
	private static SessionManager instance = null;
	
	private IoSession session;
	public static SessionManager getInstance(){
		if(instance==null){
			synchronized (SessionManager.class) {
				if(instance==null){
					instance = new SessionManager();
				}
			}
		}
		return instance;
	}
	private SessionManager(){}
	public void setSession(IoSession session){this.session = session;}
	public void writeToServer(Object msg){
		if(session != null){
			Log.e("tag", "mina client ready to send message:"+msg);
			session.write(msg);
		}
	}
	
	public void closeSession(){
		if(session != null){
			session.closeOnFlush();
		}
	}
	
	public void removeSession(){
		this.session  = null;
	}
}

MainActivity.java

package com.xxx.mina;

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.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

import com.xxx.mina.common.ConnectionManager;
import com.xxx.mina.common.MinaService;
import com.xxx.mina.common.SessionManager;

public class MainActivity extends Activity implements OnClickListener{
	
	protected TextView dateView;
	protected Button connectBtn;
	protected Button sendBtn;
	
	MessageBroadcastReceiver receiver = new MessageBroadcastReceiver();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		registerBroadcast();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		stopService(new Intent(this,MinaService.class));
		unregisterBroadcast();
	}
	
	//receive message and update ui
	private class MessageBroadcastReceiver extends BroadcastReceiver{

		@Override
		public void onReceive(Context context, Intent intent) {
			dateView.setText(intent.getStringExtra(ConnectionManager.MESSAGE));
		}
		
	}
	
	public void registerBroadcast(){
		IntentFilter filter = new IntentFilter(ConnectionManager.BROADCAST_ACTION);
		LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
	}
	
	public void unregisterBroadcast(){
		LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
	}
	
	public void initView(){
		connectBtn = (Button)findViewById(R.id.connectBtn);
		sendBtn = (Button)findViewById(R.id.sendBtn);
		dateView = (TextView)findViewById(R.id.datetxt);
		connectBtn.setOnClickListener(this);
		sendBtn.setOnClickListener(this);
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
			case R.id.connectBtn:
				Intent intent = new Intent(this,MinaService.class);
				startService(intent);
				Log.e("tag", "connect to server");	
				break;
			case R.id.sendBtn:
				SessionManager.getInstance().writeToServer("123");
				Log.e("tag", "send message to server");
				break;
		}
	}
}

运行结果截屏:

android tcp长连接库 android长连接框架_apache_02

logcat结果:

android tcp长连接库 android长连接框架_mina_03

服务端日志:

android tcp长连接库 android长连接框架_android_04

总结:

  • 代码编写完成,测试的时候,如果是笔记本电脑,可以让手机和电脑在一个WiFi下,这样手机可以通过IP访问电脑上的服务器。
  • 在android7以上版本中,对权限做了很多限制,这里涉及socket连接,因此需要放开网络限制。配置在AndroidMenifest.xml中。否则会出现socket连接异常,permission denied。
  • 如果用的是台式机,那么手机有可能在无线网络下很难连接上服务器,可以考虑将服务端代码部署到腾讯云,这样开启服务之后,可以通过设置腾讯云主机公网IP的方式访问mina服务端,本实验就是将mina server部署在腾讯云主机上测试的。

最后附上安卓客户端工程完整代码:https://github.com/buejee/minaclient

服务端代码很简单,就一个类,上面的MinaServer.java代码就可以使用,这里不再贴出。