DatePickerDialog 中的内存泄漏。如何识别是什么问题?
Memory leak in DatePickerDialog. How to identify what is the problem?
我在我的应用程序中使用 this MaterialDateTimePicker
library。我无法找到泄漏的根本原因。 DatePickerDialog
根据日志显示的内容泄漏。我不确定 lib 是否有问题,但我认为我做错了什么。
我有一个显示当前日期的 View
。用户可以单击它,然后显示 DatePickerDialog
。然后s/he选择一天,点击确定按钮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
根据泄漏轨迹,我认为问题的根源是这一行
private val datePicker: DatePickerDialog
在 DatePickerOFI
class.
作为解决方案,我认为您需要在完成工作后使 datePicker
实例变量无效,即关闭对话框。
应该是var
:
private lateinit var datePicker: DatePickerDialog
而且应该解构onDestroy
:
override fun onDestroy() {
datePicker = null;
super.onDestroy()
}
我在我的应用程序中使用 this MaterialDateTimePicker
library。我无法找到泄漏的根本原因。 DatePickerDialog
根据日志显示的内容泄漏。我不确定 lib 是否有问题,但我认为我做错了什么。
我有一个显示当前日期的 View
。用户可以单击它,然后显示 DatePickerDialog
。然后s/he选择一天,点击确定按钮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
根据泄漏轨迹,我认为问题的根源是这一行
private val datePicker: DatePickerDialog
在 DatePickerOFI
class.
作为解决方案,我认为您需要在完成工作后使 datePicker
实例变量无效,即关闭对话框。
应该是var
:
private lateinit var datePicker: DatePickerDialog
而且应该解构onDestroy
:
override fun onDestroy() {
datePicker = null;
super.onDestroy()
}