如何修复标准底部 sheet 行为中的内存泄漏问题?
How to fix memory leak issue in standard bottom sheet behaviour?
我正在使用 leakcanary,它检测 标准底部 sheet 行为 中的泄漏。但是我无法解决这个问题,
我该如何解决这个漏洞?参考我的泄漏金丝雀报告。
standardBottomSheetBehaviour
┬───
│ GC Root: System class
│
├─ leakcanary.internal.InternalLeakCanary class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static InternalLeakCanary.resumedActivity
├─ com.demoapp.zigmaster.MainActivity instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking and Activity#mDestroyed
│ is false)
│ mApplication instance of com.demoapp.zigmaster.MyApplication
│ mBase instance of androidx.appcompat.view.ContextThemeWrapper
│ ↓ ComponentActivity.mActivityResultRegistry
├─ androidx.activity.ComponentActivity instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ Anonymous subclass of androidx.activity.result.ActivityResultRegistry
│ this[=11=] instance of com.demoapp.zigmaster.MainActivity with mDestroyed =
│ false
│ ↓ ActivityResultRegistry.mKeyToCallback
├─ java.util.HashMap instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ HashMap.table
├─ java.util.HashMap$HashMapEntry[] array
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ HashMap$HashMapEntry[].[4]
├─ java.util.HashMap$HashMapEntry instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ HashMap$HashMapEntry.value
├─ androidx.activity.result.ActivityResultRegistry$CallbackAndContract instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ ActivityResultRegistry$CallbackAndContract.mCallback
├─ androidx.fragment.app.FragmentManager instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ Anonymous class implementing androidx.activity.result.
│ ActivityResultCallback
│ ↓ FragmentManager.this[=11=]
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ FragmentManager.mParent
├─ com.demoapp.zigmaster.ui.trips.FragmentTripPlanner instance
│ Leaking: NO (Fragment#mFragmentManager is not null)
│ ↓ FragmentTripPlanner.standardBottomSheetBehavior
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ com.google.android.material.bottomsheet.BottomSheetBehavior instance
│ Leaking: UNKNOWN
│ Retaining 463.1 kB in 6881 objects
│ ↓ BottomSheetBehavior.viewDragHelper
│ ~~~~~~~~~~~~~~
├─ androidx.customview.widget.ViewDragHelper instance
│ Leaking: UNKNOWN
│ Retaining 462.3 kB in 6859 objects
│ ↓ ViewDragHelper.mParentView
│ ~~~~~~~~~~~
╰→ androidx.coordinatorlayout.widget.CoordinatorLayout instance
Leaking: YES (ObjectWatcher was watching this because com.demoapp.
zigmaster.ui.trips.FragmentTripPlanner received Fragment#onDestroyView()
callback (references to its views should be cleared to prevent leaks))
Retaining 461.9 kB in 6847 objects
key = 27239e9e-c1f3-4642-b4d8-ab44c954f53b
watchDurationMillis = 57433
retainedDurationMillis = 52422
View not part of a window view hierarchy
View.mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
mContext instance of com.demoapp.zigmaster.MainActivity with
mDestroyed = false
METADATA
Build.VERSION.SDK_INT: 25
Build.MANUFACTURER: samsung
LeakCanary version: 2.7
App process name: com.demoapp.zigmaster
Stats: LruCache[maxSize=3000,hits=5750,misses=80700,hitRate=6%]
RandomAccess[bytes=4089892,reads=80700,travel=60892211355,range=18101017,size=22
288421]
Heap dump reason: user request
Analysis duration: 29922 ms
这是我的代码:
FragmentTripPlanner.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.material.bottomsheet.BottomSheetBehavior
import android.widget.LinearLayout
class FragmentTripPlanner : Fragment() {
private lateinit var standardBottomSheetBehavior: BottomSheetBehavior<LinearLayout>
private var binding : FragmentTripPlannerBinding ?=null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentTripPlannerBinding.inflate(inflater)
standardBottomSheetBehavior = BottomSheetBehavior.from(binding!!.tripBottomSheet.standardBottomSheet)
standardBottomSheetBehavior.isHideable = false
standardBottomSheetBehavior.peekHeight = 560
standardBottomSheetBehavior.isDraggable = true
standardBottomSheetBehavior.skipCollapsed = false
standardBottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> {
}
else -> {
}
}
}
})
return binding!!.root
}
override fun onDestroyView() {
super.onDestroyView()
binding=null
}
}
XML: trip_bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bottomSheet"
app:behavior_hideable="false"
app:behavior_peekHeight="200dp"
android:orientation="vertical"
android:elevation="20dp"
android:background="@drawable/rounded_dialog_bottom_sheet"
app:layout_behavior="@string/bottom_sheet_behavior">
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_gravity="center"
/>
</LinearLayout>
fragment_trip_planner.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.trips.FragmentTripPlanner">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/tripBottomSheet"
layout="@layout/trip_bottom_sheet" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
您需要使 standardBottomSheetBehavior
可为空,然后在 FragmentTripPlanner.onDestroyView()
中,您需要清除对 standardBottomSheetBehavior
的引用,因为它引用了 standardBottomSheet
override fun onDestroyView() {
super.onDestroyView()
binding=null
standardBottomSheetBehavior = null
}
我正在使用 leakcanary,它检测 标准底部 sheet 行为 中的泄漏。但是我无法解决这个问题,
我该如何解决这个漏洞?参考我的泄漏金丝雀报告。
standardBottomSheetBehaviour
┬───
│ GC Root: System class
│
├─ leakcanary.internal.InternalLeakCanary class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static InternalLeakCanary.resumedActivity
├─ com.demoapp.zigmaster.MainActivity instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking and Activity#mDestroyed
│ is false)
│ mApplication instance of com.demoapp.zigmaster.MyApplication
│ mBase instance of androidx.appcompat.view.ContextThemeWrapper
│ ↓ ComponentActivity.mActivityResultRegistry
├─ androidx.activity.ComponentActivity instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ Anonymous subclass of androidx.activity.result.ActivityResultRegistry
│ this[=11=] instance of com.demoapp.zigmaster.MainActivity with mDestroyed =
│ false
│ ↓ ActivityResultRegistry.mKeyToCallback
├─ java.util.HashMap instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ HashMap.table
├─ java.util.HashMap$HashMapEntry[] array
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ HashMap$HashMapEntry[].[4]
├─ java.util.HashMap$HashMapEntry instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ HashMap$HashMapEntry.value
├─ androidx.activity.result.ActivityResultRegistry$CallbackAndContract instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ ActivityResultRegistry$CallbackAndContract.mCallback
├─ androidx.fragment.app.FragmentManager instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ Anonymous class implementing androidx.activity.result.
│ ActivityResultCallback
│ ↓ FragmentManager.this[=11=]
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: NO (FragmentTripPlanner↓ is not leaking)
│ ↓ FragmentManager.mParent
├─ com.demoapp.zigmaster.ui.trips.FragmentTripPlanner instance
│ Leaking: NO (Fragment#mFragmentManager is not null)
│ ↓ FragmentTripPlanner.standardBottomSheetBehavior
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ com.google.android.material.bottomsheet.BottomSheetBehavior instance
│ Leaking: UNKNOWN
│ Retaining 463.1 kB in 6881 objects
│ ↓ BottomSheetBehavior.viewDragHelper
│ ~~~~~~~~~~~~~~
├─ androidx.customview.widget.ViewDragHelper instance
│ Leaking: UNKNOWN
│ Retaining 462.3 kB in 6859 objects
│ ↓ ViewDragHelper.mParentView
│ ~~~~~~~~~~~
╰→ androidx.coordinatorlayout.widget.CoordinatorLayout instance
Leaking: YES (ObjectWatcher was watching this because com.demoapp.
zigmaster.ui.trips.FragmentTripPlanner received Fragment#onDestroyView()
callback (references to its views should be cleared to prevent leaks))
Retaining 461.9 kB in 6847 objects
key = 27239e9e-c1f3-4642-b4d8-ab44c954f53b
watchDurationMillis = 57433
retainedDurationMillis = 52422
View not part of a window view hierarchy
View.mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
mContext instance of com.demoapp.zigmaster.MainActivity with
mDestroyed = false
METADATA
Build.VERSION.SDK_INT: 25
Build.MANUFACTURER: samsung
LeakCanary version: 2.7
App process name: com.demoapp.zigmaster
Stats: LruCache[maxSize=3000,hits=5750,misses=80700,hitRate=6%]
RandomAccess[bytes=4089892,reads=80700,travel=60892211355,range=18101017,size=22
288421]
Heap dump reason: user request
Analysis duration: 29922 ms
这是我的代码: FragmentTripPlanner.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.material.bottomsheet.BottomSheetBehavior
import android.widget.LinearLayout
class FragmentTripPlanner : Fragment() {
private lateinit var standardBottomSheetBehavior: BottomSheetBehavior<LinearLayout>
private var binding : FragmentTripPlannerBinding ?=null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentTripPlannerBinding.inflate(inflater)
standardBottomSheetBehavior = BottomSheetBehavior.from(binding!!.tripBottomSheet.standardBottomSheet)
standardBottomSheetBehavior.isHideable = false
standardBottomSheetBehavior.peekHeight = 560
standardBottomSheetBehavior.isDraggable = true
standardBottomSheetBehavior.skipCollapsed = false
standardBottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> {
}
else -> {
}
}
}
})
return binding!!.root
}
override fun onDestroyView() {
super.onDestroyView()
binding=null
}
}
XML: trip_bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bottomSheet"
app:behavior_hideable="false"
app:behavior_peekHeight="200dp"
android:orientation="vertical"
android:elevation="20dp"
android:background="@drawable/rounded_dialog_bottom_sheet"
app:layout_behavior="@string/bottom_sheet_behavior">
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_gravity="center"
/>
</LinearLayout>
fragment_trip_planner.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.trips.FragmentTripPlanner">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/tripBottomSheet"
layout="@layout/trip_bottom_sheet" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
您需要使 standardBottomSheetBehavior
可为空,然后在 FragmentTripPlanner.onDestroyView()
中,您需要清除对 standardBottomSheetBehavior
的引用,因为它引用了 standardBottomSheet
override fun onDestroyView() {
super.onDestroyView()
binding=null
standardBottomSheetBehavior = null
}