【问题标题】:Download file from url using Retrofit and Kotlin coroutines使用 Retrofit 和 Kotlin 协程从 url 下载文件
【发布时间】:2022-01-10 19:20:36
【问题描述】:

我有以下方法从 url 下载 pdf 文件。

我的ApiService.kt

interface ApiService {

    
    @GET("8d370189-4ec8-11ec-8469-005056ae4067/Aktionsprospekt-06-12-2021-11-12-2021-06.pdf?_ga=2.119177993.1645728632.1638630355-145806302.1638630355")
    suspend fun getData(): Response<ResponseBody>
}

我的ApiClient.kt

object ApiClient {
    fun getClient(): ApiService {
        return Retrofit.Builder().baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create()).build()
            .create(ApiService::class.java)
    }
}

在 ViewModel 中,我调用协程范围如下

class MainActivityViewModel(private val apiService: ApiService, private val context: Context) : ViewModel() {

    fun downloadFile() = viewModelScope.launch {
        val responseBody = apiService.getData().body()
        saveFile(responseBody)
    }

    private fun saveFile(body: ResponseBody?) : String {
        if (body == null) {
            return ""
        }

        var input: InputStream? = null
        try{
            input = body.byteStream()
            val path = context.getExternalFilesDir(null)!!.path
            val fos = FileOutputStream(path)
            fos.use { output ->
                val buffer = ByteArray(4 * 1024) // or other buffer size
                var read: Int
                while (input.read(buffer).also { read = it } != -1) {
                    output.write(buffer, 0, read)
                }
                output.flush()
            }
            return path
        } catch(e: Exception) {
            Log.e("saveFile",e.toString())
        }
        finally {
            input?.close()
        }
        return ""
    }

}

在主要活动中,我调用了downloadFile() 的上述函数,如下所示:

val apiService: ApiService = ApiClient.getClient()
        viewModel = getViewModel(apiService)
        downloadBtn.setOnClickListener {
            viewModel.downloadFile()
        }

我在saveFile() 函数中遇到错误

2021-12-05 02:50:04.023 7027-7027/com.mms.compareandchoose E/saveFile: java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.mms.compareandchoose/files (Is a directory)
2021-12-05 02:50:04.055 7027-7027/com.mms.compareandchoose E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.mms.compareandchoose, PID: 7027
    android.os.NetworkOnMainThreadException
        at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1303)
        at com.android.org.conscrypt.Platform.blockGuardOnNetwork(Platform.java:300)
        at com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream.write(OpenSSLSocketImpl.java:839)
        at okio.OutputStreamSink.write(JvmOkio.kt:53)
        at okio.AsyncTimeout$sink$1.write(AsyncTimeout.kt:103)
        at okio.RealBufferedSink.flush(RealBufferedSink.kt:247)
        at okhttp3.internal.http2.Http2Writer.rstStream(Http2Writer.kt:135)
        at okhttp3.internal.http2.Http2Connection.writeSynReset$okhttp(Http2Connection.kt:354)
        at okhttp3.internal.http2.Http2Stream.close(Http2Stream.kt:240)
        at okhttp3.internal.http2.Http2Stream.cancelStreamIfNecessary$okhttp(Http2Stream.kt:506)
        at okhttp3.internal.http2.Http2Stream$FramingSource.close(Http2Stream.kt:488)
        at okio.ForwardingSource.close(ForwardingSource.kt:34)
        at okhttp3.internal.connection.Exchange$ResponseBodySource.close(Exchange.kt:309)
        at okio.RealBufferedSource.close(RealBufferedSource.kt:498)
        at okio.ForwardingSource.close(ForwardingSource.kt:34)
        at okio.RealBufferedSource.close(RealBufferedSource.kt:498)
        at okio.RealBufferedSource$inputStream$1.close(RealBufferedSource.kt:170)
        at com.mms.compareandchoose.models.MainActivityViewModel.saveFile(MainActivityViewModel.kt:46)
        at com.mms.compareandchoose.models.MainActivityViewModel$downloadFile$1.invokeSuspend(MainActivityViewModel.kt:20)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6776)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1518)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1408)

谁能说出为什么会出现这个错误?还建议这种方法好不好??

【问题讨论】:

  • NetworkOnMainThreadException.

标签: android file save retrofit2 kotlin-coroutines


【解决方案1】:

当您调用input.read(buffer) 时,您从远程流中读取数据。但是viewModelScope 默认在主线程上启动代码。要修复异常,您应该明确指定在另一个 (IO) 线程上执行下载。

fun downloadFile() = viewModelScope.launch(Dispatchers.IO) {
        val responseBody = apiService.getData().body()
        saveFile(responseBody)
}

【讨论】:

    猜你喜欢
    • 2019-11-20
    • 1970-01-01
    • 2017-02-24
    • 2021-07-31
    • 1970-01-01
    • 2015-09-16
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多