安卓websocket

99ANYc3cd6
预计阅读时长 37 分钟
位置: 首页 安卓 正文

目录

  1. WebSocket 是什么? (为什么需要它)
  2. Android 中使用 WebSocket 的几种方式
  3. 实战:使用 OkHttp 实现 WebSocket (推荐)
  4. 实战:使用 Java-WebSocket 库
  5. 生命周期管理与最佳实践
  6. 高级主题:心跳机制、重连策略
  7. 总结与对比

WebSocket 是什么?

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。

安卓websocket
(图片来源网络,侵删)
  • 传统 HTTP: 客户端发起请求,服务器响应,服务器不能主动向客户端推送数据(除非使用轮询或长轮询,效率低)。
  • WebSocket: 客户端和服务器之间建立一个持久连接,双方都可以随时向对方发送数据,就像一个电话,打通后,双方可以随时说话。

为什么在 Android 中使用 WebSocket?

  • 实时性: 对于需要实时更新的应用,如聊天室、股票行情、在线游戏、体育赛事直播等,WebSocket 是最佳选择。
  • 高效性: 避免了 HTTP 轮询带来的大量冗余请求和服务器压力,节省了带宽和电量。
  • 低延迟: 数据可以瞬间从服务器推送到客户端。

Android 中使用 WebSocket 的几种方式

主要有两种主流的实现方式:

使用成熟的网络库 (推荐)

这是最常见、最稳定的方式,它们封装了底层的细节,提供了简洁的 API,并且通常与 OkHttp、Retrofit 等库生态良好集成。

  • OkHttp: Square 公司出品,是 Android 事实上的标准网络库,从 OkHttp 3.5.0 开始,官方就内置了对 WebSocket 的支持,这是最推荐的方式,因为它与 OkHttp 的连接池、拦截器等无缝集成。
  • Java-WebSocket: 一个专门为 Java 设计的 WebSocket 客户端/服务器库,功能强大,API 也比较直观,也是一个不错的选择。

使用 Android 原生 API (不推荐)

Android 8.0 (API 26) 引入了 java.net.http 包,其中包含了 WebSocket 的客户端 API (WebSocket)。

安卓websocket
(图片来源网络,侵删)
  • 优点: 不需要引入第三方库。
  • 缺点:
    • 兼容性差: 仅支持 Android 8.0+,对于需要兼容旧版本的项目来说不可用。
    • API 复杂: 回调机制相对繁琐。
    • 生态不完善: 社区支持、文档和示例代码远不如第三方库。

除非你有非常特殊的需求,否则强烈建议使用 OkHttp 或 Java-WebSocket。


实战:使用 OkHttp 实现 WebSocket (推荐)

这是最主流、最优雅的方式。

步骤 1: 添加依赖

在你的 app/build.gradle 文件中添加 OkHttp 依赖:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0") // 请使用最新版本
}

步骤 2: 创建 WebSocket 客户端

OkHttpWebSocket 是一个简单的类,你需要手动管理它的生命周期(连接、发送、关闭)。

import okhttp3.*
import okio.ByteString
import java.util.concurrent.TimeUnit
class OkHttpWebSocketManager {
    private val client: OkHttpClient = OkHttpClient.Builder()
        .pingInterval(30, TimeUnit.SECONDS) // 设置心跳间隔
        .build()
    private var webSocket: WebSocket? = null
    // 启动 WebSocket 连接
    fun connect(url: String) {
        val request = Request.Builder()
            .url(url)
            .build()
        webSocket = client.newWebSocket(request, MyWebSocketListener())
    }
    // 发送消息
    fun sendMessage(message: String) {
        webSocket?.send(message)
    }
    // 关闭连接
    fun close() {
        webSocket?.close(1000, "Normal closure") // 1000 表示正常关闭
        // 重要:关闭后,OkHttp 的 Dispatcher 仍然持有这个 WebSocket,
        // 需要调用 cancel 来释放资源
        client.dispatcher.executorService.shutdown()
    }
    // OkWebSocket 的监听器
    private inner class MyWebSocketListener : WebSocketListener() {
        override fun onOpen(webSocket: WebSocket, response: Response) {
            // 连接成功
            println("WebSocket 连接成功!")
            // 连接成功后可以发送第一条消息
            webSocket.send("Hello, Server!")
        }
        override fun onMessage(webSocket: WebSocket, text: String) {
            // 收到文本消息
            println("收到消息: $text")
            // 在这里更新 UI,例如使用 runOnUiThread
            // activity.runOnUiThread { ... }
        }
        override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
            // 收到二进制消息
            println("收到二进制消息: ${bytes.hex()}")
        }
        override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
            // 连接正在关闭
            println("连接正在关闭: code=$code, reason=$reason")
            webSocket.close(1000, null) // 可以在这里确认关闭
        }
        override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
            // 连接已完全关闭
            println("连接已关闭: code=$code, reason=$reason")
        }
        override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
            // 连接失败
            println("WebSocket 连接失败: ${t.message}")
            // 在这里可以触发重连逻辑
        }
    }
}

