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()
相关推荐
杉氧29 分钟前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏41 分钟前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧1 小时前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄2 小时前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭2 小时前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android
如此风景2 小时前
Kotlin Flow操作符学习
android·kotlin
plainGeekDev3 小时前
GreenDAO → Room
android·java·kotlin
weiggle4 小时前
第八篇:ViewModel + Compose——生产级状态管理实践
android
恋猫de小郭9 小时前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
plainGeekDev10 小时前
ButterKnife → ViewBinding
android·java·kotlin