首页 > 解决方案 > DatePickerDialog 中的内存泄漏。如何识别是什么问题?

问题描述

我在我的应用程序中使用这个MaterialDateTimePicker。我很难找到泄漏的根本原因。DatePickerDialog根据日志显示的内容泄漏。我不确定lib是否有问题,但我认为我做错了什么。

我有一个View显示当前日期的。用户可以单击它,然后DatePickerDialog显示。然后她/他选择一天并单击 的确定按钮DatePickerDialog。我收到日期,格式化,然后呈现。几秒钟后,Leak Canary lib 说,嘿,你泄露了。如果您能告诉我我的代码有什么问题,那就太好了。

所以,这是我的自定义小部件。

import android.content.Context
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentManager
import com.atco.forsite.R
import com.atco.forsite.screens.utility.DatePickerOFIState.NOW_FUTURE
import com.atco.forsite.screens.utility.DatePickerOFIState.PAST_NOW
import com.jakewharton.rxrelay2.PublishRelay
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
import java.util.Calendar

enum class DatePickerOFIState {
    PAST_NOW,
    NOW_FUTURE
}

class DatePickerOFI(
    private val context: Context,
    private val fm: FragmentManager,
    private val state: DatePickerOFIState
) : DatePickerDialog.OnDateSetListener {

    /**
     * Subscribe to this observer in order to be notified when the result is ready.
     */
    val relay: PublishRelay<Calendar> = PublishRelay.create()

    private val datePicker: DatePickerDialog

    companion object {
        const val TAG = "DatePickerDialog"
    }

    init {
        val now = Calendar.getInstance()
        datePicker = DatePickerDialog.newInstance(
                this,
                now.get(Calendar.YEAR),
                now.get(Calendar.MONTH),
                now.get(Calendar.DAY_OF_MONTH)
        )
        datePicker.version = DatePickerDialog.Version.VERSION_2
        datePicker.accentColor = ContextCompat.getColor(context, R.color.primaryBlue)
        datePicker.setOkColor(ContextCompat.getColor(context, R.color.white))
        datePicker.setCancelColor(ContextCompat.getColor(context, R.color.white))

        when (state) {
            PAST_NOW -> datePicker.maxDate = now
            NOW_FUTURE -> datePicker.minDate = now
        }
    }

    fun show() {
        datePicker.show(fm, TAG)
    }

    override fun onDateSet(view: DatePickerDialog?, year: Int, monthOfYear: Int, dayOfMonth: Int) {
        val calendar = Calendar.getInstance()
        calendar.set(year, monthOfYear, dayOfMonth)
        relay.accept(calendar)
    }
}

我的没有什么特别的Activity。我打开DatePickerDialog这样的:

class SafetyExchangeActivity : AppCompatActivity() {

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

        val datePicker = DatePickerOFI(this, supportFragmentManager, DatePickerOFIState.PAST_NOW)
        datePicker.relay.safeSubscribe(createDefaultObserver(logger) {
            setReportDate(this)
        })

        etReportedDate.setOnClickListener {
            datePicker.show()
        }
    }
}

泄漏痕迹:

┬───
│ 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.mCurRootView
├─ 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.atco.forsite.screens.records.safetyExchange.SafetyExchangeActivity 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 (SafetyExchangeActivity↓ is not leaking and View attached)
│    mContext instance of com.atco.forsite.screens.records.safetyExchange.SafetyExchangeActivity 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.atco.forsite.screens.records.safetyExchange.SafetyExchangeActivity instance
│    Leaking: NO (Activity#mDestroyed is false)
│    ↓ SafetyExchangeActivity.datePicker
│                             ~~~~~~~~~~
├─ com.atco.forsite.screens.utility.DatePickerOFI instance
│    Leaking: UNKNOWN
│    ↓ DatePickerOFI.datePicker
│                    ~~~~~~~~~~
╰→ com.wdullaer.materialdatetimepicker.date.DatePickerDialog instance
​     Leaking: YES (ObjectWatcher was watching this because com.wdullaer.materialdatetimepicker.date.DatePickerDialog received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
​     key = dd153a49-8e66-45f7-8736-16342134e7f6
​     watchDurationMillis = 5297
​     retainedDurationMillis = 297

METADATA

Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: Google
LeakCanary version: 2.2
App process name: com.atco.forsite
Analysis duration: 8193 ms

标签: androidleakcanary

解决方案


根据泄漏跟踪,我认为问题的根源是这一行

private val datePicker: DatePickerDialog

DatePickerOFI课堂上。

作为一种解决方案,我认为你需要在datePicker你的工作完成后取消实例变量,即对话框被解除。


推荐阅读