OkHttp 使用教程:从入门到精通(Kotlin)

教程来自Claude问答。

OkHttp 使用教程:从入门到精通(Kotlin)

先建立整体架构认知:

第一章:环境准备

添加依赖

kotlin

kotlin 复制代码
// build.gradle.kts
dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    // 可选:日志拦截器(开发调试必备)
    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}

OkHttpClient 的黄金法则

OkHttpClient 内部持有连接池和线程池,整个应用应只创建一个实例,用单例共享:

kotlin

kotlin 复制代码
// 推荐:应用级单例
object HttpClient {
    val instance: OkHttpClient = OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build()
}

// 错误示范:每次请求都 new 一个,会耗尽连接/线程资源
fun badPractice() {
    val client = OkHttpClient() // 每次 new ❌
    client.newCall(request).execute()
}

第二章:入门 --- GET 请求

2.1 同步 GET(阻塞,不能在 Android 主线程调用)

kotlin

kotlin 复制代码
val client = OkHttpClient()

fun getSync(url: String): String {
    val request = Request.Builder()
        .url(url)
        .build()

    // execute() 阻塞当前线程直到响应返回
    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) {
            throw IOException("请求失败: ${response.code}")
        }
        // response.body 只能读一次!use{} 会自动关闭
        return response.body!!.string()
    }
}

// 调用
val json = getSync("https://api.github.com/users/octocat")
println(json)

2.2 异步 GET(推荐方式,回调在 OkHttp 内部线程池执行)

kotlin

kotlin 复制代码
fun getAsync(url: String) {
    val request = Request.Builder()
        .url(url)
        .header("Accept", "application/json")        // 设置请求头
        .addHeader("X-Custom-Header", "my-value")    // 追加请求头
        .build()

    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            // 网络错误(无网、超时、DNS 解析失败等)
            println("请求失败: ${e.message}")
        }

        override fun onResponse(call: Call, response: Response) {
            response.use {  // 确保 body 被关闭,防止资源泄漏
                if (!it.isSuccessful) {
                    println("HTTP 错误: ${it.code}")
                    return
                }
                val body = it.body!!.string()
                println("响应: $body")
                // ⚠️ 注意:这里是子线程,更新 UI 需切换到主线程
            }
        }
    })
}

2.3 结合 Kotlin 协程(现代最佳实践)

kotlin

kotlin 复制代码
// 用扩展函数把回调包装成 suspend 函数
suspend fun OkHttpClient.getAsync(url: String): String {
    val request = Request.Builder().url(url).build()

    return suspendCancellableCoroutine { continuation ->
        val call = newCall(request)

        // 协程取消时自动取消 HTTP 请求
        continuation.invokeOnCancellation { call.cancel() }

        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                continuation.resumeWithException(e)
            }
            override fun onResponse(call: Call, response: Response) {
                response.use {
                    if (!it.isSuccessful) {
                        continuation.resumeWithException(
                            IOException("HTTP ${it.code}")
                        )
                        return
                    }
                    continuation.resume(it.body!!.string())
                }
            }
        })
    }
}

// 使用(在协程作用域中调用)
viewModelScope.launch {
    try {
        val result = client.getAsync("https://api.github.com/users/octocat")
        println(result)
    } catch (e: IOException) {
        println("出错: ${e.message}")
    }
}

实际项目中更推荐直接使用 Retrofit(底层是 OkHttp),它原生支持协程,不需要手动包装。


第三章:POST 请求与请求体

OkHttp 用 RequestBody 封装各种格式的请求体:

3.1 POST JSON

kotlin

kotlin 复制代码
// application/json; charset=utf-8
val JSON = "application/json".toMediaType()

fun postJson(url: String, jsonBody: String): String {
    val body = jsonBody.toRequestBody(JSON)

    val request = Request.Builder()
        .url(url)
        .post(body)
        .build()

    client.newCall(request).execute().use { response ->
        return response.body!!.string()
    }
}

// 调用示例
val result = postJson(
    url = "https://httpbin.org/post",
    jsonBody = """{"name": "Kotlin", "version": 2.0}"""
)

3.2 POST 表单(Form)

kotlin

kotlin 复制代码
fun postForm(url: String): String {
    val formBody = FormBody.Builder()
        .add("username", "alice")
        .add("password", "secret123")
        .addEncoded("token", "abc%2Fdef")  // 已编码的值用 addEncoded
        .build()

    val request = Request.Builder()
        .url(url)
        .post(formBody)
        .build()

    client.newCall(request).execute().use {
        return it.body!!.string()
    }
}

