Socket長連線Android端心跳機制實現
阿新 • • 發佈:2019-01-27
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;
}
}
}