Reputation: 61
I am developing an application with single activity. There is main fragment and two fragments inside, here is the code:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.coreproject.presentation.viewModel.MainViewModel" />
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<Button
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_normal"
android:onClick="@{viewModel::resetAuth}"
android:text="Reset authorization" />
<Button
android:id="@+id/about_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_normal"
android:onClick="@{viewModel::goToAbout}"
android:text="About" />
<fragment
class="com.example.services.presentation.view.PopularServiceListFragmentWidget"
android:id="@+id/widget_popular_services"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_normal"/>
<fragment
class="com.example.services.presentation.view.CategoryListFragmentWidget"
android:id="@+id/widget_categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/padding_normal"
android:layout_marginBottom="@dimen/padding_big"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</layout>
I can go to another fragment from here, when I do this, a memory leak occurs. Here is the stack trace:
LeakingInstance(referenceKey=c4b8a77a-e8c0-4e6a-937d-d14422c9a616, referenceName=,
instanceClassName=androidx.core.widget.NestedScrollView, watchDurationMillis=-515750270,
exclusionStatus=null, leakTrace=
D/LeakCanary: ┬
D/LeakCanary: ├─ android.os.HandlerThread
D/LeakCanary: │ Leaking: NO (it's a GC root)
D/LeakCanary: │ Thread name: 'LeakCanary-Heap-Dump'
D/LeakCanary: │ ↓ thread HandlerThread.contextClassLoader
D/LeakCanary: ├─ dalvik.system.PathClassLoader
D/LeakCanary: │ Leaking: NO (Object[]↓ is not leaking and Classloader never leaking)
D/LeakCanary: │ ↓ PathClassLoader.runtimeInternalObjects
D/LeakCanary: ├─ java.lang.Object[]
D/LeakCanary: │ Leaking: NO (Glide↓ is not leaking)
D/LeakCanary: │ ↓ array Object[].[694]
D/LeakCanary: ├─ com.bumptech.glide.Glide
D/LeakCanary: │ Leaking: NO (Glide↓ is not leaking and a class is never leaking)
D/LeakCanary: │ ↓ static Glide.glide
D/LeakCanary: ├─ com.bumptech.glide.Glide
D/LeakCanary: │ Leaking: NO (ArrayList↓ is not leaking)
D/LeakCanary: │ ↓ Glide.managers
D/LeakCanary: ├─ java.util.ArrayList
D/LeakCanary: │ Leaking: NO (Object[]↓ is not leaking)
D/LeakCanary: │ ↓ ArrayList.elementData
D/LeakCanary: ├─ java.lang.Object[]
D/LeakCanary: │ Leaking: NO (RequestManager↓ is not leaking)
D/LeakCanary: │ ↓ array Object[].[2]
D/LeakCanary: ├─ com.bumptech.glide.RequestManager
D/LeakCanary: │ Leaking: NO (SupportRequestManagerFragment$SupportFragmentRequestManagerTreeNode↓ is not leaking)
D/LeakCanary: │ ↓ RequestManager.treeNode
D/LeakCanary: ├─ com.bumptech.glide.manager.SupportRequestManagerFragment$SupportFragmentRequestManagerTreeNode
D/LeakCanary: │ Leaking: NO (SupportRequestManagerFragment↓ is not leaking)
D/LeakCanary: │ ↓ SupportRequestManagerFragment$SupportFragmentRequestManagerTreeNode.this$0
D/LeakCanary: ├─ com.bumptech.glide.manager.SupportRequestManagerFragment
D/LeakCanary: │ Leaking: NO (MainFragment↓ is not leaking and SupportRequestManagerFragment#mFragmentManager is not null)
D/LeakCanary: │ ↓ SupportRequestManagerFragment.mParentFragment
D/LeakCanary: ├─ com.example.coreproject.presentation.view.MainFragment
D/LeakCanary: │ Leaking: NO (MainFragment#mFragmentManager is not null)
D/LeakCanary: │ ↓ MainFragment.binding
D/LeakCanary: │ ~~~~~~~
D/LeakCanary: ├─ com.example.coreproject.databinding.FragmentMainBindingImpl
D/LeakCanary: │ Leaking: UNKNOWN
D/LeakCanary: │ ↓ FragmentMainBindingImpl.mRoot
D/LeakCanary: │ ~~~~~
D/LeakCanary: ╰→ androidx.core.widget.NestedScrollView
D/LeakCanary: Leaking: YES (RefWatcher was watching this)
D/LeakCanary: , retainedHeapSize=null)])
In addition to the main fragment, the built-in and all subsequent ones also leak if you switch to them, but if you return to the main one, the leak disappears. I have no idea how to fix this. Any help is appreciated.
Upvotes: 0
Views: 1720
Reputation: 1145
According to google documents View Binding fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment's onDestroyView() method.
// This property is only valid between onCreateView and
// onDestroyView.
private var _binding: LoginBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = LoginBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
You can now use the instance of the binding class to reference any of the views:
Upvotes: 1
Reputation: 8349
Based on the leaktrace, we can see that MainFragment is not leaking but it has a binding
field which has a reference to FragmentMainBindingImpl which itself references a detached view. That view is the view of MainFragment, which means that Fragment.onDestroyView() was called. When Fragment.onDestroyView() is called then the fragment should let go of the reference to the view, ie binding
should be set to null.
Upvotes: 3