3.3 Multipart 上传文件

kotlin

kotlin 复制代码
fun uploadFile(url: String, file: File): String {
    val MEDIA_IMAGE = "image/jpeg".toMediaType()

    val multipartBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        // 普通表单字段
        .addFormDataPart("description", "我的头像")
        // 文件字段:字段名、文件名、文件体
        .addFormDataPart(
            name = "avatar",
            filename = file.name,
            body = file.asRequestBody(MEDIA_IMAGE)
        )
        .build()

    val request = Request.Builder()
        .url(url)
        .post(multipartBody)
        .build()

    client.newCall(request).execute().use {
        return it.body!!.string()
    }
}

3.4 流式上传(大文件,带进度)

kotlin

kotlin 复制代码
fun uploadWithProgress(
    url: String,
    file: File,
    onProgress: (Long, Long) -> Unit
): String {
    val fileBody = object : RequestBody() {
        override fun contentType() = "application/octet-stream".toMediaType()
        override fun contentLength() = file.length()

        override fun writeTo(sink: BufferedSink) {
            val total = file.length()
            var uploaded = 0L
            val buffer = ByteArray(8192)

            file.inputStream().use { input ->
                var read: Int
                while (input.read(buffer).also { read = it } != -1) {
                    sink.write(buffer, 0, read)
                    uploaded += read
                    onProgress(uploaded, total)
                }
            }
        }
    }

    val request = Request.Builder().url(url).post(fileBody).build()
    client.newCall(request).execute().use { return it.body!!.string() }
}

// 调用
uploadWithProgress(
    url = "https://example.com/upload",
    file = File("/sdcard/video.mp4"),
    onProgress = { done, total ->
        val pct = (done * 100 / total)
        println("上传进度: $pct%")
    }
)

第四章:进阶 --- 拦截器(Interceptor)

拦截器是 OkHttp 最核心的扩展机制,理解它相当于理解了 OkHttp 的灵魂。

4.1 日志拦截器(开发必备)

kotlin

kotlin 复制代码
val loggingInterceptor = HttpLoggingInterceptor { message ->
    println("[HTTP] $message")
}.apply {
    // NONE / BASIC / HEADERS / BODY
    level = HttpLoggingInterceptor.Level.BODY
}

val client = OkHttpClient.Builder()
    .addInterceptor(loggingInterceptor)  // 应用拦截器
    .build()

4.2 认证拦截器(自动附加 Token)

kotlin

kotlin 复制代码
class AuthInterceptor(private val tokenProvider: () -> String) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()

        // 在原请求上追加 Authorization 头,构造新请求
        val authenticatedRequest = originalRequest.newBuilder()
            .header("Authorization", "Bearer ${tokenProvider()}")
            .build()

        return chain.proceed(authenticatedRequest)
    }
}

// 使用
val client = OkHttpClient.Builder()
    .addInterceptor(AuthInterceptor { UserSession.getToken() })
    .build()

4.3 自动重试拦截器(含指数退避)

kotlin

kotlin 复制代码
class RetryInterceptor(
    private val maxRetries: Int = 3,
    private val initialDelayMs: Long = 500
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var retryCount = 0
        var lastException: IOException? = null

        while (retryCount <= maxRetries) {
            try {
                val response = chain.proceed(chain.request())

                // 5xx 服务端错误才重试,4xx 客户端错误不重试
                if (response.isSuccessful || response.code < 500) {
                    return response
                }
                response.close()
            } catch (e: IOException) {
                lastException = e
            }

            retryCount++
            if (retryCount <= maxRetries) {
                // 指数退避:500ms, 1000ms, 2000ms...
                val delay = initialDelayMs * (1L shl (retryCount - 1))
                println("第 $retryCount 次重试,等待 ${delay}ms")
                Thread.sleep(delay)
            }
        }

        throw lastException ?: IOException("重试 $maxRetries 次后失败")
    }
}

4.4 请求/响应计时拦截器

kotlin

kotlin 复制代码
class TimingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val startNs = System.nanoTime()

        val response = chain.proceed(request)

        val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
        println("${request.method} ${request.url} --- ${response.code} (${elapsedMs}ms)")

        return response
    }
}

第五章:缓存

OkHttp 内置基于 HTTP 标准协议(Cache-Control)的磁盘缓存:

kotlin

kotlin 复制代码
// 配置缓存:指定缓存目录 + 最大缓存大小
val cacheDir = File(context.cacheDir, "http_cache")
val cache = Cache(directory = cacheDir, maxSize = 50L * 1024 * 1024) // 50MB

