Android OkHttp 下载限速方案实现

一、背景

下载时,大量的文件写入,会影响其他操作以及带宽,需要限制下载速度

二、实现方案

1、添加一个限制下载速度的请求头

RateLimitedInterceptor

Kotlin 复制代码
package com.vc.appbackup.net

/**
 * @Description : RateLimitedInterceptor 下载限速请求头
 */
import okhttp3.Interceptor
import okhttp3.Response
import okio.Buffer
import okio.BufferedSource
import okio.ForwardingSource
import okio.buffer
import java.io.IOException
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong

class RateLimitedInterceptor(
    private val maxBytesPerSecond: Long,//单位字节,即大B
    private val bucketSize: Long = maxBytesPerSecond // 令牌桶容量
):Interceptor{

    private val tokens = AtomicLong(bucketSize)
    private var lastRefillTime = System.nanoTime()

    @Synchronized
    private fun refillTokens() {
        val now = System.nanoTime()
        val elapsedNanos = now - lastRefillTime
        val tokensToAdd = (elapsedNanos * maxBytesPerSecond) / TimeUnit.SECONDS.toNanos(1)

        if (tokensToAdd > 0) {
            tokens.set(minOf(bucketSize, tokens.get() + tokensToAdd))
            lastRefillTime = now
        }
    }

    private fun consumeTokens(bytes: Long) {
        var remainingBytes = bytes

        while (remainingBytes > 0) {
            refillTokens()

            val available = tokens.get()
            if (available <= 0) {
                // 等待令牌补充
                try {
                    val waitTime = TimeUnit.NANOSECONDS.toMillis(
                        TimeUnit.SECONDS.toNanos(1) / maxBytesPerSecond
                    )
                    Thread.sleep(maxOf(1, waitTime))
                } catch (e: InterruptedException) {
                    Thread.currentThread().interrupt()
                    break
                }
                continue
            }

            val toConsume = minOf(available, remainingBytes)
            if (tokens.compareAndSet(available, available - toConsume)) {
                remainingBytes -= toConsume
            }
        }
    }

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalResponse = chain.proceed(chain.request())

        return originalResponse.newBuilder()
            .body(originalResponse.body?.let {
                TokenBucketResponseBody(it, this::consumeTokens)
            })
            .build()
    }
}

class TokenBucketResponseBody(
    private val originalBody: okhttp3.ResponseBody,
    private val consumeTokens: (Long) -> Unit
) : okhttp3.ResponseBody() {

    override fun contentType() = originalBody.contentType()

    override fun contentLength() = originalBody.contentLength()

    override fun source(): BufferedSource {
        return originalBody.source().let { source ->
            object : ForwardingSource(source) {
                @Throws(IOException::class)
                override fun read(sink: Buffer, byteCount: Long): Long {
                    val bytes = super.read(sink, byteCount)
                    if (bytes > 0) {
                        consumeTokens(bytes)
                    }
                    return bytes
                }
            }
        }.buffer()
    }

    override fun close() {
        originalBody.close()
    }
}
2、在请求中加入RateLimitedInterceptor拦截器

示例代码如下:

Kotlin 复制代码
   val client = OkHttpClient.Builder()
                .callTimeout(0, TimeUnit.SECONDS)
                .connectTimeout(120, TimeUnit.SECONDS)
                .readTimeout(120, TimeUnit.SECONDS)
                .writeTimeout(120, TimeUnit.SECONDS)
                .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
                .addInterceptor(RateLimitedInterceptor(DeviceUtil.getInstance().getMaxBytesPerSecond().toLong()))//添加限速拦截器
                .eventListener(object:EventListener() {
                     override fun callFailed (call: Call, ioe:IOException) {
                        
                     }
                }).build()
相关推荐
最好一梦2 分钟前
佳维视安卓工控机在堆垛机中的应用
android
传奇开心果编程37 分钟前
组合优先开发范式:从汉字造字原理到声明式UI组件组合思想
android·flutter·ios·swiftui·移动开发·harmonyos·android jetpack
腾讯端服务1 小时前
一码Duo端-Kuikly-iOS折叠屏新UI适配实践
android·ui·ios·kotlin·鸿蒙
T量子永生1 小时前
Android SISO 分布式编译集群配置方案
android·分布式·云计算·源代码管理
街头便衣2 小时前
OpenRouter周榜复算:164万亿token,输出2.55%,DeepSeek账单占1.4%
android·java·开发语言·人工智能·语言模型·开源
警醒与鞭策14 小时前
Codex桌面版与Claude桌面版使用设置
android·unity·ai·性能优化
传奇开心果17 小时前
【Compose Multiplatform跨端声明式UI实用技术】第19课:多平台部署与发布
android·kotlin
红尘伴蝶舞17 小时前
加密拦截器让抓包失效,我只能从 4KB 的 logcat 碎片里拼回 JSON
android·kotlin·ai编程
booboosui17 小时前
219 个还是 0 个:读不到微信界面时,我只加不删
android
Android打工仔17 小时前
Compose 中何时该用 padding,何时该用 offset
android·android jetpack