多场景 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("长连接已建立")
                this@MainActivity.webSocket = 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请求和文件下载等多种网络需求。

相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952718 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android