1. 程式人生 > >Socket長連線Android端心跳機制實現

Socket長連線Android端心跳機制實現

1. 把socket連結和心跳功能都放在一個Service中,為什麼要放在Service中?

一般我們這種socket幾乎是跟app的生命週期一樣長,甚至更長。不管在不在Service中去完成操作,我們都得開非同步執行緒,雖然Service並不是非同步操作,但是為了提升我們任務的優先順序,我們最好是放在Service中,因為Service是由Android系統管理的,並且擁有比較高的優先順序,執行緒是java中的非同步任務載體,可以說Android系統不太認識執行緒。放在Service中可以很大程度上避免任務被回收或者關閉

2. 為什麼需要心跳機制?

由於移動裝置的網路的複雜性,經常會出現網路斷開,如果沒有心跳包的檢測,客戶端只會在需要傳送資料的時候才知道自己已經斷線,會延誤,甚至丟失伺服器傳送過來的資料。我們可以每隔3秒鐘或者每間隔1秒鐘去判斷一下socket是否斷開,沒斷開就讀取資料,斷開了就重新連線socket。

3. Service與Activity之間怎麼通訊?

3.1 Activity呼叫bindService (Intent service, ServiceConnection conn, int flags)方法,得到Service物件的一個引用,這樣Activity可以直接呼叫到Service中的方法,如果要主動通知Activity,我們可以利用回撥方法

3.2 Service向Activity傳送訊息,可以使用廣播,當然Activity要註冊相應的接收器。比如Service要向多個Activity傳送同樣的訊息的話,用這種方法就更好

4.原始碼

4.1 Service程式碼

package danxx.library.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;

