我正在参加「启航方案」

能够先阅览该系列上一篇文章

1、十分钟带你写一个Android Websocket 第一篇

1、前语

上一篇挖的坑重连战略,这也是WS的重中之重,让咱们一同来看下吧。(咕咕咕)一同学习、一同前进。假如写的欠好,或者有过错之处,恳请在谈论、私信、邮箱指出,万分感谢

2、介绍

  • 心跳测试法的优化:通过延迟心跳测试法,咱们能够确保测试成果的准确性。咱们认为,在树立长衔接后,接连屡次成功的短心跳测试能够极大地确保下一次心跳的环境是正常的。
  • 失败判别的改进:咱们采用了成功一次确定,失败接连累积确定的战略。这意味着成功是肯定的,只要在接连屡次失败后,咱们才会判定为失败的状况。
  • 临界值的奇妙处理:为了防止呈现临界值的问题,咱们运用略小于计算出的心跳值作为安稳心跳的标准。这样能够更好地确保心跳的安稳性,防止临界值带来的不确定性。
  • 动态调整的灵活性:即使在一次完好的智能心跳计算过程中,咱们没有找到最优的数值,也不必担心,由于咱们依然有机会进行校正和调整。这种动态调整的机制使得咱们能够不断优化心跳的体现,适应各种复杂的环境和改变。

4、开端

1)界说衔接状况

维护一个衔接状况变量,用于表明当时WebSocket的衔接状况,包括”CONNECTED”(已衔接)、”DISCONNECTED”(已断开衔接)和”RECONNECTING”(正在重连)等。

private var connectionStatus = ConnectionStatus.DISCONNECTED // 初始状况为断开衔接
enum class ConnectionStatus {
  CONNECTED,
  DISCONNECTED,
  RECONNECTING
}

2)预备一个循环

这儿我运用的是协程

private var reconnectJob: Job? = null
private fun reconnect() {
  if (reconnectJob?.isActive == true) {
    Log.e(TAG, "reconnect isActive")
    // 防止重复执行重连逻辑
    return
   }
  connectionStatus = ConnectionStatus.RECONNECTING
  reconnectJob = launch(Dispatchers.IO) {
    var retryCount = 0
    while (true) {
      if (connectionStatus != ConnectionStatus.CONNECTED) {
        // 进行重连
        connect()
        Log.e(TAG, "reconnect 进行重连")
        retryCount++
       } else {
        Log.e(TAG, "reconnect 衔接成功")
        // 衔接成功,退出重连循环
        break
       }
      delay(1000)
     }
   }
}

其中connect的内容如下

private fun connect() {
  Log.e(TAG, "connectWs")
  mWebSocket = wsHttpClient.newWebSocket(requestHttp, object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
      super.onOpen(webSocket, response)
      connectionStatus = ConnectionStatus.CONNECTED
      Log.e(TAG, "WS connection successful")
      // WebSocket 衔接树立
     }
   //**
    override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
      super.onMessage(webSocket, bytes)
      // 收到服务端发送来的 ByteString 类型音讯
     }
      //**
    override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
      super.onClosed(webSocket, code, reason)
      // WebSocket 衔接封闭
      if (code != 1000) {
        reconnect()
       }
     }
    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
      super.onFailure(webSocket, t, response)
      // 出错了
      reconnect()
     }
   })
}

为什么要code!=1000呢?

1000 indicates a normal closure, meaning that the purpose for
which the connection was established has been fulfilled.
//1000表明正常封闭,这意味着树立衔接的目的现已完成。

3)重连距离操控

其实到这儿,一个重连战略就现已完成了,这个重连战略运用1000ms的距离去频频的测验重连,频频重连对服务端和网络都有压力,所以咱们需求去优化,这儿咱们运用

指数退避算法

来进行重连距离操控,修正后

private fun reconnect() {
   //**省掉**
  reconnectJob = launch(Dispatchers.IO) {
    var retryCount = 0
    while (true) {
      //**省掉**
      delay(exponentialBackoffRetry(retryCount))
     }
   }
}
private fun exponentialBackoffRetry(retryCount: Int): Long {
  val maxRetryDelay = 10000L // 最大重试延迟时间(毫秒)
  val baseDelay = 500L // 根底延迟时间(毫秒)
  val multiplier = 1.1 // 延迟时间乘数
  val delay = baseDelay * multiplier.pow(retryCount.toDouble()).toLong()
  return minOf(delay, maxRetryDelay)
}

跟着重试次数的添加,重连距离时间逐步添加。

4)重连次数操控

假如一定规则下重连不上,不断去测验重连是没有意义的,为了防止无限重连,能够设置最大重连次数。

private val mMaxRetryCount = 5 // 最大重试次数
private fun reconnect() {
    //**省掉**
  reconnectJob = launch(Dispatchers.IO) {
    var retryCount = 0
    while (retryCount <= mMaxRetryCount) {
      //**省掉**
      if (retryCount == mMaxRetryCount) {
        Log.e(TAG, "last reconnect max retry")
        break
       }
      delay(exponentialBackoffRetry(retryCount))
     }
   }
}