步骤 3: 在 Activity/ViewModel 中使用

注意: 网络操作和回调都在子线程中,如果需要更新 UI,必须切回主线程。

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import okhttp3.Response
import okhttp3.WebSocket
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
    private lateinit var webViewManager: OkHttpWebSocketManager
    private lateinit var statusText: TextView
    private lateinit var sendButton: Button
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        statusText = findViewById(R.id.statusText)
        sendButton = findViewById(R.id.sendButton)
        webViewManager = OkHttpWebSocketManager()
        sendButton.setOnClickListener {
            webViewManager.sendMessage("这是来自客户端的消息!")
        }
        // 启动连接
        webViewManager.connect("ws://echo.websocket.org") // 这是一个测试服务器,会回显你发送的消息
    }
    override fun onDestroy() {
        super.onDestroy()
        // 在 Activity 销毁时,务必关闭 WebSocket 连接,防止内存泄漏
        webViewManager.close()
    }
}

实战:使用 Java-WebSocket 库

如果你不使用 OkHttp,或者更喜欢这个库的 API,它也是一个很好的选择。

步骤 1: 添加依赖

dependencies {
    implementation("org.java-websocket:Java-WebSocket:1.5.5") // 请使用最新版本
}

步骤 2: 创建客户端

import org.java_websocket.client.WebSocketClient
import org.java_websocket.handshake.ServerHandshake
import java.net.URI
class JavaWebSocketClient(uri: URI) : WebSocketClient(uri) {
    override fun onOpen(handshake: ServerHandshake?) {
        println("Java-WebSocket 连接成功!")
        // 连接成功后发送消息
        send("Hello from Java-WebSocket Client!")
    }
    override fun onMessage(message: String?) {
        println("收到消息: $message")
    }
    override fun onClose(code: Int, reason: String?, remote: Boolean) {
        println("连接关闭: code=$code, reason=$reason, remote=$remote")
    }
    override fun onError(ex: Exception?) {
        println("发生错误: ${ex?.message}")
    }
}

步骤 3: 使用客户端

// 在 Activity 或其他地方
val client = JavaWebSocketClient(URI("ws://echo.websocket.org"))
client.connect() // 注意:connect 是异步的

生命周期管理与最佳实践

这是 Android WebSocket 开发中最关键的部分。

问题:内存泄漏

WebSocket 连接是一个长期存在的任务,Activity/Fragment 销毁了,但 WebSocket 连接没有关闭,它仍然会持有 Activity 的引用(例如在回调中),导致 Activity 无法被回收,造成内存泄漏。

解决方案:

  1. onDestroy 中关闭连接: 这是最基本的一步,当你的 UI 组件销毁时,必须调用 close() 方法。

    override fun onDestroy() {
        super.onDestroy()
        webViewManager.close()
    }
  2. 使用 ViewModel (强烈推荐): ViewModel 专门用于保存和管理与 UI 相关的数据,当屏幕旋转导致 Activity 重建时,ViewModel 不会销毁,因此可以安全地持有 WebSocket 连接,避免因屏幕旋转而断开重连。

    class MyViewModel : ViewModel() {
        private val _messages = MutableLiveData<String>()
        val messages: LiveData<String> = _messages
        private val webSocketManager = OkHttpWebSocketManager()
        init {
            // 设置监听器,将消息更新到 LiveData
            webSocketManager.setListener { message ->
                _messages.postValue(message)
            }
            webSocketManager.connect("ws://your-server-url")
        }
        fun sendMessage(message: String) {
            webSocketManager.sendMessage(message)
        }
        // ViewModel 被清除时(当它对应的 Activity 真正销毁时)
        override fun onCleared() {
            super.onCleared()
            // 关闭 WebSocket 连接
            webSocketManager.close()
        }
    }
  3. 在 UI 层处理 LiveData: 在你的 ActivityFragment 中,观察 ViewModel 的 LiveData 来更新 UI。

    class MyActivity : AppCompatActivity() {
        private lateinit var viewModel: MyViewModel
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
            viewModel.messages.observe(this) { message ->
                statusText.text = message
            }
        }
    }

