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

news2025/12/15 1:34:37

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

<?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>

// 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")
    }
}

// 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请求和文件下载等多种网络需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2407852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…