You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Kotlin协程中调用同一函数并实现同步API与异步UI更新?

问题描述

需求是先在UI中显示带加载状态的列表条目,等API请求成功返回后,处理列表数据并更新RecyclerView Adapter。目前在Fragment的两个协程中分别监听Flow,收到数据后调用updateAndSetAdapterList函数更新列表,但现在希望改用同步的API调用函数,同时保证从同步函数中触发的UI更新操作能异步执行。

当前Fragment中的调用代码:

lifecycleScope.launch(Dispatchers.IO) {
    // Videos
    viewModel.dashboardVideosFlow.collectLatest { flow ->
        when (flow) {
            ...
            is VideosFlow.DataReceived -> {
                val row = SectionVideosBodyData(flow.videos)
                updateAndSetAdapterList(1, row)
            }
        }
    }
}

lifecycleScope.launch(Dispatchers.IO) {
    // Questions
    viewModel.dashboardQuestionsFlow.collectLatest { flow ->
        when (flow) {
            ....
            is QuestionsFlow.DataReceived -> {
                val row = SectionQuestionsBodyData(flow.questions)
                updateAndSetAdapterList(3, row)
            }
        }
    }
}

当前的updateAndSetAdapterList函数实现:

private suspend fun updateAndSetAdapterList(indexAt: Int? = null, row: BaseAdapterData? = null) {
    
    lifecycleScope.launch(Dispatchers.IO) {
            val newRows = mutableListOf<BaseAdapterData>()
            newRows.addAll(rows)
    
            if (indexAt != null && row != null) {
                newRows.removeAt(indexAt)
                newRows.add(indexAt, row)
            }
    
            withContext(Dispatchers.Main) {
                dashboardAdapter.submitList(newRows)
            }
    
            rows = newRows
        }
    }
}
解决方案

1. 给列表加线程安全保护

先给Fragment添加一个Mutex,用来管控rows列表的读写操作,避免多协程并发修改引发异常:

private val rowsMutex = Mutex()
private var rows = mutableListOf<BaseAdapterData>()

2. 重构列表更新函数

优化updateAndSetAdapterList,确保列表修改线程安全,同时UI更新异步执行:

private fun updateAndSetAdapterList(indexAt: Int, row: BaseAdapterData) {
    // 后台线程处理列表修改,不阻塞主线程
    lifecycleScope.launch(Dispatchers.IO) {
        rowsMutex.withLock {
            // 安全拷贝并修改列表
            val newRows = rows.toMutableList()
            newRows.removeAt(indexAt)
            newRows.add(indexAt, row)
            // 更新原列表
            rows = newRows
            // 切回主线程异步更新UI
            withContext(Dispatchers.Main) {
                dashboardAdapter.submitList(newRows)
            }
        }
    }
}

3. 同步API调用的正确姿势

同步API函数必须放在Dispatchers.IO线程的协程中执行,避免阻塞主线程;拿到数据后直接调用更新函数即可:

// 处理Videos同步API请求
lifecycleScope.launch(Dispatchers.IO) {
    try {
        // 同步调用API,IO线程执行不卡UI
        val videos = fetchVideosSync()
        val row = SectionVideosBodyData(videos)
        // 触发列表更新,UI操作自动异步执行
        updateAndSetAdapterList(1, row)
    } catch (e: Exception) {
        // 处理请求异常,比如替换为错误状态条目
        withContext(Dispatchers.Main) {
            val errorRow = SectionVideosErrorData()
            updateAndSetAdapterList(1, errorRow)
        }
    }
}

// 处理Questions同步API请求
lifecycleScope.launch(Dispatchers.IO) {
    try {
        val questions = fetchQuestionsSync()
        val row = SectionQuestionsBodyData(questions)
        updateAndSetAdapterList(3, row)
    } catch (e: Exception) {
        withContext(Dispatchers.Main) {
            val errorRow = SectionQuestionsErrorData()
            updateAndSetAdapterList(3, errorRow)
        }
    }
}

核心说明

  • 线程安全:用Mutex.withLock包裹列表读写,确保同一时间只有一个协程修改rows,杜绝并发问题。
  • 异步UI更新:列表修改在IO线程完成,UI更新通过withContext(Dispatchers.Main)切回主线程,全程不阻塞UI。
  • 同步API适配:同步API必须放在IO线程协程中调用,否则会直接卡住主线程导致界面无响应。

内容的提问来源于stack exchange,提问作者murat_yuksektepe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 10:01:43