Kotlin协程+Retrofit下载文件并实现进度监听
文章目录
1.前言
网上很多Retrofit
+RxJava
下载文件的功能,这里我使用kotlin+协程的方式使用一下,并且结合workmanager
使用下载
DownloadWorker.startDownload(
this@MainActivity,
"https://dldir1.qq.com/wework/work_weixin/wxwork_android_3.0.31.13637_100001.apk",
this@MainActivity.cacheDir.path,
"wxwork_android_3.apk"
)
2. 实现过程
1. 构建下载的ApiService
import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Streaming
import retrofit2.http.Url
interface ApiService {
@Streaming
@GET
suspend fun downloadFile(@Url fileUrl: String?): ResponseBody
}
2. 声明相关监听方法
typealias download_error = suspend (Throwable) -> Unit
typealias download_process = suspend (downloadedSize: Long, length: Long, progress: Float) -> Unit
typealias download_success = suspend (uri: File) -> Unit
3. 构建Retrofit,并且获取ApiService
import retrofit2.Retrofit
object HttpKit {
val retrofit = Retrofit.Builder()
.baseUrl("http://www.xxx.com")
.validateEagerly(true) //在开始的时候直接开始检测所有的方法
.build()
val apiService = retrofit.create(ApiService::class.java)
}
4. 定义返回实体
/**
* @author stj
* @Date 2021/10/28-16:42
* @Email 375105540@qq.com
*/
class HttpResult<out T> constructor(val value: Any?) {
val isSuccess: Boolean get() = value !is Failure && value !is Progress
val isFailure: Boolean get() = value is Failure
val isLoading : Boolean get() = value is Progress
fun exceptionOrNull(): Throwable? =
when (value) {
is Failure -> value.exception
else -> null
}
/*
.....代码省略
*/
companion object {
fun <T> success(value: T): HttpResult<T> =
HttpResult(value)
fun <T> failure(exception: Throwable): HttpResult<T> =
HttpResult(createFailure(exception))
fun <T> progress(currentLength: Long, length: Long, process: Float):HttpResult<T> =
HttpResult(createLoading(currentLength, length, process))
}
data class Failure(val exception: Throwable)
data class Progress(val currentLength: Long, val length: Long, val process: Float)
}
private fun createFailure(exception: Throwable): HttpResult.Failure =
HttpResult.Failure(exception)
private fun createLoading(currentLength: Long, length: Long, process: Float) =
HttpResult.Progress(currentLength, length, process)
inline fun <R, T> HttpResult<T>.fold(
onSuccess: (value: T) -> R,
onLoading:(loading: HttpResult.Progress) ->R,
onFailure: (exception: Throwable?) -> R
): R {
return when {
isFailure -> {
onFailure(exceptionOrNull())
}
isLoading -> {
onLoading(value as HttpResult.Progress)
}
else -> {
onSuccess(value as T)
}
}
}
5. 构建下载请求,已经处理
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import me.shetj.download.http.HttpKit.apiService
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
object KCHttpV2 {
@JvmOverloads
suspend fun download(
url: String, outputFile: String,
onError: download_error = {},
onProcess: download_process = { _, _, _ -> },
onSuccess: download_success = { }
) {
flow {
try {
val body = apiService.downloadFile(url)
val contentLength = body.contentLength()
val inputStream = body.byteStream()
val file = File(outputFile)
val outputStream = FileOutputStream(file)
var currentLength = 0
val bufferSize = 1024 * 8
val buffer = ByteArray(bufferSize)
val bufferedInputStream = BufferedInputStream(inputStream, bufferSize)
var readLength: Int
while (bufferedInputStream.read(buffer, 0, bufferSize)
.also { readLength = it } != -1
) {
outputStream.write(buffer, 0, readLength)
currentLength += readLength
emit(
HttpResult.progress(
currentLength.toLong(),
contentLength,
currentLength.toFloat() / contentLength.toFloat()
)
)
}
bufferedInputStream.close()
outputStream.close()
inputStream.close()
emit(HttpResult.success(file))
} catch (e: Exception) {
emit(HttpResult.failure<File>(e))
}
}.flowOn(Dispatchers.IO)
.collect {
it.fold(onFailure = { e ->
e?.let { it1 -> onError(it1) }
}, onSuccess = { file ->
onSuccess(file)
}, onLoading = { progress ->
onProcess(progress.currentLength, progress.length, progress.process)
})
}
}
}
5. 请求示例
lifecycleScope.launch{
apiService.download(downloadUrl, saveFile,
onProcess = { currentLength, length, process ->
Log.i("download","${(process * 100).toInt()}%")
}, onSuccess = {
Log.i("download","${it.absolutePath}%")
},onError = {
it?.printStackTrace()
})
}
3.配合workManager使用
1. 引入workManager
def work_version = "2.7.0"
implementation "androidx.work:work-runtime-ktx:$work_version"
2. 构建DownloadWorker
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Color
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.*
import me.shetj.download.demo.R
import me.shetj.download.http.KCHttpV2
import java.util.*
/**
* 测试下载
*/
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputUrl = inputData.getString(KEY_OUT_PUT_URL)
?: return Result.failure()
val filename = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputUrl, filename)
return Result.success()
}
private suspend fun download(downloadUrl: String, outputFile: String, fileName: String) {
KCHttpV2.download(downloadUrl, "$outputFile/$fileName", onProcess = { _, _, process ->
setForeground(createForegroundInfo("${(process * 100).toInt()}%"))
setProgress(Data.Builder().let {
it.putInt("progress", (process * 100).toInt())
it.build()
})
}, onSuccess = {
setForeground(createForegroundInfo("download ok"))
},onError = {
it.printStackTrace()
})
}
private fun createForegroundInfo(progress: String): ForegroundInfo {
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(id)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, getChannelID())
.setContentTitle(getTitle())
.setTicker(getTitle())
.setContentText(progress)
.setSmallIcon(R.mipmap.ic_launcher)
.setOngoing(true) //防止滑动删除
.addAction(R.mipmap.picture_icon_delete_photo , "取消", intent)
.build()
return ForegroundInfo("下载文件".hashCode(), notification)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
if (NotificationManagerCompat.from(applicationContext).getNotificationChannel(
getChannelID()
) == null) {
val name = "文件下载"
val description = "文件下载"
val importance = NotificationManager.IMPORTANCE_LOW
val mChannel = NotificationChannel(getChannelID(), name, importance)
mChannel.description = description
mChannel.enableLights(true)
mChannel.lightColor = Color.RED
mChannel.enableVibration(true)
return NotificationManagerCompat.from(applicationContext).createNotificationChannel(mChannel)
}
}
private fun getTitle(): String {
return "文件下载"
}
private fun getChannelID(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
"文件下载"
} else {
""
}
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUT_PUT_URL = "KEY_OUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
fun startDownload(context: Context, downloadUrl: String, outputFile: String, fileName: String): UUID {
val inputData: Data = Data.Builder().apply {
putString(KEY_INPUT_URL, downloadUrl)
putString(KEY_OUTPUT_FILE_NAME, fileName)
putString(KEY_OUT_PUT_URL, outputFile)
}.build()
val request = OneTimeWorkRequestBuilder<DownloadWorker>().setInputData(inputData).build()
WorkManager.getInstance(context).enqueue(request)
return request.id
}
}
}
3. 使用示例
DownloadWorker.startDownload(
this@MainActivity,
"https://dldir1.qq.com/wework/work_weixin/wxwork_android_3.0.31.13637_100001.apk",
this@MainActivity.cacheDir.path,
"wxwork_android_3.apk"
)
demo下载
因为之前没有添加demo,现在加上
downloadDemo