WebSocket连接(含重连机制)
首先大致的了解一下,我们为什么有时会用webSocket而不去用Socket呢?这是因为WebSocket是一个应用层协议。很多的东西都是规定好了,我们直接可以按照它的规定来用就可以了。而Socket是传输层和应用层的一个抽象层。里面有很多的东西我们得自己去规定。这样来说会显的相对的比较麻烦。
这里我们不可能自己去实现一个webSocket协议,所有我们运用到一个框架nv-websocket-client。
在我的项目中,我因为对接的服务器是通过webSocket返回给我推送的信息。所以,我这里就是一个很简单的逻辑,只需要通过webSocket去连接服务器,服务器不停的给我推送数据。在此过程中做好异常情况的处理(断网情况)时进行重新连接。
其实,在日常的开发中,我们在很多场景中也要实现这种重连的机制。比如说,一个用户登录流程
接下来,我们就简单的来看一下,具体的实现
1、引入webSocket的依赖
implementation 'com.neovisionaries:nv-websocket-client:2.2'
2、创建一个单例模式的WsManager,用来管理webSocket的全局调用
private WsManager() {
}
public static WsManager getInstance() {
if (mInstance == null) {
synchronized (WsManager.class) {
if (mInstance == null) {
mInstance = new WsManager();
}
}
}
return mInstance;
}
3、并在这个这个管理类里面添加建立连接的代码
public class WsManager {
private static WsManager mInstance;
private final String TAG = this.getClass().getSimpleName();
/**
* WebSocket config
*/
private static final int FRAME_QUEUE_SIZE = 5;
private static final int CONNECT_TIMEOUT = 5000;
//测试服默认地址
private static final String DEF_TEST_URL = "ws://10.7.5.88:8089/gs-robot/notice/device_status";
//正式服默认地址
private static final String DEF_RELEASE_URL = "ws://10.7.5.88:8089/gs-robot/notice/device_status";
private static final String DEF_URL = BuildConfig.DEBUG ? DEF_TEST_URL : DEF_RELEASE_URL;
private String url;
private WsStatus mStatus;
private WebSocket ws;
private WsListener mListener;
private WsManager() {
}
public static WsManager getInstance() {
if (mInstance == null) {
synchronized (WsManager.class) {
if (mInstance == null) {
mInstance = new WsManager();
}
}
}
return mInstance;
}
public void init() {
try {
/**
* configUrl其实是缓存在本地的连接地址
* 这个缓存本地连接地址是app启动的时候通过http请求去服务端获取的,
* 每次app启动的时候会拿当前时间与缓存时间比较,超过6小时就再次去服务端获取新的连接地址更新本地缓存
*/
String configUrl = "";
url = TextUtils.isEmpty(configUrl) ? DEF_URL : configUrl;
ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
.setFrameQueueSize(FRAME_QUEUE_SIZE)//设置帧队列最大值为5
.setMissingCloseFrameAllowed(false)//设置不允许服务端关闭连接却未发送关闭帧
.addListener(mListener = new WsListener())//添加回调监听
.connectAsynchronously();//异步连接
setStatus(WsStatus.CONNECTING);
Logger.t(TAG).d("第一次连接");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 继承默认的监听空实现WebSocketAdapter,重写我们需要的方法
* onTextMessage 收到文字信息
* onConnected 连接成功
* onConnectError 连接失败
* onDisconnected 连接关闭
*/
class WsListener extends WebSocketAdapter {
@Override
public void onTextMessage(WebSocket websocket, String text) throws Exception {
super.onTextMessage(websocket, text);
Logger.t(TAG).d(text);
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
throws Exception {
super.onConnected(websocket, headers);
Logger.t(TAG).d("连接成功");
setStatus(WsStatus.CONNECT_SUCCESS);
cancelReconnect();//连接成功的时候取消重连,初始化连接次数
}
@Override
public void onConnectError(WebSocket websocket, WebSocketException exception)
throws Exception {
super.onConnectError(websocket, exception);
Logger.t(TAG).d("连接错误");
setStatus(WsStatus.CONNECT_FAIL);
reconnect();//连接错误的时候调用重连方法
}
@Override
public void onDisconnected(WebSocket websocket,
WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer)
throws Exception {
super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
Logger.t(TAG).d("断开连接");
setStatus(WsStatus.CONNECT_FAIL);
reconnect();//连接断开的时候调用重连方法
}
}
private void setStatus(WsStatus status) {
this.mStatus = status;
}
private WsStatus getStatus() {
return mStatus;
}
public void disconnect() {
if (ws != null) {
ws.disconnect();
}
}
private Handler mHandler = new Handler();
private int reconnectCount = 0;//重连次数
private long minInterval = 3000;//重连最小时间间隔
private long maxInterval = 60000;//重连最大时间间隔
public void reconnect() {
if (!isNetConnect()) {
reconnectCount = 0;
Logger.t(TAG).d("重连失败网络不可用");
return;
}
//这里其实应该还有个用户是否登录了的判断 因为当连接成功后我们需要发送用户信息到服务端进行校验
//由于我们这里是个demo所以省略了
if (ws != null &&
!ws.isOpen() &&//当前连接断开了
getStatus() != WsStatus.CONNECTING) {//不是正在重连状态
reconnectCount++;
setStatus(WsStatus.CONNECTING);
long reconnectTime = minInterval;
if (reconnectCount > 3) {
url = DEF_URL;
long temp = minInterval * (reconnectCount - 2);
reconnectTime = temp > maxInterval ? maxInterval : temp;
}
Logger.t(TAG).d("准备开始第%d次重连,重连间隔%d -- url:%s", reconnectCount, reconnectTime, url);
mHandler.postDelayed(mReconnectTask, reconnectTime);
}
}
private Runnable mReconnectTask = new Runnable() {
@Override
public void run() {
try {
ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
.setFrameQueueSize(FRAME_QUEUE_SIZE)//设置帧队列最大值为5
.setMissingCloseFrameAllowed(false)//设置不允许服务端关闭连接却未发送关闭帧
.addListener(mListener = new WsListener())//添加回调监听
.connectAsynchronously();//异步连接
} catch (IOException e) {
e.printStackTrace();
}
}
};
private void cancelReconnect() {
reconnectCount = 0;
mHandler.removeCallbacks(mReconnectTask);
}
private boolean isNetConnect() {
ConnectivityManager connectivity = (ConnectivityManager) WsApplication.getContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo info = connectivity.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
// 当前网络是连接的
if (info.getState() == NetworkInfo.State.CONNECTED) {
// 当前所连接的网络可用
return true;
}
}
}
return false;
}
}
public enum WsStatus {
CONNECT_SUCCESS,//连接成功
CONNECT_FAIL,//连接失败
CONNECTING;//正在连接
}
上面的init()代表的就是建立连接的地方。里面添加了一个WsListener监听,是用来监听我们所需要的方法。
同时我们维护着自己的一些连接状态。在reconnect()(重连)方法中,我们在连接断开,并且不是重连状态的情况下,进行重连操作。这里定义了一个最小重连时间间隔min和一个最大重连时间间隔max,当重连次数小于等于3次的时候,都是以最小的重连间隔时间去尝试重连。当重连次数大于3次的时候,我们将重连地址替换成默认地址DEF_URL,将重连时间间隔min*(重连次数-2)递增最大不超过max。还有就是在重连的时候,去判断当前的网络状态是否可用
4、对于可用网络切换这里通过广播的监听来实现重连的
public class NetStatusReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
// 获取网络连接管理器
ConnectivityManager connectivityManager
= (ConnectivityManager) WsApplication.getContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取当前网络状态信息
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info != null && info.isAvailable()) {
Logger.t("WsManager").d("监听到可用网络切换,调用重连方法");
WsManager.getInstance().reconnect();//wify 4g切换重连websocket
}
}
}
5、应用回到前台情况的重连
通过Application.ActivityLifecycleCallbacks实现app前后台切换监听
**
* 前后台切换的监听
*/
public class ForegroundCallbacks implements Application.ActivityLifecycleCallbacks {
public static final long CHECK_DELAY = 600;
public static final String TAG = ForegroundCallbacks.class.getName();
private static ForegroundCallbacks instance;
private boolean foreground = false, paused = true;
private Handler handler = new Handler();
private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
private Runnable check;
public static ForegroundCallbacks init(Application application) {
if (instance == null) {
instance = new ForegroundCallbacks();
application.registerActivityLifecycleCallbacks(instance);
}
return instance;
}
public static ForegroundCallbacks get(Application application) {
if (instance == null) {
init(application);
}
return instance;
}
public static ForegroundCallbacks get(Context ctx) {
if (instance == null) {
Context appCtx = ctx.getApplicationContext();
if (appCtx instanceof Application) {
init((Application) appCtx);
}
throw new IllegalStateException(
"Foreground is not initialised and " +
"cannot obtain the Application object");
}
return instance;
}
public static ForegroundCallbacks get() {
return instance;
}
public boolean isForeground() {
return foreground;
}
public boolean isBackground() {
return !foreground;
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
@Override
public void onActivityResumed(Activity activity) {
paused = false;
boolean wasBackground = !foreground;
foreground = true;
if (check != null)
handler.removeCallbacks(check);
if (wasBackground) {
for (Listener l : listeners) {
try {
l.onBecameForeground();
} catch (Exception exc) {
}
}
} else {
}
}
@Override
public void onActivityPaused(Activity activity) {
paused = true;
if (check != null)
handler.removeCallbacks(check);
handler.postDelayed(check = new Runnable() {
@Override
public void run() {
if (foreground && paused) {
foreground = false;
for (Listener l : listeners) {
try {
l.onBecameBackground();
} catch (Exception exc) {
}
}
} else {
}
}
}, CHECK_DELAY);
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
public interface Listener {
public void onBecameForeground();
public void onBecameBackground();
}
}
6、最后在Application中初始化监听,当应用回到前台的时候尝试重连
public class WsApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initAppStatusListener();
}
private void initAppStatusListener() {
ForegroundCallbacks.init(this).addListener(new ForegroundCallbacks.Listener() {
@Override
public void onBecameForeground() {
Logger.t("WsManager").d("应用回到前台调用重连方法");
WsManager.getInstance().reconnect();
}
@Override
public void onBecameBackground() {
}
});
}
}
到这里连接的建立和重连讲完了,还剩客户端发送请求和服务端主动通知消息.下一次在说这个。
源代码demoWebSocketReconnection