用到了依赖

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.2"

implementation "androidx.room:room-runtime:2.4.3"
implementation "androidx.room:room-ktx:2.4.3"
kapt "androidx.room:room-compiler:2.4.3"

implementation "androidx.navigation:navigation-fragment-ktx:2.5.2"
implementation "androidx.navigation:navigation-ui-ktx:2.5.2"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"

implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

implementation "androidx.legacy:legacy-support-v4:1.0.0"

implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"

 没用到的也有。。不做剔除了

 权限

<uses-permission android:name="android.permission.INTERNET" />

 下载核心类

package com.example.android_flow_practice.download

import com.example.android_flow_practice.utils.copyTo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.io.IOException

object DownloadManager {
fun download(url: String, file: File): Flow<DownLoadStatus> {

return flow {
val request = Request.Builder().url(url).get().build()
val response = OkHttpClient.Builder().build().newCall(request).execute()
if (response.isSuccessful) {
response.body()!!.let { responseBody ->
val total = responseBody.contentLength()
//文件读写操作
file.outputStream().use { output ->
val input = responseBody.byteStream()
var emittedProgress = 0L
input.copyTo(output) { bytes ->
val progress = bytes * 100 / total
if (progress - emittedProgress > 5) {
delay(100)
emit(DownLoadStatus.Progress(progress.toInt()))
emittedProgress = progress;
}

}

}

}
emit(DownLoadStatus.Done(file))
} else {
throw IOException(response.toString())
}
}.catch {
file.delete()
emit(DownLoadStatus.Error(it))

}.flowOn(Dispatchers.IO)

}
}

核心类用到了一个 


DownLoadStatus 的密封类


package com.example.android_flow_practice.download

import java.io.File

//密封类 成员都是他的子类
sealed class DownLoadStatus {

object None : DownLoadStatus()
data class Progress(val value: Int) : DownLoadStatus()

data class Error(val throwable: Throwable) : DownLoadStatus()

data class Done(val file: File) : DownLoadStatus()

}

用到了一个下载帮助类 


IOExt.kt


package com.example.android_flow_practice.utils

import java.io.InputStream
import java.io.OutputStream

inline fun InputStream.copyTo(
out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE, progress: (value: Long) -> Unit
): Long {
var btyesCopied: Long = 0
val buffer = ByteArray(bufferSize)
var bytes = read(buffer)

while (bytes >= 0) {
out.write(buffer, 0, bytes)
btyesCopied += bytes
bytes = read(buffer)
progress(btyesCopied)
}
return btyesCopied;
}

那其实代码布局也可以放进来

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
tools:context=".fragment.HomeFragment">

<ProgressBar
android:id="@+id/progressBar"
style="?android:progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:max="100" />

<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"

android:layout_height="wrap_content"
android:layout_below="@id/progressBar"
android:layout_centerHorizontal="true"
android:textSize="20sp"
tools:text="10%"

/>


</RelativeLayout>

Android Kotlin Flow下载图片并回调进度和错误_java

下载对应的代码

package com.example.android_flow_practice.fragment


class DownloadFragment : Fragment() {

private val mBinding: FragmentDownloadBinding by lazy {
FragmentDownloadBinding.inflate(layoutInflater)
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return mBinding.root
}

private val TAG = "DownloadFragment"

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launchWhenCreated {
context?.apply {
val url = "https://www.xxx.com/xxx.png"
val path = getExternalFilesDir(null)?.path
Log.e(TAG, "path ${path}")
val file = File(path, "pic.png")
DownloadManager.download(url, file).collect { status ->
when (status) {
is DownLoadStatus.Progress -> {
mBinding.apply {
progressBar.progress = status.value
tvProgress.text = "${status.value}%"
}
}
is DownLoadStatus.Error -> {
Toast.makeText(context, "下载错误", Toast.LENGTH_SHORT).show()
}
is DownLoadStatus.Done -> {
mBinding.apply {
progressBar.progress = 100
tvProgress.text = "100%"
}
Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show()
}
else -> {
Log.d(TAG, "下载错误")
}
}
}

}
}

}

}

 这里下了一个图片并存放到sd卡当中

图片网络地址你们替换下吧

Android Kotlin Flow下载图片并回调进度和错误_kotlin_02