咱们在到达最大的重连次数后,不再进行while循环

5)监听网络状况改变

在咱们中止重连时,说明此时客户端网络现已呈现了问题,咱们能够客户端监听网络状况的改变。当网络从头可用时,启动重连操作,确保在网络可用时及时进行重连。

咱们预备一个东西类存放和网络相关的方法

PS:appContext仅仅一个application context ,假如你测试需求,也能够运用Activity。可是可能会导致内存走漏

/**
 * 网络状况监测器
 * 留意:需求在恰当的时候取消注册以防止内存走漏
 */
object NetworkStatusMonitor {
  private val connectivityService by lazy {
    appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
   }
  private var isRegistered = false;
  var isAvailable = false
    private set
  var isUnAvailable = false
    private set
    get() {
      return !isAvailable
     }
  private val mCallback by lazy {
    object : ConnectivityManager.NetworkCallback() {
      override fun onAvailable(network: Network) {
        // 联网后开端重连
        isAvailable = true
        Log.e("", "onAvailable")
       }
​
      override fun onLost(network: Network) {
        // 断网中止重连
        isAvailable = false
        Log.e("", "onLost")
​
       }
     }
   }
​
  fun register(callback: ConnectivityManager.NetworkCallback) {
    connectivityService.registerNetworkCallback(
      NetworkRequest.Builder()
         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
         .build(), callback
     )
    if (!isRegistered) {
      isRegistered = true
      register(callback = mCallback)
     }
   }
​
​
  fun unregister(callback: ConnectivityManager.NetworkCallback) {
    connectivityService.unregisterNetworkCallback(callback)
   }
​
  /**
   * 判别网络是否衔接
   */
  fun isNetworkConnected(): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      val mNetworkInfo = connectivityService.activeNetworkInfo
      if (mNetworkInfo != null) {
        return mNetworkInfo.isAvailable
       }
     } else {
      val network = connectivityService.activeNetwork ?: return false
      val status = connectivityService.getNetworkCapabilities(network)
        ?: return false
      if (status.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
        return true
       }
     }
    return false
   }
​
  /**
   * 判别是否是WiFi衔接
   */
  fun isWifiConnected(context: Context?): Boolean {
    if (context != null) {
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        val mWiFiNetworkInfo = connectivityService
           .getNetworkInfo(ConnectivityManager.TYPE_WIFI)
        if (mWiFiNetworkInfo != null) {
          return mWiFiNetworkInfo.isAvailable
         }
       } else {
        val network = connectivityService.activeNetwork ?: return false
        val status = connectivityService.getNetworkCapabilities(network)
          ?: return false
        if (status.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
          return true
         }
       }
     }
    return false
   }
​
  /**
   * 判别是否是数据网络衔接
   */
  fun isMobileConnected(context: Context?): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
      val mMobileNetworkInfo = connectivityService
         .getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
      if (mMobileNetworkInfo != null) {
        return mMobileNetworkInfo.isAvailable
       }
     } else {
      val network = connectivityService.activeNetwork ?: return false
      val status = connectivityService.getNetworkCapabilities(network)
        ?: return false
      status.transportInfo
      if (status.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
        return true
       }
     }
    return false
   }
​
}

咱们在WsManager中预备一个网络状况改变的监听

private val mNetWorkCallback by lazy {
  object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
      // 联网后开端重连
      reconnect()
      Log.e(TAG, "onAvailable reconnect")
     }
​
    override fun onLost(network: Network) {
      // 断网中止重连
      if (!NetworkStatusMonitor.isNetworkConnected()) {
        cancelReconnect()
       }
      Log.e(TAG, "onLost cancelReconnect")
     }
   }
}
​

cancelReconnect想必你也知道是什么啦~就是取消协程

private fun cancelReconnect() {
  connectionStatus = ConnectionStatus.DISCONNECTED
  reconnectJob?.cancel()
}

在咱们openWs中,注册监听

fun openWs() {
  if (connectionStatus == ConnectionStatus.RECONNECTING || connectionStatus ==      ConnectionStatus.CONNECTED) {
    return
   }
  connect()
  NetworkStatusMonitor.register(mNetWorkCallback)
}

至此咱们一个重连战略就好了很多啦

十分钟带你写一个Android Websocket 第二篇

十分钟带你写一个Android Websocket 第二篇

当然你还能够考虑到客户端切换前后台来修正重连战略,不过就自己扩展吧~~

一个简单的WsManager就做好了,当然了还有许多东西都没有扩展好(由于天气太热了),大部分代码也都塞在一个类里边的。不过我相信各位还是好懂的,咕咕咕咕

5、下个华章

由于篇幅原因,咱们先到这,哈、下一篇还没想好写什么,假如你有想看的也能够在谈论区,或者私信给我,立刻写~咕咕咕。

假如您有任何疑问、对文章写的不满意、发现过错或者有更好的方法,欢迎在谈论、私信或邮件中提出,十分感谢您的支持。

6、感谢

  1. 校稿:ChatGpt
  2. 文笔优化:ChatGpt

代码可能有所收支,不过大差不差啦