【问题标题】:Android ViewModel is leakingAndroid ViewModel 正在泄漏
【发布时间】:2021-02-04 00:41:20
【问题描述】:

LeakCanary 告诉我,我的一个 ViewModel 正在泄漏,但玩了 2 天后,我无法让泄漏消失。

Here is why LeakCanary shows

这是获取 ViewModel 的 Fragment

viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java).apply {
        getStrains(arguments?.getString(BREEDER_ID_KEY, "")!!)
    }

这是视图模型

class ViewBreederViewModel(application: Application) : AndroidViewModel(application) {

private val breederRepository = BreederRepository(application)
val strainList = MutableLiveData<List<MinimalStrain>>()

fun getStrains(breederId: String) {
    viewModelScope.launch {
        breederRepository.getMinimalStrains(breederId).observeForever {
            strainList.value = it
        }
    }
}

}

这里是 BreederRepository:

class BreederRepository(context: Context) {

private val dao: BreederDao
private val breederApi = RetrofitClientInstance.getInstance(context).breederAndStrainIdsApi

init {
    val database: Db = Db.getInstance(
        context
    )!!
    dao = database.breederDao()
}

suspend fun getMinimalStrains(breederId: String): LiveData<List<MinimalStrain>> =
    withContext(Dispatchers.IO) {
        dao.getMinimalStrains(breederId)
    }

}

这是 Db 类

@Database(
entities = [Breeder::class, Strain::class],
version = 1,
exportSchema = true)
@TypeConverters(RoomDateConverter::class)

abstract class Db : RoomDatabase() {

abstract fun breederDao(): BreederDao

companion object {
    private var instance: Db? = null

    @JvmStatic
    fun getInstance(context: Context): Db? {
        if (instance == null) {
            synchronized(Db::class) {
                instance = Room.databaseBuilder(
                    context.applicationContext,
                    Db::class.java, "seedfinder_db"
                )
                    .build()
            }
        }
        return instance
    }
}

}

【问题讨论】:

    标签: android android-room android-viewmodel leakcanary


    【解决方案1】:

    您正在使用observeForever,顾名思义,即使在您的 ViewModel 被清除后,它也会一直观察。 Room 不需要为返回 LiveData 的 DAO 方法使用 suspend 方法,这在任何情况下都不是正确的方法 - LiveData 已经是异步的。

    相反,您应该是transforming your LiveData,使用您的breederId 作为strainList LiveData 的输入:

    class ViewBreederViewModel(application: Application) : AndroidViewModel(application) {
    
        private val breederRepository = BreederRepository(application)
        private val currentBreederId = MutableLiveData<String>()
    
        // Here we use the switchMap method from the lifecycle-livedata-ktx artifact
        val strainList: LiveData<String> = currentBreederId.switchMap {
                breederId -> breederRepository.getMinimalStrains(breederId)
        }
    
        private fun setBreederId(breederId: String) {
            currentBreederId.value = breederId
        }
    }
    

    你的getMinimalStrains 变成:

    fun getMinimalStrains(breederId: String): LiveData<List<MinimalStrain>> =
        dao.getMinimalStrains(breederId)
    

    您可以通过在 UI 中设置 breederId 并像以前一样观察您的 strainList 来使用它:

    viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java).apply {
        setBreederId(arguments?.getString(BREEDER_ID_KEY, "")!!)
    }
    viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
      // use your updated list
    }
    

    如果您使用的是Saved State module for ViewModels(如果您使用的是最新的稳定片段/活动库,这是默认设置),那么您可以使用SavedStateHandle,它会自动从您的片段的参数中填充并跳过@ 987654336@完全:

    class ViewBreederViewModel(
        application: Application,
        savedStateHandle: SavedStateHandle
    ) : AndroidViewModel(application) {
    
        private val breederRepository = BreederRepository(application)
    
        // Here we use the switchMap method from the lifecycle-livedata-ktx artifact
        val strainList: LiveData<String> = savedStateHandle
            .getLiveData(BREEDER_ID_KEY) // Automatically populated from arguments
            .switchMap {
                breederId -> breederRepository.getMinimalStrains(breederId)
            }
    }
    

    这意味着您的代码可以简单地变成:

    viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java)
    viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
      // use your updated list
    }
    

    如果您使用fragment-ktx 工件,您可以进一步简化为:

    // Move this to where you declare viewModel
    val viewModel: ViewBreederViewModel by viewModels()
    
    viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
      // use your updated list
    }
    

    【讨论】:

    • 哇,一位传奇人物的出色而及时的回答,谢谢!看起来我有一些重构要做!
    猜你喜欢
    • 2021-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-23
    • 2011-12-31
    相关资源
    最近更新 更多