Я пытаюсь реализовать горизонтальный просмотр полной страницы прокрутки во фрагменте, но у меня проблемы с исправлением утечек памяти. В каждом представлении есть прослушиватель событий значения базы данных Firebase в реальном времени, который определяет, понравился ли пользователю текущий контент на экране или нет. Функциональность работает должным образом, но в MainActivity
обнаружена утечка памяти, когда кнопка «Назад» используется для выхода из приложения. Я использую LeakCanary, чтобы найти утечки памяти.
Внутри класса адаптера для пейджера у меня есть:
class MediaAdapter(private val uid: String): RecyclerView.Adapter<MediaAdapter.MediaViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.view_pager_media, parent, false)
return MediaViewHolder(view)
}
override fun onViewDetachedFromWindow(holder: MediaViewHolder) {
super.onViewDetachedFromWindow(holder)
holder.removeListeners()
}
override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {
val currentMedia = media[position]
holder.bind(currentMedia)
}
// inner class MediaViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) ...
}
У меня ViewHolder в качестве внутреннего класса:
inner class MediaViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
private lateinit var ref: DatabaseReference
private lateinit var likedListener: ValueEventListener
fun removeListeners() {
ref.removeEventListener(likedListener)
}
fun bind(currentMedia: Media) {
setupListeners(currentMedia)
}
private fun setupListeners(currentMedia: Media) {
ref = Firebase.database.getReference("Likes/${currentMedia.id}")
setIsLikedListener(uid)
}
private fun setIsLikedListener(uid: String) {
likedListener = object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.child(uid).exists()) {
itemView.like_button.setImageResource(R.drawable.ic_favorite_black_24dp)
itemView.like_button.tag = Constant.CONTENT_LIKED
} else {
itemView.like_button.setImageResource(R.drawable.ic_favorite_border_black_24dp)
itemView.like_button.tag = Constant.CONTENT_NOT_LIKED
}
}
}
ref.addValueEventListener(likedListener)
}
}
Я использую пейджер вида в фрагмент, управляемый с MainActivity
. Внутри этого фрагмента я устанавливаю для адаптера значение null
при уничтожении:
override fun onDestroyView() {
super.onDestroyView()
media_view_pager.adapter = null
}
Когда приложение открыто, я перемещаюсь к фрагменту из MainActivity
. Я не называю ничего в onDestroy
в основной активности, и журнал утечек предполагает, что утечка происходит из MainActivity
. Любая помощь в том, что я делаю неправильно?
Вывод журнала LeakCanary:
┬───
│ GC Root: System class
│
├─ com.google.firebase.database.core.ZombieEventManager class
│ Leaking: NO (a class is never leaking)
│ ↓ static ZombieEventManager.defaultInstance
│ ~~~~~~~~~~~~~~~
├─ com.google.firebase.database.core.ZombieEventManager instance
│ Leaking: UNKNOWN
│ ↓ ZombieEventManager.globalEventRegistrations
│ ~~~~~~~~~~~~~~~~~~~~~~~~
├─ java.util.HashMap instance
│ Leaking: UNKNOWN
│ ↓ HashMap.table
│ ~~~~~
├─ java.util.HashMap$Node[] array
│ Leaking: UNKNOWN
│ ↓ HashMap$Node[].[0]
│ ~~~
├─ java.util.HashMap$Node instance
│ Leaking: UNKNOWN
│ ↓ HashMap$Node.key
│ ~~~
├─ com.google.firebase.database.core.ValueEventRegistration instance
│ Leaking: UNKNOWN
│ ↓ ValueEventRegistration.eventListener
│ ~~~~~~~~~~~~~
├─ hydroid.adapters.MediaAdapter$MediaViewHolder$setIsLikedListener$1 instance
│ Leaking: UNKNOWN
│ Anonymous class implementing com.google.firebase.database.ValueEventListener
│ ↓ MediaAdapter$MediaViewHolder$setIsLikedListener$1.this$0
│ ~~~~~~
├─ hydroid.adapters.MediaAdapter$MediaViewHolder instance
│ Leaking: UNKNOWN
│ ↓ MediaAdapter$MediaViewHolder.itemView
│ ~~~~~~~~
├─ androidx.constraintlayout.widget.ConstraintLayout instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ mContext instance of hydroid.activities.MainActivity with mDestroyed = true
│ View#mParent is null
│ View#mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ ↓ ConstraintLayout.mContext
╰→ hydroid.activities.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because hydroid.activities.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = adc0793b-8dec-41da-a565-25b984b9e54a
watchDurationMillis = 5167
retainedDurationMillis = 167
METADATA
Build.VERSION.SDK_INT: 26
Build.MANUFACTURER: Google
LeakCanary version: 2.2
Analysis duration: 4819 ms```