这篇文章也是通过学习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包:
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;
}
}
}
运行结果截屏:
logcat结果:
服务端日志:
总结:
- 代码编写完成,测试的时候,如果是笔记本电脑,可以让手机和电脑在一个WiFi下,这样手机可以通过IP访问电脑上的服务器。
- 在android7以上版本中,对权限做了很多限制,这里涉及socket连接,因此需要放开网络限制。配置在AndroidMenifest.xml中。否则会出现socket连接异常,permission denied。
- 如果用的是台式机,那么手机有可能在无线网络下很难连接上服务器,可以考虑将服务端代码部署到腾讯云,这样开启服务之后,可以通过设置腾讯云主机公网IP的方式访问mina服务端,本实验就是将mina server部署在腾讯云主机上测试的。
最后附上安卓客户端工程完整代码:https://github.com/buejee/minaclient
服务端代码很简单,就一个类,上面的MinaServer.java代码就可以使用,这里不再贴出。