Прослушиватели утечки памяти из пейджера при закрытии приложения - PullRequest
0 голосов
/ 26 апреля 2020

Я пытаюсь реализовать горизонтальный просмотр полной страницы прокрутки во фрагменте, но у меня проблемы с исправлением утечек памяти. В каждом представлении есть прослушиватель событий значения базы данных 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```
...