DialogFragment в CustomView вызывает утечку памяти - PullRequest
1 голос
/ 11 марта 2020

У меня есть MainActivity, которая имеет customView (DatePicker) в своем макете. Пользовательский вид DatePicker имеет кнопку и CustomDialogFragment. Когда кнопка нажата на DatePicker, он показывает CustomDialogFragment. Приложение работает нормально, но leakCanary показывает утечку. Вот код (Некоторый код удален для краткости)

MainActivity.class

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        date_picker.calendarDialog = getCalendarDialog()
    }

    private fun getCalendarDialog(): CalendarDialog {
        return CalendarDialog()
    }
}

activity_main. xml

  <androidx.constraintlayout.widget.ConstraintLayout>
    <com.example.testproject.customViews.DatePicker
        android:id="@+id/date_picker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </com.example.testproject.customViews.DatePicker>
 </androidx.constraintlayout.widget.ConstraintLayout>

DatePicker.class

class DatePicker : FrameLayout {
    var calendarDialog: CalendarDialog? = null
    init {
        View.inflate(context, R.layout.date_picker, this)
        open_calendar.setOnClickListener {
            calendarDialog?.show((context as MainActivity).supportFragmentManager.beginTransaction(), "Calendar")
        }
    }
}

CalendarDialog.class

class CalendarDialog: DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val builder = AlertDialog.Builder(context!!)
        builder.setView(view)
            .setMessage("This is a dummy message")
            .setPositiveButton("OK") { dialog, which -> }
            .setNegativeButton("Cancel") { dialog, which -> }

        return builder.create()
    }
}

РЕЗУЛЬТАТ АНАЛИЗА КАРТЫ ============================ ====== 1 УТВЕРЖДЕНИЕ ПРИЛОЖЕНИЯ

References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.

