多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="#0f1c2e"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Multi-Client OkHttp Manager"
        android:textSize="24sp"
        android:textColor="#4fc3f7"
        android:textStyle="bold"
        android:gravity="center"
        android:layout_marginBottom="24dp"/>

    <Button
        android:id="@+id/btnStartLongConnection"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动长连接"
        android:textColor="#ffffff"
        android:backgroundTint="#2196F3"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btnStopLongConnection"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止长连接"
        android:textColor="#ffffff"
        android:backgroundTint="#f44336"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btnSendHttpRequest"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送API请求"
        android:textColor="#ffffff"
        android:backgroundTint="#4CAF50"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btnDownloadFile"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="下载文件"
        android:textColor="#ffffff"
        android:backgroundTint="#FF9800"
        android:layout_marginBottom="16dp"/>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:progressTint="#4fc3f7"
        android:layout_marginBottom="16dp"/>

    <TextView
        android:id="@+id/tvLogs"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#1e3250"
        android:textColor="#e0f7fa"
        android:textSize="14sp"
        android:padding="8dp"
        android:scrollbars="vertical"
        android:scrollbarStyle="outsideOverlay"
        android:scrollbarThumbVertical="@android:color/darker_gray"/>

</LinearLayout>
Kotlin 复制代码
// OkHttpClientManager.kt
import android.content.Context
import android.util.Log
import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit

object OkHttpClientManager {

    // 长连接客户端 (WebSocket)
    val longConnectionClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .pingInterval(20, TimeUnit.SECONDS) // 20秒心跳
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(0, TimeUnit.SECONDS) // 无超时限制
            .writeTimeout(0, TimeUnit.SECONDS)
            .connectionPool(ConnectionPool(2, 10, TimeUnit.MINUTES))
            .addInterceptor(LongConnectionInterceptor())
            .build()
    }

    // 普通HTTP请求客户端
    val httpClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(15, TimeUnit.SECONDS)
            .addInterceptor(LoggingInterceptor())
            .addInterceptor(AuthInterceptor())
            .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))
            .build()
    }

    // 文件下载客户端
    val downloadClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(300, TimeUnit.SECONDS) // 5分钟超时
            .writeTimeout(300, TimeUnit.SECONDS)
            .addInterceptor(ProgressInterceptor())
            .connectionPool(ConnectionPool(3, 10, TimeUnit.MINUTES))
            .build()
    }

    // 长连接拦截器 - 添加设备ID等
    class LongConnectionInterceptor : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request().newBuilder()
                .addHeader("Connection", "Keep-Alive")
                .addHeader("Device-ID", "HW-12345")
                .build()
            return chain.proceed(request)
        }
    }

    // 日志拦截器
    class LoggingInterceptor : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
            Log.d("HTTP_Request", "URL: ${request.url}")
            return chain.proceed(request)
        }
    }

    // 认证拦截器
    class AuthInterceptor : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request().newBuilder()
                .addHeader("Authorization", "Bearer your_token_here")
                .build()
            return chain.proceed(request)
        }
    }

    // 进度拦截器
    class ProgressInterceptor : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            val originalResponse = chain.proceed(chain.request())
            return originalResponse.newBuilder()
                .body(ProgressResponseBody(originalResponse.body))
                .build()
        }
    }

    // 清理所有客户端资源
    fun cleanup() {
        longConnectionClient.dispatcher.executorService.shutdown()
        httpClient.dispatcher.executorService.shutdown()
        downloadClient.dispatcher.executorService.shutdown()
        
        longConnectionClient.connectionPool.evictAll()
        httpClient.connectionPool.evictAll()
        downloadClient.connectionPool.evictAll()
    }
}

// 进度响应体
class ProgressResponseBody(
    private val responseBody: ResponseBody?
) : ResponseBody() {
    private var bufferedSource: BufferedSource? = null
    var progressListener: ((bytesRead: Long, contentLength: Long) -> Unit)? = null

    override fun contentType(): MediaType? = responseBody?.contentType()
    override fun contentLength(): Long = responseBody?.contentLength() ?: 0L

    override fun source(): BufferedSource {
        if (bufferedSource == null) {
            bufferedSource = responseBody?.source()?.let { source ->
                object : ForwardingSource(source) {
                    var totalBytesRead = 0L

                    override fun read(sink: Buffer, byteCount: Long): Long {
                        val bytesRead = super.read(sink, byteCount)
                        totalBytesRead += if (bytesRead != -1L) bytesRead else 0
                        progressListener?.invoke(totalBytesRead, contentLength())
                        return bytesRead
                    }
                }
            }?.buffer()
        }
        return bufferedSource ?: throw IllegalStateException("BufferedSource is null")
    }
}
Kotlin 复制代码
// MainActivity.kt
import android.os.Bundle
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import okhttp3.*
import okhttp3.WebSocket
import java.io.File
import java.io.IOException

class MainActivity : AppCompatActivity() {

    private lateinit var tvLogs: TextView
    private lateinit var progressBar: ProgressBar
    private var webSocket: WebSocket? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvLogs = findViewById(R.id.tvLogs)
        progressBar = findViewById(R.id.progressBar)

        findViewById<Button>(R.id.btnStartLongConnection).setOnClickListener {
            startLongConnection()
        }

        findViewById<Button>(R.id.btnStopLongConnection).setOnClickListener {
            stopLongConnection()
        }

        findViewById<Button>(R.id.btnSendHttpRequest).setOnClickListener {
            sendHttpRequest()
        }

