下面是一个完整的 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分钟)
-
-
适用场景:
-
大文件下载
-
固件升级包下载
-
媒体文件传输
-
设计优势
-
资源隔离:每个客户端使用独立的连接池和线程池,避免相互干扰
-
性能优化:针对不同场景优化超时和连接参数
-
生命周期管理:统一清理机制防止资源泄漏
-
可扩展性:通过拦截器机制灵活添加功能
-
进度反馈:文件下载提供实时进度更新
-
错误处理:完善的异常处理机制
使用建议
-
长连接管理:
-
在Activity的onResume中启动连接
-
在onPause中关闭连接以节省资源
-
实现自动重连机制
-
-
文件下载:
-
添加断点续传功能
-
支持后台下载服务
-
添加下载队列管理
-
-
安全增强:
-
为硬件通信添加SSL/TLS加密
-
实现证书锁定 (Certificate Pinning)
-
添加请求签名验证
-
这个解决方案提供了完整的多种客户端管理实现,能够满足Android应用中与硬件通信、API请求和文件下载等多种网络需求。