1. 程式人生 > >使用mina實現Android長連線

使用mina實現Android長連線

一.概述
android長連線的實現有很多種,最常用的是使用第三方的長連線,比如推送服務的實現.使用第三方的長連線雖然在實現上最簡單,但是擴充套件性缺少最差,要受限於三方的api,所以在這裡介紹使用mina來實現android的長連線服務.

二.服務端的實現
首先來說說服務端的實現,這裡只是舉個簡單的例項,目的只是告訴大家如何實現.

  • 匯入需要的jar包
    這裡寫圖片描述

  • 具體的程式碼實現

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; /** * mina服務端 */ public class MainService { 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 DemoServerHandler()); //設定讀取大小 acceptor.getSessionConfig().setReadBufferSize(2048); //設定超時時間 acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); try { //開啟監聽 acceptor.bind(new InetSocketAddress(9123)); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } private static class DemoServerHandler extends IoHandlerAdapter{ @Override public void sessionCreated(IoSession session) throws Exception { // TODO Auto-generated method stub super.sessionCreated(session); } /** * 一個客戶端連線時回撥的方法 * @param session * @throws Exception */ @Override public void sessionOpened(IoSession session) throws Exception { // TODO Auto-generated method stub super.sessionOpened(session); } /** * 接收到訊息時回撥的方法 * @param session * @param message * @throws Exception */ @Override public void messageReceived(IoSession session, Object message) throws Exception { // TODO Auto-generated method stub super.messageReceived(session, message); //接收客戶端的訊息 String msg = message.toString(); Date date = new Date(); //傳送訊息給客戶端 session.write(msg+date.toString()); } /** * 傳送訊息時回撥的方法 * @param session * @param message * @throws Exception */ @Override public void messageSent(IoSession session, Object message) throws Exception { // TODO Auto-generated method stub super.messageSent(session, message); } /** * 一個客戶端斷開時回撥的方法 * @param session * @throws Exception */ @Override public void sessionClosed(IoSession session) throws Exception { // TODO Auto-generated method stub super.sessionClosed(session); } } }

服務端程式碼非常簡單,只是簡單的開啟,然後接收客戶端的資訊,在將進行回傳給客戶端.

三.Android客戶端的實現
Android客戶端的實現流程就是一個使用者介面,用來開啟服務,傳送訊息和顯示訊息->一個服務,用來完成長連線->一個長連線的管理類,用來完成長連線具體實現->一個配置檔案類,用來配置長連線實現需要的引數.

  • 使用者介面的具體程式碼
public class MainActivity extends AppCompatActivity {
    private TextView tv;
    private Button btn,btn2;
    private MyBroadcaseRedceiver mBroadcaseRedceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
        btn2 = (Button) findViewById(R.id.btn2);

        //註冊廣播 用來接收伺服器返回的資訊
        IntentFilter filter = new IntentFilter(ConnectManager.BROADCAST_ACTION);
        mBroadcaseRedceiver = new MyBroadcaseRedceiver();
        registerReceiver(mBroadcaseRedceiver, filter);
        //設定按鈕的點選事件
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SessionManager.getmInstance().writeToServer("發給伺服器的訊息 ");
            }
        });
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //開啟服務
                Intent intent = new Intent(MainActivity.this, ConnectService.class);
                startService(intent);
            }
        });
    }





    public class MyBroadcaseRedceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            //更新介面
            tv.setText(intent.getStringExtra(ConnectManager.MESSAGE));
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mBroadcaseRedceiver);
    }
}

介面的效果圖如下:
這裡寫圖片描述

  • 服務的具體程式碼

/**
 * 開啟長連線的服務
 * Created by lyf on 2017/5/20.
 */

public class ConnectService extends Service {
    private ConnectThred connectThred;

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

    @Override
    public void onCreate() {
        super.onCreate();
        //使用子執行緒開啟連線
        connectThred = new ConnectThred("mina",getApplicationContext());
        connectThred.start();
    }



    @Override
    public void onDestroy() {
        super.onDestroy();
        connectThred.disConnection();
        connectThred=null;
    }

    /**
     * 負責呼叫connectmanager類來完成與伺服器的連線
     */
    class ConnectThred extends HandlerThread {

        boolean isConnection;
        ConnectManager mManager;
        public ConnectThred(String name,Context context) {
            super(name);
            //建立配置檔案類
            ConnectConfig config = new ConnectConfig.Builder(context)
                    .setIp("192.168.0.113")
                    .setPort(9123)
                    .setReadBufferSize(10240)
                    .setConnectionTimeout(10000)
                    .bulid();
            //建立連線的管理類
            mManager = new ConnectManager(config);
        }

        @Override
        protected void onLooperPrepared() {
            //利用迴圈請求連線
            while (true) {
                isConnection = mManager.connect();
                if (isConnection) {
                    //當請求成功的時候,跳出迴圈
                    break;
                }
                try {
                    Thread.sleep(3000);
                } catch (Exception e) {

                }
            }
        }

        /**
         *斷開連線
         */
        public void disConnection(){
            mManager.disConnect();
        }

    }


因為長連線一般都是在後臺執行的,所以推薦用服務開啟,為了保證長連線的開啟,我們在這裡利用子執行緒開啟死迴圈請求連線,直到成功為止.

  • 長連線實現的管理類的程式碼

/**
 * 連線的管理類
 * Created by lyf on 2017/5/20.
 */

public class ConnectManager {
    public static final String BROADCAST_ACTION="com.example.lyf.longconnect";
    public static final String MESSAGE="message";

    private ConnectConfig mConfig;//配置檔案
    private WeakReference<Context> mContext;
    private NioSocketConnector mConnection;
    private IoSession mSessioin;
    private InetSocketAddress mAddress;

