DatePickerFragment内存泄漏

时间:2020-03-28 17:21:01

标签: android leakcanary

我将所有侦听器和引用都设置为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显示:

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 个答案:

答案 0 :(得分:4)

这是Android框架中的已知泄漏,这就是LeakCanary将其显示为“库泄漏”的原因。如果您在共享的跟踪的底部阅读,则会看到以下内容:

pattern =实例字段android.os.Message#obj,

description =正在等待阻塞队列的线程将泄漏最后一个出队列的对象作为堆栈本地引用。

我不确定您为什么只共享一部分说明,完整说明可以在泄漏源中查看: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)永远不会睡太久。有点难过,但这就是我们在Square处理的方式。