我正在尝试使用 android studio 从 Spring 启动服务器下载文件。代码如下:
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun getCurrentDetailFile(detail: Detail) {
uiScope.launch {
val stream = detailsContainerRepository.downloadFile(detail.file)
if (stream.success != null) {
val wrapper = ContextWrapper(context)
var path = Environment.getExternalStorageDirectory()
var file = Environment.getExternalStorageDirectory()
when (detail) {
is PhotoDetail -> {
path =
wrapper.getDir(Environment.DIRECTORY_PICTURES, Context.MODE_PRIVATE)
file = File(path, "image.jpg")
}
is AudioDetail -> {
path = wrapper.getDir(Environment.DIRECTORY_MUSIC, Context.MODE_PRIVATE)
file = File(path, "audio.mpeg")
}
}
try {
path.mkdir()
val inputStream = stream.success.byteStream()
val outputStream = FileOutputStream(file)
IOUtils.copy(inputStream, outputStream)
detail.file = file
_currentDetail.postValue(detail)
_goToDetail.value = detail
doneLoading()
} catch (e: Exception) {
Timber.e()
_errorMessage.postValue(R.string.error_load_detail_failed)
}
}
if (stream.exception != null) {
_errorMessage.postValue(R.string.error_load_detail_failed)
}
}
}
尽管它在异步线程上运行,但在执行此行时我得到了 NetworkOnMainThreadException:
IOUtils.copy(inputStream, outputStream)
我不知道为什么会这样,也不知道如何解决。谁能帮帮我?
答案 0 :(得分:2)
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
是您的问题。您可能应该使用 Dispatchers.IO
而不是 Dispatchers.Main
。即使您的网络调用在协程中,它仍然在主/UI 线程上运行(主线程和 UI 线程本质上是同义词)。
Android 不允许在主线程上进行网络调用,因为不知道它们会持续多久或是否会完成。所有网络调用都必须发生在不同的线程上。可以将协程视为在特定上下文中执行的作业,在您的情况下,您说协程应该在 Dispatchers.Main
上下文中运行,该上下文将在 UI 线程上运行。
综上所述,确保网络调用在代码中实际发生的任何地方都在后台线程上可能是值得的,而不管该代码是如何调用的。例如,这意味着更改方法 donwloadFile
以在 IO 线程上启动协程。最终结果是,无论在何处或如何触发网络调用,都不会在主 UI 线程上发生。