val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

// --- 请求策略 ---

// 1. 优先使用缓存,缓存过期才请求网络
val request = Request.Builder()
    .url("https://api.example.com/data")
    .cacheControl(CacheControl.Builder()
        .maxAge(60, TimeUnit.SECONDS)   // 允许最多 60 秒的缓存
        .build())
    .build()

// 2. 强制走网络,不读缓存
val freshRequest = Request.Builder()
    .url("https://api.example.com/data")
    .cacheControl(CacheControl.FORCE_NETWORK)
    .build()

// 3. 强制走缓存,无网络时也能返回数据(适合离线模式)
val offlineRequest = Request.Builder()
    .url("https://api.example.com/data")
    .cacheControl(CacheControl.FORCE_CACHE)
    .build()

// 4. 网络可用时用网络,无网时用缓存(常用离线策略)
val smartRequest = Request.Builder()
    .url("https://api.example.com/data")
    .cacheControl(
        if (isNetworkAvailable()) CacheControl.FORCE_NETWORK
        else CacheControl.FORCE_CACHE
    )
    .build()

// 查看缓存命中情况
client.newCall(request).execute().use { response ->
    println("缓存响应: ${response.cacheResponse}")   // 非 null = 命中缓存
    println("网络响应: ${response.networkResponse}") // 非 null = 走了网络
}

第六章:HTTPS 与安全

6.1 自定义证书(内网/自签名证书)

kotlin

kotlin 复制代码
fun buildClientWithCustomCert(certInputStream: InputStream): OkHttpClient {
    val cf = CertificateFactory.getInstance("X.509")
    val cert = cf.generateCertificate(certInputStream)

    val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
        load(null, null)
        setCertificateEntry("ca", cert)
    }

    val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
        init(keyStore)
    }

    val sslContext = SSLContext.getInstance("TLS").apply {
        init(null, tmf.trustManagers, null)
    }

    return OkHttpClient.Builder()
        .sslSocketFactory(sslContext.socketFactory, tmf.trustManagers[0] as X509TrustManager)
        .build()
}

6.2 证书固定(Certificate Pinning,防止中间人攻击)

kotlin

kotlin 复制代码
// sha256/ 后面的值从服务器证书中提取
val certificatePinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // 备用
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

// 获取服务器 pin 值(调试用)
// 运行后在日志里找 "Certificate pinning failure",里面有正确的 sha256 值

第七章:WebSocket

kotlin

kotlin 复制代码
class WebSocketManager {
    private val client = OkHttpClient.Builder()
        .pingInterval(20, TimeUnit.SECONDS) // 每 20 秒 ping 一次,保持连接
        .build()

    private var webSocket: WebSocket? = null

    fun connect(url: String) {
        val request = Request.Builder().url(url).build()

        webSocket = client.newWebSocket(request, object : WebSocketListener() {

            override fun onOpen(webSocket: WebSocket, response: Response) {
                println("连接成功")
                webSocket.send("Hello Server!") // 发送文本
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                println("收到文本: $text")
            }

            override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
                println("收到二进制: ${bytes.hex()}")
            }

            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                println("服务端关闭: $code / $reason")
                webSocket.close(1000, null)
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                println("连接失败: ${t.message}")
                // 这里可以实现重连逻辑
            }
        })
    }

    fun send(message: String) = webSocket?.send(message)

    fun sendBytes(data: ByteArray) = webSocket?.send(data.toByteString())

    fun disconnect() = webSocket?.close(1000, "正常关闭")
}

第八章:精通 --- 连接池与性能调优

kotlin

kotlin 复制代码
val client = OkHttpClient.Builder()
    // 连接池:最多 10 个空闲连接,每个连接最长空闲 5 分钟
    .connectionPool(ConnectionPool(
        maxIdleConnections = 10,
        keepAliveDuration = 5,
        timeUnit = TimeUnit.MINUTES
    ))
    // 协议优先级:优先 HTTP/2,降级到 HTTP/1.1
    .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
    // 连接规格:TLS 1.2+ 和现代密码套件
    .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT))
    // DNS 解析:可自定义,实现 DNS-over-HTTPS 或负载均衡
    .dns { hostname ->
        // 先尝试 IPv6,再 IPv4(Happy Eyeballs 算法)
        Dns.SYSTEM.lookup(hostname)
    }
    // 调度器:控制最大并发请求数
    .dispatcher(Dispatcher().apply {
        maxRequests = 64          // 全局最大并发
        maxRequestsPerHost = 10   // 单个 host 最大并发(默认 5)
    })
    .build()

