通过协程的方式下载文件

首先构建Retrofit:

1
2
3
4
5
6
7
OkHttpClient client = new OkHttpClient.Builder()
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("www.baidu.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
1
2
3
4
5
6
interface DownLoadService {
// 下载文件 fileUrl:文件的网络路径
@Streaming
@GET
suspend fun downloadFileWithDynamicUrlAsync(@Url fileUrl: String?): Response<ResponseBody>
}

进行网络请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var app_dir = AppCache.getContext().getExternalFilesDir("")?.absolutePath
var IMAGE_DOWNLOAD_DIR = "$IMAGE_BASE_DIR/download/" // 原图保存目录
// downloadPath 文件下载地址
fun downLoad(downloadPath:String){
// 首先需要判断保存图片的目录存不存在 不存在创建
var fileRoot = File(IMAGE_DOWNLOAD_DIR)
if (!fileRoot.exists()) {
fileRoot.mkdirs()
}
lifecycleScope.launch {
var responseBoy = retrofit.create(DownLoadService::class.java).downService.downloadFileWithDynamicUrlAsync(downloadPath)
/**到这里我们就已经可以获取到对应文件的所有数据了
val body = responseBoy.body() ?: throw RuntimeException("下载出错")
//文件总长度
val length = body.contentLength()
//文件minetype
val contentType = body.contentType()
val ios = body.byteStream()
*/
// 我们需要读取请求得到的字节数据 然后写入文件
dowload(
this@PictureDetailsActivity,
responseBoy
) {
setFileName = {
fileName //如果在下载文件前知道文件的类型的话 可以直接在这里写上 **.jpg 或者 **.txt 记得一定要写明他的类型
}
success {url->
ImageView.setImageURI(FileUtil.getUrl(url.path.toString())
}
error {
Toast.makeText(this@PictureDetailsActivity, "原图下载失败", Toast.LENGTH_SHORT).show()
}
}.startDowload()
}

}

写流读取工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package com.chat.module_chat.util

import android.content.ContentResolver
import okhttp3.ResponseBody
import retrofit2.Response

/**
* @Author Yu
* @Date 2021/6/1 8:59
* @Description TODO
*/
import android.content.Context
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.webkit.MimeTypeMap
import com.lin.baselib.util.TUIKitConstants
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withContext
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.MediaType

/**
* Created by jingzz on 2020/7/9.
*/
typealias DOWLOAD_ERROR = (Throwable) -> Unit
typealias DOWLOAD_PROCESS = (downloadedSize: Long, length: Long, process: Float) -> Unit
typealias DOWLOAD_SUCCESS = (uri: Uri) -> Unit

public suspend fun dowload(
context: Context,
response: Response<ResponseBody>,
block: DowloadBuild.() -> Unit
): DowloadBuild {
val build = DowloadBuild(response, context)
build.block()
return build
}

public class DowloadBuild(val response: Response<ResponseBody>, var mContext: Context) {
private var error: DOWLOAD_ERROR = {} //错误贺词
private var process: DOWLOAD_PROCESS = { downloadedSize, filsLength, process -> } //进度
private var success: DOWLOAD_SUCCESS = {} //下载完成
private val context: Context = mContext //全局context
var setUri: () -> Uri? = { null } //设置下载的uri
var setFileName: () -> String? = { null } //设置文件名

fun process(process: DOWLOAD_PROCESS) {
this.process = process
}

fun error(error: DOWLOAD_ERROR) {
this.error = error
}

fun success(success: DOWLOAD_SUCCESS) {
this.success = success
}

suspend fun startDowload() {
withContext(Dispatchers.Main) {
//使用流获取下载进度
flow.flowOn(Dispatchers.IO)
.collect {
when (it) {
is DowloadStatus.DowloadErron -> error(it.t)
is DowloadStatus.DowloadProcess -> process(
it.currentLength,
it.length,
it.process
)
is DowloadStatus.DowloadSuccess -> success(it.uri)
}
}
}
}

val flow = flow<DowloadStatus> {
try {
val body = response.body() ?: throw RuntimeException("下载出错")
//文件总长度
val length = body.contentLength()
//文件minetype
val contentType = body.contentType()
val ios = body.byteStream()
var uri: Uri? = null
var file: File? = null
val ops = kotlin.run {
setUri()?.let {
//url转OutPutStream
uri = it
context.contentResolver.openOutputStream(it)
} ?: kotlin.run {
val fileName = setFileName() ?: kotlin.run {
//如果连文件名都不给,那就自己生成文件名
"${System.currentTimeMillis()}.${
contentType!!.subtype
}"
}
file =
File("${TUIKitConstants.IMAGE_DOWNLOAD_DIR}$fileName")
FileOutputStream(file)
}
}
//下载的长度
var currentLength: Int = 0
//写入文件
val bufferSize = 1024 * 8
val buffer = ByteArray(bufferSize)
val bufferedInputStream = BufferedInputStream(ios, bufferSize)
var readLength: Int = 0
while (bufferedInputStream.read(buffer, 0, bufferSize)
.also { readLength = it } != -1
) {
ops.write(buffer, 0, readLength)
currentLength += readLength
emit(
DowloadStatus.DowloadProcess(
currentLength.toLong(),
length,
currentLength.toFloat() / length.toFloat()
)
)
}
bufferedInputStream.close()
ops.close()
ios.close()
if (uri != null) {
emit(DowloadStatus.DowloadSuccess(uri!!))
} else if (file != null) {
emit(DowloadStatus.DowloadSuccess(Uri.fromFile(file)))
}

} catch (e: Exception) {
emit(DowloadStatus.DowloadErron(e))
}
}


}

sealed class DowloadStatus {
class DowloadProcess(val currentLength: Long, val length: Long, val process: Float) :
DowloadStatus()

class DowloadErron(val t: Throwable) : DowloadStatus()
class DowloadSuccess(val uri: Uri) : DowloadStatus()
}

这里使用了kotlin里面的flow 方法:

使用方法就是 创建一个流,然后在需要返回结果的使用掉用 emit() 就行。