如何正确使用Dispatchers.IO在Android上读取本地文件?

问题描述 投票:0回答:1

在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()
    }
}
android kotlin android-jetpack-compose kotlin-coroutines android-mvvm
1个回答
0
投票

问题出在您的撰写代码中。当您在可组合项中执行此操作时:

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
的默认设置),那么您的主线程将在您读取文件时被阻塞,而您肯定不希望这样。

© www.soinside.com 2019 - 2024. All rights reserved.