        findViewById<Button>(R.id.btnDownloadFile).setOnClickListener {
            downloadFile()
        }
    }

    private fun startLongConnection() {
        addLog("启动长连接...")
        
        val request = Request.Builder()
            .url("ws://your-hardware-ip:8080/ws") // 替换为实际硬件地址
            .build()

        val listener = object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                addLog("长连接已建立")
                [email protected] = webSocket
                webSocket.send("Hello, Hardware!")
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                addLog("收到消息: $text")
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                addLog("连接关闭: $reason")
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                addLog("连接失败: ${t.message}")
            }
        }

        OkHttpClientManager.longConnectionClient.newWebSocket(request, listener)
    }

    private fun stopLongConnection() {
        webSocket?.close(1000, "用户关闭连接")
        addLog("长连接已关闭")
    }

    private fun sendHttpRequest() {
        addLog("发送API请求...")
        
        val request = Request.Builder()
            .url("https://api.example.com/data") // 替换为实际API地址
            .build()

        OkHttpClientManager.httpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                addLog("请求失败: ${e.message}")
            }

            override fun onResponse(call: Call, response: Response) {
                val body = response.body?.string() ?: "Empty response"
                addLog("API响应: ${body.take(200)}...")
            }
        })
    }

    private fun downloadFile() {
        addLog("开始下载文件...")
        progressBar.visibility = ProgressBar.VISIBLE
        
        val request = Request.Builder()
            .url("https://example.com/largefile.zip") // 替换为实际文件URL
            .build()

        val progressListener = { bytesRead: Long, contentLength: Long ->
            val progress = (bytesRead.toFloat() / contentLength * 100).toInt()
            runOnUiThread {
                progressBar.progress = progress
                if (progress >= 100) {
                    progressBar.visibility = ProgressBar.GONE
                }
            }
        }

        // 自定义带进度监听的响应体
        val progressResponseBody = ProgressResponseBody(null).apply {
            this.progressListener = progressListener
        }

        OkHttpClientManager.downloadClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                addLog("下载失败: ${e.message}")
                progressBar.visibility = ProgressBar.GONE
            }

            override fun onResponse(call: Call, response: Response) {
                try {
                    val body = response.body
                    if (body != null) {
                        // 保存文件到本地
                        val file = File(getExternalFilesDir(null), "downloaded_file.zip")
                        file.outputStream().use { output ->
                            body.byteStream().copyTo(output)
                        }
                        addLog("文件下载完成: ${file.absolutePath}")
                    } else {
                        addLog("下载失败: 响应体为空")
                    }
                } catch (e: Exception) {
                    addLog("保存文件出错: ${e.message}")
                } finally {
                    progressBar.visibility = ProgressBar.GONE
                }
            }
        })
    }

    private fun addLog(message: String) {
        runOnUiThread {
            tvLogs.append("$message\n")
            // 自动滚动到底部
            val scrollAmount = tvLogs.layout.getLineTop(tvLogs.lineCount) - tvLogs.height
            tvLogs.scrollTo(0, if (scrollAmount > 0) scrollAmount else 0)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 清理所有网络资源
        OkHttpClientManager.cleanup()
    }
}

多客户端配置说明

1. 长连接客户端 (WebSocket)

  • 配置特点:

    • 心跳机制 (20秒间隔)

    • 无读写超时限制

    • 专用连接池 (2个连接/10分钟)

    • 自定义设备ID头部

  • 适用场景:

    • 实时硬件控制指令

    • 设备状态监控

    • 即时消息推送

2. 普通HTTP客户端

  • 配置特点:

    • 15秒超时设置

    • 认证拦截器 (Bearer Token)

    • 日志记录

    • 标准连接池 (5个连接/5分钟)

  • 适用场景:

    • RESTful API请求

    • 数据获取与提交

    • 常规网络操作

3. 文件下载客户端

  • 配置特点:

    • 长超时设置 (5分钟)

    • 进度监听拦截器

    • 大文件下载优化

    • 专用连接池 (3个连接/10分钟)

  • 适用场景:

    • 大文件下载

    • 固件升级包下载

    • 媒体文件传输

设计优势

  1. 资源隔离:每个客户端使用独立的连接池和线程池,避免相互干扰

  2. 性能优化:针对不同场景优化超时和连接参数

  3. 生命周期管理:统一清理机制防止资源泄漏

  4. 可扩展性:通过拦截器机制灵活添加功能

  5. 进度反馈:文件下载提供实时进度更新

  6. 错误处理:完善的异常处理机制

使用建议

  1. 长连接管理

    • 在Activity的onResume中启动连接

    • 在onPause中关闭连接以节省资源

    • 实现自动重连机制

  2. 文件下载

    • 添加断点续传功能

    • 支持后台下载服务

    • 添加下载队列管理

  3. 安全增强

    • 为硬件通信添加SSL/TLS加密

    • 实现证书锁定 (Certificate Pinning)

    • 添加请求签名验证

这个解决方案提供了完整的多种客户端管理实现,能够满足Android应用中与硬件通信、API请求和文件下载等多种网络需求。

相关推荐
用户2018792831671 小时前
Android 核心大管家 ActivityManagerService (AMS)
android
春马与夏2 小时前
Android自动化AirScript
android·运维·自动化
键盘歌唱家3 小时前
mysql索引失效
android·数据库·mysql
webbin4 小时前
Compose @Immutable注解
android·android jetpack
无知的前端4 小时前
Flutter开发,GetX框架路由相关详细示例
android·flutter·ios
玲小珑4 小时前
Auto.js 入门指南(十二)网络请求与数据交互
android·前端
webbin5 小时前
Compose 副作用
android·android jetpack
whysqwhw5 小时前
Dokka 插件系统与 Android 文档生成技术全解
android
橙子199110166 小时前
ActionBar 和 Toolbar
android
纳于大麓6 小时前
Kotlin基础语法五
android·开发语言·kotlin