高级主题:心跳机制、重连策略

生产环境中的 WebSocket 需要更健壮的设计。

心跳机制

网络中间设备(如路由器、NAT)可能会因为长时间没有数据传输而断开连接,为了保持连接活跃,客户端需要定期向服务器发送一个简单的“心跳”消息({"type": "ping"}),服务器收到后回复一个“心跳应答”({"type": "pong"})。

  • OkHttp 的 pingInterval: OkHttp 已经内置了心跳机制,通过 client.newBuilder().pingInterval(...) 设置即可,它会自动在后台发送 Ping 帧(这是一种底层的 WebSocket 帧,不涉及业务逻辑),服务器会自动响应 Pong 帧。
  • 自定义心跳: 如果你的业务协议有自己的心跳格式,你可以在 onOpen 成功后,启动一个定时任务,定期发送自定义的心跳消息。
// 在 MyWebSocketListener 的 onOpen 中
private val heartBeatJob = CoroutineScope(Dispatchers.IO).launch {
    while (isActive) {
        delay(30000) // 30秒
        webSocket.send("{\"type\":\"heartbeat\"}")
    }
}
// 在 onClosed 或 onFailure 中取消
heartBeatJob.cancel()

重连策略

网络可能不稳定,连接随时可能断开,一个健壮的客户端应该具备自动重连的能力。

  1. 检测断开:WebSocketListeneronFailureonClosed 回调中,可以认为连接已经断开。
  2. 指数退避算法: 不要立即重连,这会给服务器带来压力,应该采用“指数退避”策略,即每次重连的等待时间逐渐增加(1s, 2s, 4s, 8s...),直到达到一个最大值。
  3. 实现逻辑:
    • onFailure 中,启动一个重连计数器。
    • 根据计数器计算等待时间。
    • 使用 HandlerCoroutine 在等待时间后再次调用 connect()
    • 如果重连成功,重置计数器。
    • 如果用户手动关闭(例如退出登录),则取消重连逻辑。
// 在 OkHttpWebSocketManager 中添加重连逻辑
private var reconnectAttempts = 0
private val maxReconnectAttempts = 5
private val handler = Handler(Looper.getMainLooper())
private fun scheduleReconnect() {
    if (reconnectAttempts >= maxReconnectAttempts) {
        println("重连次数已达上限,停止重连")
        return
    }
    val delay = (1000L * (1 shl reconnectAttempts.coerceAtMost(4))) // 指数退避,最大16秒
    println("准备第 ${reconnectAttempts + 1} 次重连,等待 ${delay}ms")
    handler.postDelayed({
        reconnectAttempts++
        connect(url) // 重新连接
    }, delay)
}
// 修改 onFailure
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
    println("WebSocket 连接失败: ${t.message}")
    scheduleReconnect() // 调度重连
}
// 在手动关闭或连接成功时重置状态
override fun onOpen(webSocket: WebSocket, response: Response) {
    println("WebSocket 连接成功!")
    reconnectAttempts = 0 // 重置重连次数
    // ...
}
override fun close() {
    handler.removeCallbacksAndMessages(null) // 取消所有待执行的重连任务
    // ... 原有关闭逻辑
}

总结与对比

特性 OkHttp Java-WebSocket Android 原生 API
易用性 (与 OkHttp 生态无缝集成) 中等 (API 直观但独立) 低 (API 繁琐)
稳定性 (成熟稳定,广泛使用) 高 (老牌库) 一般 (较新,社区支持少)
兼容性 (支持所有 Android 版本) (纯 Java,兼容性好) (仅 Android 8.0+)
生态集成 极高 (与 Retrofit, Moshi 等完美配合) 低 (独立生态)
功能 强大 (内置心跳、连接池) 强大 (功能全面) 基础
推荐度 ⭐⭐⭐⭐⭐ (首选) ⭐⭐⭐⭐ (备选) ⭐ (不推荐)

最终建议:

对于绝大多数 Android 项目,直接使用 OkHttp 是最简单、最可靠、最符合现代 Android 开发实践的选择,请务必结合 ViewModel 进行生命周期管理,并根据需要实现 心跳和重连 机制,以确保应用的健壮性。

-- 展开阅读全文 --
头像
苹果笔记本如何用U盘装系统?
« 上一篇 今天
苹果手机有ID锁怎么解除?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]