一、背景
下载时,大量的文件写入,会影响其他操作以及带宽,需要限制下载速度
二、实现方案
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()