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

Android Kotlin观测数据处理异常排查:代码实现是否正确?

问题分析与代码修复建议

问题描述

我开发了一款从数据库获取数据的Android应用,数据下载过程中屏幕会显示进度条与提示文本告知用户正在下载,下载完成后该文本与进度条会消失。但应用存在异常表现:有时会出现卡顿,有时进度条和文本消失但数据并未正确加载。Spinner仅用于Activity启动初期展示默认值。

相关代码

Repository层

fun getAllZebra(): LiveData<List<Int>>{
    return sybaseDao.getAllZebra()
}

ViewModel层

@HiltViewModel
class ZebraViewModel @Inject constructor(
    private val repository: SybaseRepository
):ViewModel(){
    val skladnici = repository.getSkladnik().asLiveData()

    fun getAllZebra(): LiveData<List<Int>>{
        return repository.getAllZebra()
    }
}

Activity层

@AndroidEntryPoint
class ZebraActivity : AppCompatActivity()  {
    var idZebra : Int = 0
    var idExp: Int = 0
    private val zebraViewModel: ZebraViewModel by viewModels()
    private val spinner3 by lazy {findViewById<Spinner>(R.id.zebra_spinner)}
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)        
        SybaseApi.initilaizeBaseUrl(this)
        val binding = ActivityZebraBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)     

        binding.apply {
            zebraViewModel.skladnici.observe(this@ZebraActivity) { result ->
                val skladnici: List<Skladnik>? = result.data
                if (skladnici != null) {
                    Log.e("Skladnici", skladnici.size.toString())
                } else {
                    Log.e("Skladnici", "NULL")
                }
                progressBarLogin.isVisible =
                    result is Resource.Loading && result.data.isNullOrEmpty()
                textViewProgressBar.isVisible =
                    result is Resource.Loading && result.data.isNullOrEmpty()
                textViewProgressBar.text = "Loading data from database"
                textViewErrorLogin.isVisible =
                    result is Resource.Error && result.data.isNullOrEmpty()
                textViewErrorLogin.text = result.error?.localizedMessage
                // 此处存在逻辑错误
                textViewErrorLogin.isVisible = result is Resource.Success
                textViewErrorLogin.text = "Data uploaded:"
            }
        }
    }

    override fun onResume() {
        super.onResume()
        SybaseApi.initilaizeBaseUrl(this)
        initSpinnerData()
    }

    private fun initSpinnerData(){
        val allDevices = this?.let {
            ArrayAdapter<Any>(it, R.layout.simple_item_spinner, R.id.tv_simple_item)
        }        
        allDevices?.clear()
        // 每次onResume都会重新订阅LiveData,导致多次回调
        zebraViewModel.getAllZebra()
            .observe(this, { devices ->
                devices?.forEach{
                    // 冗余判断,且可能导致数据添加不及时
                    allDevices?.getPosition(it)?.let { it1 -> if (it1 < 0) allDevices?.add(it) }
                }
            } )
        spinner3.adapter = allDevices
        spinner3.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
            override fun onItemSelected(parent: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
                if (parent != null) {
                    val idZebraString = parent.getItemAtPosition(position).toString()
                    idZebra = idZebraString.toInt()
                }
            }

            override fun onNothingSelected(p0: AdapterView<*>?) {
                Toast.makeText(this@ZebraActivity, "You need to choose some item", Toast.LENGTH_LONG).show()
            }
        }
    }
}

代码问题与修复方案

1. UI状态逻辑覆盖错误

问题点:在skladnici的观察者中,最后两行代码直接将textViewErrorLogin的可见性设置为Success状态,完全覆盖了之前的Error状态判断。这会导致错误状态无法正常显示、成功状态错误复用错误提示控件,造成UI逻辑混乱,甚至进度条消失后数据并未正确加载的假象。

修复:
新增一个成功提示的TextView(比如textViewSuccess),用when语句清晰区分三种状态的UI控制:

binding.apply {
    zebraViewModel.skladnici.observe(this@ZebraActivity) { result ->
        val skladnici: List<Skladnik>? = result.data
        when(result) {
            is Resource.Loading -> {
                progressBarLogin.isVisible = true
                textViewProgressBar.isVisible = true
                textViewProgressBar.text = "Loading data from database"
                textViewErrorLogin.isVisible = false
                textViewSuccess.isVisible = false
            }
            is Resource.Success -> {
                progressBarLogin.isVisible = false
                textViewProgressBar.isVisible = false
                textViewErrorLogin.isVisible = false
                textViewSuccess.isVisible = true
                textViewSuccess.text = "Data uploaded:"
                // 处理成功数据
                skladnici?.let { 
                    Log.e("Skladnici", it.size.toString())
                    // 这里添加数据加载后的业务逻辑
                } ?: Log.e("Skladnici", "NULL")
            }
            is Resource.Error -> {
                progressBarLogin.isVisible = false
                textViewProgressBar.isVisible = false
                textViewSuccess.isVisible = false
                textViewErrorLogin.isVisible = true
                textViewErrorLogin.text = result.error?.localizedMessage ?: "Unknown error"
                Log.e("Skladnici", "Error: ${result.error?.localizedMessage}")
            }
        }
    }
}

2. LiveData重复订阅与内存泄漏风险

问题点:initSpinnerData在onResume中调用,每次Activity回到前台都会重新订阅getAllZebra()的LiveData,导致多次回调、UI重复更新,还可能引发内存泄漏。

修复:
将Spinner数据订阅移到onCreate中,仅订阅一次:

override fun onCreate(savedInstanceState: Bundle?) {
    // ... 其他代码
    initSpinnerData()
}

override fun onResume() {
    super.onResume()
    SybaseApi.initilaizeBaseUrl(this)
    // 移除initSpinnerData调用,避免重复订阅
}

private fun initSpinnerData(){
    val allDevices = ArrayAdapter<Any>(this, R.layout.simple_item_spinner, R.id.tv_simple_item)
    spinner3.adapter = allDevices
    // 只订阅一次LiveData
    zebraViewModel.getAllZebra().observe(this) { devices ->
        allDevices.clear()
        devices?.let { allDevices.addAll(it) }
        allDevices.notifyDataSetChanged()
    }
    // ... 其他Spinner逻辑
}

3. Spinner适配器数据更新逻辑冗余

问题点:原代码中getPosition遍历适配器数据的判断逻辑冗余,多次调用会降低性能,还可能导致数据更新不及时。

修复:
直接清空适配器后添加所有数据,如需去重则先处理数据:

zebraViewModel.getAllZebra().observe(this) { devices ->
    allDevices.clear()
    devices?.distinct()?.let { allDevices.addAll(it) } // 去重可选
    allDevices.notifyDataSetChanged()
}

4. 潜在的UI线程卡顿问题

问题点:如果Sybase数据库操作未在后台线程执行,会阻塞UI线程导致卡顿。

修复:
确保Repository层的数据库操作在后台线程执行,比如用协程:

// Repository层示例
fun getAllZebra(): LiveData<List<Int>>{
    return liveData(Dispatchers.IO) {
        emit(sybaseDao.getAllZebra().value ?: emptyList())
    }
}

5. 初始化逻辑重复

问题点:SybaseApi.initilaizeBaseUrl(this)在onCreate和onResume重复调用,造成不必要的性能损耗。

修复:
仅在onCreate中调用一次初始化:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)        
    SybaseApi.initilaizeBaseUrl(this) // 仅初始化一次
    // ... 其他代码
}

override fun onResume() {
    super.onResume()
    // 移除重复初始化
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 12:59:53