在Android文档中它说:
Dispatchers.IO - 此调度程序经过优化,可以在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件,读取或写入文件,以及运行任何 网络操作。
在我的应用程序中,在某些情况下我需要读取位于
.json
目录中的 assets
文件,转换这些文件(使用 Moshi)并使用 Jetpack Compose 将它们显示在屏幕上。
根据文档中的内容,我明白我应该使用 Dispatchers.IO 来执行此类操作。
在 ViewModel 中,我有这段代码,它读取给定的本地文件:
@HiltViewModel
class FileViewModel @Inject constructor(
private val getFileUseCase: GetFileUseCase,
@Dispatcher(LPlusDispatchers.IO) private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
private val _uiState = MutableStateFlow<FileUiState>(FileUiState.Loading)
val uiState: StateFlow<FileUiState> = _uiState.asStateFlow()
fun loadData(fileRequest: FileRequest) {
_uiState.value = FileUiState.Loading
viewModelScope.launch(ioDispatcher) {
try {
val result = getFileUseCase(fileRequest)
_uiState.value = FileUiState.Loaded(FileItemUiState(result))
} catch (error: Exception) {
_uiState.value = FileUiState.Error(ExceptionParser.getMessage(error))
}
}
}
//...
}
这是用例:
class GetFileUseCase @Inject constructor(
private val fileRepository: LocalFileRepository
) {
suspend operator fun invoke(fileRequest: FileRequest): MutableList<FileResponse> =
fileRepository.getFile(fileRequest)
}
这是存储库中的代码:
override suspend fun getFile(fileRequest: FileRequest): MutableList<FileResponse> {
val fileResponse = assetProvider.getFiles(fileRequest.fileName)
val moshi = Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Content::class.java, "type")
.withSubtype(Paragraphus::class.java, "p")
.withSubtype(Rubrica::class.java, "r")
.withSubtype(Titulus::class.java, "t")
//...
)
.add(KotlinJsonAdapterFactory())
.build()
fileResponse.forEach {
if (books.contains(it.fileName)) {
it.text = moshi.adapter(Book::class.java).fromJson(it.text.toString()))
// ...
}
}
return fileResponse
}
令我惊讶的是,当放入
viewModelScope.launch(ioDispatcher)
时,代码一直在运行。也就是说,如果我在搜索参数中传递的文件的代码中放置一个断点,它会不断地在该点停止。 另一方面,如果我只放置 viewModelScope.launch()
,代码将按预期工作,仅读取参数中传递的文件一次。
我的问题是:在这种情况下是否没有必要使用 Dispatchers.IO,即使文档说使用它来读取文件?为什么?
不知道这里的变化是不是Jetpack Compose的使用。下面我展示了我对 ViewModel 中生成的状态的使用:
@Composable
fun FileScreen(
modifier: Modifier = Modifier,
fileRequest: FileRequest,
viewModel: FileViewModel = hiltViewModel(),
)
{
viewModel.loadData(fileRequest)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
MasScreen(modifier = modifier, uiState = uiState)
}
@Composable
fun MasScreen(
modifier: Modifier,
uiState: FileViewModel.FileUiState
)
{
when (uiState) {
FileViewModel.FileUiState.Empty -> EmptyState()
is FileViewModel.FileUiState.Error -> ErrorState()
is FileViewModel.FileUiState.Loaded -> {
uiState.itemState.allData.forEach {
Text(text = it.text)
}
}
FileViewModel.FileUiState.Loading -> LoadingState()
}
}
问题出在您的撰写代码中。当您在可组合项中执行此操作时:
viewModel.loadData(fileRequest)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
viewModel.loadData
将在每次重组时调用(例如每次状态更改)。由于您在同一个可组合项中观察到 uiState
并且 loadData
正在更改该 ui 状态,因此您刚刚创建了一个无限循环。您应该阅读Compose 中的副作用。目前,最简单的解决方法是:
LaunchedEffect(viewModel, fileRequest) { viewModel.loadData(fileRequest) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
仅当
loadData
发生变化时才会调用 fileRequest
(或 viewModel
,但这实际上不应该发生)。
除此之外,是的,在这种情况下你绝对应该使用
Dispatchers.IO
。如果您使用主调度程序(这是 viewModelScope
的默认设置),那么您的主线程将在您读取文件时被阻塞,而您肯定不希望这样。