    public ConnectManager(ConnectConfig mConfig) {
        this.mConfig = mConfig;
        this.mContext = new WeakReference<Context>(mConfig.getmContext());
        init();
    }

    private void init() {
        mAddress = new InetSocketAddress(mConfig.getIp(),mConfig.getPort());
        //建立連線物件
        mConnection = new NioSocketConnector();
        //設定連線地址
        mConnection.setDefaultRemoteAddress(mAddress);
        mConnection.getSessionConfig().setReadBufferSize(mConfig.getReadBufferSize());
        //設定過濾
        mConnection.getFilterChain().addLast("logger",new LoggingFilter());
        mConnection.getFilterChain().addLast("codec",new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
        //設定連線監聽
        mConnection.setHandler(new DefaultHandler(mContext.get()));
    }

    private static class DefaultHandler extends IoHandlerAdapter{
        private Context context;

        public DefaultHandler(Context context) {
            this.context = context;
        }

        /**
         * 連線成功時回撥的方法
         * @param session
         * @throws Exception
         */
        @Override
        public void sessionOpened(IoSession session) throws Exception {
            //當與伺服器連線成功時,將我們的session儲存到我們的sesscionmanager類中,從而可以傳送訊息到伺服器
            SessionManager.getmInstance().setIoSession(session);
        }

        /**
         * 接收到訊息時回撥的方法
         * @param session
         * @param message
         * @throws Exception
         */
        @Override
        public void messageReceived(IoSession session, Object message) throws Exception {

            if (context != null) {
                //將接收到的訊息利用廣播發送出去
                Intent intent = new Intent(BROADCAST_ACTION);
                intent.putExtra(MESSAGE,message.toString());
                context.sendBroadcast(intent);
            }
        }
    }

    /**
     * 與伺服器連線的方法
     * @return
     */
    public boolean connect(){
       try{
           ConnectFuture future =mConnection.connect();
           future.awaitUninterruptibly();
           mSessioin = future.getSession();
       }catch (Exception e){
           e.printStackTrace();
            return false;
       }
        return mSessioin==null?false:true;
    }

    /**
     * 斷開連線的方法
     */
    public void disConnect(){
        mConnection.dispose();
        mConnection=null;
        mSessioin=null;
        mAddress=null;
        mContext=null;
    }


這個管理類就是利用mina框架的api來實現的與伺服器連線的方法.具體的分析看註釋.

  • 配置檔案類

/**
 * 連線的配置檔案
 * Created by lyf on 2017/5/20.
 */

public class ConnectConfig {
    private Context mContext;
    private String ip;
    private int port;
    private int readBufferSize; //快取大小
    private long connectionTimeout;//連線超時時間

    public Context getmContext() {
        return mContext;
    }



    public String getIp() {
        return ip;
    }



    public int getPort() {
        return port;
    }


    public int getReadBufferSize() {
        return readBufferSize;
    }



    public long getConnectionTimeout() {
        return connectionTimeout;
    }



    public static class Builder{
        private Context mContext;
        private String ip="";
        private int port=9123;
        private int readBufferSize=10240; //快取大小
        private long connectionTimeout=10000;//連線超時時間


        public Builder(Context mContext) {
            this.mContext = mContext;
        }

        public Builder setConnectionTimeout(long connectionTimeout) {
            this.connectionTimeout = connectionTimeout;
            return this;
        }

        public Builder setIp(String ip) {
            this.ip = ip;
            return this;
        }

        public Builder setmContext(Context mContext) {
            this.mContext = mContext;
            return this;
        }

        public Builder setPort(int port) {
            this.port = port;
            return this;
        }

        public Builder setReadBufferSize(int readBufferSize) {
            this.readBufferSize = readBufferSize;
            return this;
        }

        public ConnectConfig bulid(){
            ConnectConfig connectConfig = new ConnectConfig();
            connectConfig.connectionTimeout = this.connectionTimeout;
            connectConfig.ip = this.ip;
            connectConfig.port = this.port;
            connectConfig.mContext = this.mContext;
            connectConfig.readBufferSize = this.readBufferSize;
            return  connectConfig;
        }
    }
}

這裡就是利用Builder模型來方便建立配置檔案物件,就是配置一些實現長連線需要的引數.

  • 與伺服器通訊的單例

/**
 * session管理類,通過ioSession與伺服器通訊
 * Created by lyf on 2017/5/21.
 */

public class SessionManager {
    private static  SessionManager mInstance = null;

    private IoSession ioSession;//最終與伺服器 通訊的物件

    public static SessionManager getmInstance(){
        if (mInstance == null) {
            synchronized (SessionManager.class) {
                if (mInstance == null) {
                    mInstance = new SessionManager();
                }
            }
        }
        return mInstance;
    }
    private SessionManager(){

    }

    public void setIoSession(IoSession ioSession) {
        this.ioSession = ioSession;
    }

    /**
     * 將物件寫到伺服器
     */
    public void writeToServer(Object msg) {
        if (ioSession != null) {
            ioSession.write(msg);
        }
    }

    /**
     * 關閉連線
     */
    public void closeSession() {
        if (ioSession != null) {
            ioSession.closeOnFlush();
        }
    }

    public void removeSession() {
        ioSession = null;
    }


}

通過這個單例的writeToServer方法就可以將資訊傳送到伺服器了.

四.總結
以上就是通過mina框架實現的一套簡單長連線,通過mina框架來實現長連線雖然比第三方的複雜,好處就是便於擴充套件,想要什麼功能都可以實現.本文的程式碼我都放到了github