1437 bytes retained by leaking objects
Signature: 1eb8b5c7c3fd403a9a6851729c4044c8a6ce7cf6
┬───
│ GC Root: System class
│
├─ android.view.inputmethod.InputMethodManager class
│    Leaking: NO (InputMethodManager↓ is not leaking and a class is never leaking)
│    ↓ static InputMethodManager.sInstance
├─ android.view.inputmethod.InputMethodManager instance
│    Leaking: NO (DecorView↓ is not leaking and InputMethodManager is a singleton)
│    ↓ InputMethodManager.mNextServedView
├─ com.android.internal.policy.DecorView instance
│    Leaking: NO (LinearLayout↓ is not leaking and View attached)
│    mContext instance of com.android.internal.policy.DecorContext, wrapping activity com.example.testproject.MainActivity with mDestroyed = false
│    Parent android.view.ViewRootImpl not a android.view.View
│    View#mParent is set
│    View#mAttachInfo is not null (view attached)
│    View.mWindowAttachCount = 1
│    ↓ DecorView.mContentRoot
├─ android.widget.LinearLayout instance
│    Leaking: NO (MainActivity↓ is not leaking and View attached)
│    mContext instance of com.example.testproject.MainActivity with mDestroyed = false
│    View.parent com.android.internal.policy.DecorView attached as well
│    View#mParent is set
│    View#mAttachInfo is not null (view attached)
│    View.mWindowAttachCount = 1
│    ↓ LinearLayout.mContext
├─ com.example.testproject.MainActivity instance
│    Leaking: NO (DatePicker↓ is not leaking and Activity#mDestroyed is false)
│    ↓ MainActivity._$_findViewCache
├─ java.util.HashMap instance
│    Leaking: NO (DatePicker↓ is not leaking)
│    ↓ HashMap.table
├─ java.util.HashMap$Node[] array
│    Leaking: NO (DatePicker↓ is not leaking)
│    ↓ HashMap$Node[].[0]
├─ java.util.HashMap$Node instance
│    Leaking: NO (DatePicker↓ is not leaking)
│    ↓ HashMap$Node.value
├─ com.example.testproject.customViews.DatePicker instance
│    Leaking: NO (View attached)
│    mContext instance of com.example.testproject.MainActivity with mDestroyed = false
│    View.parent androidx.constraintlayout.widget.ConstraintLayout attached as well
│    View#mParent is set
│    View#mAttachInfo is not null (view attached)
│    View.mID = R.id.date_picker
│    View.mWindowAttachCount = 1
│    ↓ DatePicker.calendarDialog
│                 ~~~~~~~~~~~~~~
╰→ com.example.testproject.customViews.CalendarDialog instance
​     Leaking: YES (ObjectWatcher was watching this because com.example.testproject.customViews.CalendarDialog received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
​     key = e176896c-49c6-4b17-a21e-4a6ca7cde260
​     watchDurationMillis = 11213
​     retainedDurationMillis = 6208
​     key = f3a2f22a-c77f-4c8e-a281-d803d110acff
​     watchDurationMillis = 11214
====================================
0 LIBRARY LEAKS

Library Leaks are leaks coming from the Android Framework or Google libraries.
====================================
METADATA

Please include this in bug reports and Stack Overflow questions.

Build.VERSION.SDK_INT: 28
Build.MANUFACTURER: Google
LeakCanary version: 2.2
App process name: com.example.testproject
Analysis duration: 4191 ms
Heap dump file path: /data/user/0/com.example.testproject/files/leakcanary/2020-03-11_10-15-46_729.hprof
Heap dump timestamp: 1583936152876
====================================

Это то, что я пытался до сих пор безуспешно.

  1. Я пытался инициализировать CalendarDialog в разных местах.
  2. Создайте прослушиватель в классе CalendarDialog и сделайте экземпляр calendarDialog пустым при закрытии диалога. И еще немного ..

Ответы [ 4 ]

1 голос
/ 16 марта 2020

Вы можете поместить CalendarDialog в WeakReference, так что вам не нужно явно устанавливать его в null через обратный вызов. Сборщик мусора автоматически очистит его, и вы сможете избежать утечки памяти.

Для получения дополнительной информации о WeakReference: https://developer.android.com/reference/java/lang/ref/WeakReference

0 голосов
/ 22 марта 2020

У меня был подобный опыт раньше. Все, что я сделал, это изменил supportFragmentManager на childFragmentManager. Поэтому я предлагаю изменить это:

calendarDialog?.show((context as MainActivity).supportFragmentManager.beginTransaction(), "Calendar")

на это:

calendarDialog?.show((context as MainActivity).childFragmentManager.beginTransaction(), "Calendar")

Также ищите любые другие supportFragmentManager (если есть).

0 голосов
/ 22 марта 2020

Сделать CalenderDialog в качестве функции утилиты

class CalendarDialog {

  fun onCreateDialog(context: Context): Dialog {
    val builder = AlertDialog.Builder(context)
    builder
        .setMessage("This is a dummy message")
        .setPositiveButton("OK") { dialog, which -> }
        .setNegativeButton("Cancel") { dialog, which -> }

    return builder.create()
  }
}

и

class DatePicker(context: Context, attr: AttributeSet) : FrameLayout(context, attr) {

  var calendarDialog: CalendarDialog? = null

  init {
    View.inflate(context, R.layout.date_picker, this)
    open_calendar.setOnClickListener {
        calendarDialog?.onCreateDialog(context)?.show()
    }
  }

  override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    calendarDialog = null
    open_calendar.setOnClickListener(null)
  }
}

В задании нет представления о существовании фрагмента диалога в вашем случае.

0 голосов
/ 13 марта 2020

Когда фрагмент CalendarDialog отклоняется, ожидается, что этот фрагмент будет сборщиком мусора. Однако здесь мы видим, что макет DatePicker хранит ссылку на него, предотвращая сбор мусора. DatePicker по-прежнему подключен, поэтому имеет смысл, чтобы он все еще существовал, но ему следует установить нулевое значение в поле DatePicker.calendarDialog, когда диалоговое окно закрыто.

...