// 批量并发请求示例
fun fetchAllConcurrently(urls: List<String>): List<String> {
    val latch = CountDownLatch(urls.size)
    val results = Array<String?>(urls.size) { null }

    urls.forEachIndexed { index, url ->
        val request = Request.Builder().url(url).build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                results[index] = "Error: ${e.message}"
                latch.countDown()
            }
            override fun onResponse(call: Call, response: Response) {
                response.use { results[index] = it.body?.string() }
                latch.countDown()
            }
        })
    }

    latch.await(30, TimeUnit.SECONDS)
    return results.map { it ?: "" }
}

第九章:EventListener --- 全链路监控

这是 OkHttp 最高级的能力,可以精确追踪每个请求的每个阶段耗时:

kotlin

kotlin 复制代码
class MetricsEventListener : EventListener() {
    private val startNs = mutableMapOf<String, Long>()

    private fun mark(call: Call, tag: String) {
        startNs[tag] = System.nanoTime()
    }
    private fun elapsed(call: Call, tag: String): Long {
        val start = startNs[tag] ?: return -1
        return (System.nanoTime() - start) / 1_000_000
    }

    override fun callStart(call: Call) = mark(call, "call")
    override fun dnsStart(call: Call, domainName: String) = mark(call, "dns")
    override fun dnsEnd(call: Call, domainName: String, inetAddressList: List<InetAddress>) {
        println("DNS 解析: ${elapsed(call, "dns")}ms → $inetAddressList")
    }
    override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) = mark(call, "connect")
    override fun secureConnectStart(call: Call) = mark(call, "tls")
    override fun secureConnectEnd(call: Call, handshake: Handshake?) {
        println("TLS 握手: ${elapsed(call, "tls")}ms")
    }
    override fun connectEnd(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol?) {
        println("TCP 连接: ${elapsed(call, "connect")}ms,协议: $protocol")
    }
    override fun requestBodyStart(call: Call) = mark(call, "reqBody")
    override fun requestBodyEnd(call: Call, byteCount: Long) {
        println("请求体发送: ${elapsed(call, "reqBody")}ms,$byteCount bytes")
    }
    override fun responseBodyStart(call: Call) = mark(call, "resBody")
    override fun responseBodyEnd(call: Call, byteCount: Long) {
        println("响应体读取: ${elapsed(call, "resBody")}ms,$byteCount bytes")
    }
    override fun callEnd(call: Call) {
        println("总耗时: ${elapsed(call, "call")}ms")
    }
    override fun callFailed(call: Call, ioe: IOException) {
        println("请求失败: ${ioe.message},已耗时: ${elapsed(call, "call")}ms")
    }
}

val client = OkHttpClient.Builder()
    .eventListenerFactory(EventListener.Factory { MetricsEventListener() })
    .build()

总结:学习路径图

几个贯穿始终的关键原则:

OkHttpClient 全局单例,避免重建连接池。response.body 只能读一次,读完立即关闭(用 use{})。拦截器是横切逻辑的最优解,不要把认证、日志、重试散落在业务代码里。生产环境日志拦截器设为 BASICNONE,避免打印敏感信息。如果你的项目用到 Kotlin 协程,直接搭配 Retrofit 使用,OkHttp 作为底层传输层即可,不需要手动包装回调。

相关推荐
Yang-Never1 天前
OpenGL ES ->YUV图像基础知识
android·java·开发语言·kotlin·android studio
idealzouhu1 天前
【Kotlin】 数据流完全指南:冷流、热流与 Android 实战
android·开发语言·kotlin
常利兵1 天前
Android 字体字重设置:从XML到Kotlin的奇妙之旅
android·xml·kotlin
idealzouhu1 天前
【Kotlin】快速理解协程
kotlin
hnlgzb1 天前
Gemini:kotlin这几个类型有什么区别?类比java的文件,是怎样的?
java·开发语言·kotlin
hnlgzb1 天前
kotlin安卓app中,当一个类继承ViewModel类的时候,这个类是想干什么?
android·开发语言·kotlin
新镜1 天前
【Kotlin】StateFlow / MutableStateFlow只有值不相等时才会发射
kotlin
hnlgzb1 天前
kotlin类 继承android.app.Activity 和androidx.activity.ComponentActivity 有什么区别?
android·kotlin·androidx
alexhilton2 天前
Compose中的ContentScale:终极可视化指南
android·kotlin·android jetpack