Okhttp下载文件,带进度条

有大佬说下载大文件时这样做会导致OOM,我自己也没试过,那就先限定为只能下载一些小文件吧

一些坑

运行时发现日志报

Cleartext HTTP traffic not permitted
CLEARTEXT communication to * not permitted by network security policy

下面有解决方案

(可以跳过不看:根据安卓官方最新的网络安全配置
自API Level 28以后,对明文传输的支持不再是默认支持
因此在下载之前必须要做些准备工作
爆栈上的方案(有好几个方案),只写我自己用的
剩下方案自己的进去看:
https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted)

解决方案

1.创建res/xml/network_security_config.xml



    
        要访问url比如127.0.0.1
    

2.AndroidManifest.xml



    
    
        ...
    

文件下载

为了引起舒适,以下全用kotlin描述

1、创建类ProgressResponseBody
这个类用来对ResponseBody的包装

class ProgressResponseBody(
    //真正的ResponseBody
    private val responseBody: ResponseBody?,
    //回调接口
    private val progressListener: ProgressListener
): ResponseBody(){
    //读取响应体时的缓冲区
    private lateinit var bufferedSource: BufferedSource

    //响应体的总大小
    override fun contentLength(): Long {
        return responseBody!!.contentLength()
    }

    //contentType,这里没用到,但是都要重载
    override fun contentType(): MediaType? {
        return responseBody?.contentType()
    }
    
    /*上面bufferedSource的注释说到读取响应体时需要缓冲区
    就是在这里通过Okio.buffer(Source)获取的
    但是这样在读取响应体时,接口方法就没有调用
    解决方法是利用Okio提供的ForwardingSource可以用来包装Source
    有兴趣的可以看一下下面的ForwardingSource部分源码
    */
    override fun source(): BufferedSource {
        if(!this::bufferedSource.isInitialized)
            bufferedSource = Okio.buffer(source(responseBody!!.source()))
        return bufferedSource
    }

    //构建ForwardingSource
    private fun source(source: Source): Source{
        return object : ForwardingSource(source){
            private var totalBytesRead = 0L
            override fun read(sink: Buffer, byteCount: Long): Long {
                //读缓冲区的数据,得到读了多少字节
                val bytesRead = super.read(sink, byteCount)
                if(bytesRead != -1L)
                    totalBytesRead += bytesRead
                //接口回调
                progressListener.update(totalBytesRead,responseBody!!.contentLength(),bytesRead == -1L)
                return bytesRead
            }
        }
    }

    //回调接口
    interface ProgressListener{
        //参数名应该都写的很清楚是什么了
        fun update(bytesRead: Long, contentLength: Long, done: Boolean)
    }

ForwardingSource实现了Source接口

/** A {@link Source} which forwards calls to another. Useful for subclassing. */
public abstract class ForwardingSource implements Source {

  @Override public long read(Buffer sink, long byteCount) throws IOException {
    return delegate.read(sink, byteCount);
  }

  @Override public Timeout timeout() {
    return delegate.timeout();
  }

  @Override public void close() throws IOException {
    delegate.close();
  }

  @Override public String toString() {
    return getClass().getSimpleName() + "(" + delegate.toString() + ")";
  }
  //省略部分成员
}

2、创建回调对象

//进度监听器
val listener = object : ProgressResponseBody.ProgressListener {
    override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
        //计算百分比并更新ProgressBar
        val percent = (100 * bytesRead / contentLength)
        //progressBar内部切换线程
        progress.progress = percent.toInt()
        //textView手动先换线程
        runOnUiThread{
            textView.text = percent.toString()
        }
        //Log.d(TAG, "update 下载进度:$percent%")
    }
}

需要注意update是非UI线程的,progressBar可以直接设置progress是因为progressBar封装了线程的切换,而类似TextView就需要手动切换

3、 创建client时添加拦截器将responseBody替换成ProgressResponseBody

val client = OkHttpClient.Builder()
                .addNetworkInterceptor {
                    val response = it.proceed(it.request())
                    //换成ProgressResponseBody
                    response.newBuilder()
                        .body(ProgressResponseBody(response.body(), listener))
                        .build()
                }
                .build()

总结

1、准备好ProgressResponseBody类、回调接口
2、创建回调对象
3、利用Builder创建OkhttpClinet时添加拦截器,并把responseBody换成ProgressResponseBody
4、newCall
(本文省略利用Okhttp GET其它的基本步骤)

参考链接https://blog.csdn.net/a553181867/article/details/56292116#commentBox

你可能感兴趣的:(Okhttp下载文件,带进度条)