有大佬说下载大文件时这样做会导致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
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">要访问url比如127.0.0.1</domain>
</domain-config>
</network-security-config>
2.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application
...
android:networkSecurityConfig="@xml/network_security_config"
...>
...
</application>
</manifest>
文件下载
为了引起舒适,以下全用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