import android.app.Service;
import android.content.Intent;
import
android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; /** * Created by dawish on 2017/7/24. * 由於移動裝置的網路的複雜性,經常會出現網路斷開,如果沒有心跳包的檢測, * 客戶端只會在需要傳送資料的時候才知道自己已經斷線,會延誤,甚至丟失伺服器傳送過來的資料。 */ public class BackService extends Service { private static final String TAG = "danxx"; /**心跳頻率*/ private static final long HEART_BEAT_RATE = 3 * 1000; /**伺服器ip地址*/ public static final String HOST = "192.168.123.27";// "192.168.1.21";// /**伺服器埠號*/ public static final int PORT = 9800; /**伺服器訊息回覆廣播*/ public static final String MESSAGE_ACTION="message_ACTION"; /**伺服器心跳回復廣播*/ public static final String HEART_BEAT_ACTION="heart_beat_ACTION"; /**讀執行緒*/ private ReadThread mReadThread; private LocalBroadcastManager mLocalBroadcastManager; /***/ private WeakReference<Socket> mSocket; // For heart Beat private Handler mHandler = new Handler(); /**心跳任務,不斷重複呼叫自己*/ private Runnable heartBeatRunnable = new Runnable() { @Override public void run() { if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) { boolean isSuccess = sendMsg("HeartBeat");//就傳送一個\r\n過去 如果傳送失敗,就重新初始化一個socket if (!isSuccess) { mHandler.removeCallbacks(heartBeatRunnable); mReadThread.release(); releaseLastSocket(mSocket); new InitSocketThread().start(); } } mHandler.postDelayed(this, HEART_BEAT_RATE); } }; private long sendTime = 0L; /** * aidl通訊回撥 */ private IBackService.Stub iBackService = new IBackService.Stub() { /** * 收到內容傳送訊息 * @param message 需要傳送到伺服器的訊息 * @return * @throws RemoteException */ @Override public boolean sendMessage(String message) throws RemoteException { return sendMsg(message); } }; @Override public IBinder onBind(Intent arg0) { return iBackService; } @Override public void onCreate() { super.onCreate(); new InitSocketThread().start(); mLocalBroadcastManager=LocalBroadcastManager.getInstance(this); } public boolean sendMsg(final String msg) { if (null == mSocket || null == mSocket.get()) { return false; } final Socket soc = mSocket.get(); if (!soc.isClosed() && !soc.isOutputShutdown()) { new Thread(new Runnable() { @Override public void run() { try { OutputStream os = soc.getOutputStream(); String message = msg + "\r\n"; os.write(message.getBytes()); os.flush(); } catch (IOException e) { e.printStackTrace(); } } }).start(); sendTime = System.currentTimeMillis();//每次傳送成資料,就改一下最後成功傳送的時間,節省心跳間隔時間 } else { return false; } return true; } private void initSocket() {//初始化Socket try { Socket so = new Socket(HOST, PORT); mSocket = new WeakReference<Socket>(so); mReadThread = new ReadThread(so); mReadThread.start(); mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//初始化成功後,就準備傳送心跳包 } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 心跳機制判斷出socket已經斷開後,就銷燬連線方便重新建立連線 * @param mSocket */ private void releaseLastSocket(WeakReference<Socket> mSocket) { try { if (null != mSocket) { Socket sk = mSocket.get(); if (!sk.isClosed()) { sk.close(); } sk = null; mSocket = null; } } catch (IOException e) { e.printStackTrace(); } } class InitSocketThread extends Thread { @Override public void run() { super.run(); initSocket(); } } // Thread to read content from Socket class ReadThread extends Thread { private WeakReference<Socket> mWeakSocket; private boolean isStart = true; public ReadThread(Socket socket) { mWeakSocket = new WeakReference<Socket>(socket); } public void release() { isStart = false; releaseLastSocket(mWeakSocket); } @Override public void run() { super.run(); Socket socket = mWeakSocket.get(); if (null != socket) { try { InputStream is = socket.getInputStream(); byte[] buffer = new byte[1024 * 4]; int length = 0; while (!socket.isClosed() && !socket.isInputShutdown() && isStart && ((length = is.read(buffer)) != -1)) { if (length > 0) { String message = new String(Arrays.copyOf(buffer, length)).trim(); Log.e(TAG, message); //收到伺服器過來的訊息,就通過Broadcast傳送出去 if(message.equals("ok")){//處理心跳回復 Intent intent=new Intent(HEART_BEAT_ACTION); mLocalBroadcastManager.sendBroadcast(intent); }else{ //其他訊息回覆 Intent intent=new Intent(MESSAGE_ACTION); intent.putExtra("message", message); mLocalBroadcastManager.sendBroadcast(intent); } } } } catch (IOException e) { e.printStackTrace(); } } } } @Override public void onDestroy() { super.onDestroy(); mHandler.removeCallbacks(heartBeatRunnable); mReadThread.release(); releaseLastSocket(mSocket); } }

4.2 Activity程式碼

package com.danxx.views;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.lang.ref.WeakReference;
import java.util.concurrent.Executors;

import danxx.library.socket.BackService;
import danxx.library.socket.IBackService;

/**
 * Created by daish on 2017/7/24.
 */

public class ActivityHeartSocket extends AppCompatActivity {

    private TextView mResultText;
    private EditText mEditText;
    private Intent mServiceIntent;

    private IBackService iBackService;

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iBackService = null;

        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBackService = IBackService.Stub.asInterface(service);
        }
    };

    class MessageBackReciver extends BroadcastReceiver {
        private WeakReference<TextView> textView;

        public MessageBackReciver(TextView tv) {
            textView = new WeakReference<TextView>(tv);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            TextView tv = textView.get();
            if (action.equals(BackService.HEART_BEAT_ACTION)) {
                if (null != tv) {
                    Log.i("danxx", "Get a heart heat");
                    tv.setText("Get a heart heat");
                }
            } else {
                Log.i("danxx", "Get a heart heat");
                String message = intent.getStringExtra("message");
                tv.setText("伺服器訊息:"+message);
            }
        };
    }
    private MessageBackReciver mReciver;

    private IntentFilter mIntentFilter;

    private LocalBroadcastManager mLocalBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_heart_socket);

        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);

        mResultText = (TextView) findViewById(R.id.resule_text);
        mEditText = (EditText) findViewById(R.id.content_edit);

        mReciver = new MessageBackReciver(mResultText);

        mServiceIntent = new Intent(this, BackService.class);

        mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(BackService.HEART_BEAT_ACTION);
        mIntentFilter.addAction(BackService.MESSAGE_ACTION);

    }

    @Override
    protected void onStart() {
        super.onStart();
        mLocalBroadcastManager.registerReceiver(mReciver, mIntentFilter);
        bindService(mServiceIntent, conn, BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(conn);
        mLocalBroadcastManager.unregisterReceiver(mReciver);
    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.send:
                String content = mEditText.getText().toString();
                try {
                    boolean isSend = iBackService.sendMessage(content);//Send Content by socket
                    Toast.makeText(this, isSend ? "success" : "fail",
                            Toast.LENGTH_SHORT).show();
                    mEditText.setText("");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;

            default:
                break;
        }
    }

}

5.效果圖

5.1 golang服務端

這裡寫圖片描述

5.2 Android客戶端

這裡寫圖片描述