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()
相关推荐
三少爷的鞋4 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)8 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码10 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程10 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓15 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer