Android ViewModel 正在泄漏
Android ViewModel is leaking
LeakCanary 告诉我我的一个 ViewModel 正在泄漏,但在玩了 2 天后我无法消除泄漏。
这是获取 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 class
@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
}
}
}
您正在使用 observeForever
,顾名思义,即使在您的 ViewModel 被清除后,它也将永远保持观察。 Room 不需要对 return 和 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(如果您使用最新的稳定 Fragments / Activity 库,这是默认设置),那么您可以使用 SavedStateHandle
,这是从您的片段参数自动填充并完全跳过 setBreederId()
:
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
}
LeakCanary 告诉我我的一个 ViewModel 正在泄漏,但在玩了 2 天后我无法消除泄漏。
这是获取 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 class
@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
}
}
}
您正在使用 observeForever
,顾名思义,即使在您的 ViewModel 被清除后,它也将永远保持观察。 Room 不需要对 return 和 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(如果您使用最新的稳定 Fragments / Activity 库,这是默认设置),那么您可以使用 SavedStateHandle
,这是从您的片段参数自动填充并完全跳过 setBreederId()
:
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
}