Утечка памяти в DatePickerFragment - PullRequest
0 голосов
/ 28 марта 2020

Я сделал все слушатели и ссылки как WeakReference, но тем не менее, это утечка памяти.

DatePickerFragment:

class DatePickerFragment : DialogFragment() {


    private var datePickerDialog: WeakReference<DatePickerDialog>? = null
    private var listener: WeakReference<DatePickerDialog.OnDateSetListener>? = null
    var dateSetListener: DateSet? = null

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        listener = WeakReference(DatePickerDialog.OnDateSetListener { view, year, month, dayOfMonth ->
            val selectedDate = ZonedDateTime.now().startOfTheDay().withDayOfMonth(dayOfMonth).withMonth(month.plus(1)).withYear(year)
            dateSetListener?.onDateSet(selectedDate, arguments?.getBoolean(REQUIRED_RELOAD)!!)
            dismissAllowingStateLoss()
        })
        // Use the current date as the default date in the picker
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)

        // Create a new instance of DatePickerDialog and return it
        datePickerDialog = WeakReference(DatePickerDialog(requireActivity(), listener?.get(), year, month, day))
        return datePickerDialog!!.get()!!
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        dateSetListener = (activity as DateSet)
    }


    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        dismissAllowingStateLoss()
    }


    interface DateSet {
        fun onDateSet(date: ZonedDateTime, reload: Boolean)
    }

    override fun onDestroy() {
        Timber.d("onDestroy called")
        listener = null
        dateSetListener = null
        datePickerDialog = null
        super.onDestroy()
    }

    companion object {
        const val REQUIRED_RELOAD = "REQUIRED_RELOAD_KEY"
    }
}

Показ DatePicker из Activity:

private var dateFragment: WeakReference<DatePickerFragment>? = null
private fun showDatePicker(reload: Boolean) {
    dateFragment = WeakReference(DatePickerFragment())
    dateFragment?.get()?.arguments = Bundle().apply { putBoolean(REQUIRED_RELOAD, reload) }
    dateFragment?.get()?.show(supportFragmentManager, "PaDatePicker")
}

Как вы можете видеть из фрагмента кода, я стараюсь всеми возможными способами исправить утечку, но не повезло. Любая помощь очень ценится.

 ┬
    ├─ android.os.HandlerThread
    │    Leaking: UNKNOWN
    │    Thread name: 'queued-work-looper'
    │    GC Root: Java local variable
    │    ↓ thread HandlerThread.<Java Local>
    │                           ~~~~~~~~~~~~
    ├─ android.os.Message
    │    Leaking: UNKNOWN
    │    ↓ Message.obj
    │              ~~~
    ├─ android.app.DatePickerDialog
    │    Leaking: YES (Dialog#mDecor is null)
    │    ↓ DatePickerDialog.mDateSetListener
    ├─ god.panchang.DatePickerFragment$onCreateDialog$1
    │    Leaking: YES (DatePickerDialog↑ is leaking)
    │    Anonymous class implementing android.app.DatePickerDialog$OnDateSetListener
    │    ↓ DatePickerFragment$onCreateDialog$1.this$0
    ╰→ god.panchang.DatePickerFragment
    ​     Leaking: YES (DatePickerFragment$onCreateDialog$1↑ is leaking and Fragment#mFragmentManager is null and ObjectWatcher was watching this)
    ​     key = 6b7d9e5c-79bb-4e74-83c3-c570b34aa23a
    ​     watchDurationMillis = 23765
    ​     retainedDurationMillis = 18763
    , retainedHeapByteSize=1547, pattern=instance field android.os.Message#obj, description=A thread waiting on a blocking queue will leak the last dequeued object as a stack local reference.

1 Ответ

4 голосов
/ 31 марта 2020

Это известная утечка в Android Framework, поэтому LeakCanary показывает ее как «утечку библиотеки». Если вы прочтете нижнюю часть трассы, которой вы поделились, вы увидите следующее:

pattern = поле экземпляра android .os.Message # obj,

description = A поток, ожидающий в очереди блокировки, утечет последний удаленный объект как локальную ссылку стека.

Я не уверен, почему вы поделились только частью описания, полное описание можно увидеть в источник утечки: https://github.com/square/leakcanary/blob/master/shark-android/src/main/java/shark/AndroidReferenceMatchers.kt#L165 -L185

  val description = ("A thread waiting on a blocking queue will leak the last"
      + " dequeued object as a stack local reference. So when a HandlerThread becomes idle, it"
      + " keeps a local reference to the last message it received. That message then gets"
      + " recycled and can be used again. As long as all messages are recycled after being"
      + " used, this won't be a problem, because these references are cleared when being"
      + " recycled. However, dialogs create template Message instances to be copied when a"
      + " message needs to be sent. These Message templates holds references to the dialog"
      + " listeners, which most likely leads to holding a reference onto the activity in some"
      + " way. Dialogs never recycle their template Message, assuming these Message instances"
      + " will get GCed when the dialog is GCed."
      + " The combination of these two things creates a high potential for memory leaks as soon"
      + " as you use dialogs. These memory leaks might be temporary, but some handler threads"
      + " sleep for a long time."
      + " To fix this, you could post empty messages to the idle handler threads from time to"
      + " time. This won't be easy because you cannot access all handler threads, but a library"
      + " that is widely used should consider doing this for its own handler threads. This leaks"
      + " has been shown to happen in both Dalvik and ART.")

Что вы можете сделать? Легкого пути нет. Вы можете:

  1. прекратить одновременное использование диалогов. Нет диалогов, нет проблем.
  2. Убедитесь, что HandlerThread (queued-work-looper) никогда не спит слишком долго. Это немного грустно, но вот как мы справились с этим на площади.
...