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()
相关推荐
严同学正在努力19 分钟前
从备份到恢复:我用 30 分钟恢复了误删的核心业务表
android·java·数据库·ai
梦想三三1 小时前
LangChain Output Parser 实战:从字符串到结构化数据的完整指南
android·服务器·langchain·github·uv
爱笑鱼3 小时前
Binder(八):远端进程死了,BinderProxy 为什么还能收到通知?
android
Android-Flutter3 小时前
Kotlin 冷流与热流详解
android·kotlin
zhangphil3 小时前
Android ContentProvider/ContentResolver读图片,跨进程 IPC慢
android
yueqc14 小时前
Android .so 文件压缩
android·so·包体积
龚礼鹏4 小时前
深入解析 Android Automotive (AAOS) 启动流程与 CarService 核心机制(基于 Android 16 最新源码视角)
android
rosmis4 小时前
agent各指标定义
android·java·开发语言
风样滴男人哟6 小时前
PHP特性之反射类ReflectionClass机制
android·开发语言·php
小孔龙6 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化