Fragment is leaking in ViewPager2 onDestroyView
sergeykonar opened this issue · comments
Hello guys. I have been trying to resolve one issue with memory leak in ViewPager2.
So, I have:
- MainActivity - here I initialize the views and setup Navigation component to navigate using bottom navigation view.
- HomeFragment and Profile Fragment
- In HomeFragment I have ViewPager2
- TestFragment is a fragemnt that represents each viewPager's page
Iam using FragmentStateAdapter(fragmentManager, lifecycle) to create an adapter for my ViewPager2
I am getting a memory leak when I am navigating between my fragments in ViewPager2. I can't understand why I am getting the leak.
The Log:
`D/LeakCanary:
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
6126 bytes retained by leaking objects
Displaying only 1 leak trace out of 3 with the same signature
Signature: f3295dce470d2cb483f6ce0a93abe2c145e0d996
┬───
│ 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 (ViewPager2$RecyclerViewImpl↓ is not leaking and View attached)
│ View is part of a window view hierarchy
│ View.mAttachInfo is not null (view attached)
│ View.mWindowAttachCount = 1
│ mContext instance of com.android.internal.policy.DecorContext, wrapping activity com.konda.viewpagertest.
│ MainActivity with mDestroyed = false
│ ↓ View.mAttachInfo
├─ android.view.View$AttachInfo instance
│ Leaking: NO (ViewPager2$RecyclerViewImpl↓ is not leaking)
│ ↓ View$AttachInfo.mScrollContainers
├─ java.util.ArrayList instance
│ Leaking: NO (ViewPager2$RecyclerViewImpl↓ is not leaking)
│ ↓ ArrayList[0]
├─ androidx.viewpager2.widget.ViewPager2$RecyclerViewImpl instance
│ Leaking: NO (View attached)
│ View is part of a window view hierarchy
│ View.mAttachInfo is not null (view attached)
│ View.mID = R.id.null
│ View.mWindowAttachCount = 1
│ mContext instance of com.konda.viewpagertest.MainActivity with mDestroyed = false
│ ↓ RecyclerView.mAdapter
│ ~~~~~~~~
├─ com.konda.viewpagertest.adapters.RadioStationFragmentAdapter instance
│ Leaking: UNKNOWN
│ Retaining 10,0 kB in 343 objects
│ ↓ RadioStationFragmentAdapter.hashMap
│ ~~~~~~~
├─ java.util.HashMap instance
│ Leaking: UNKNOWN
│ Retaining 6,5 kB in 220 objects
│ ↓ HashMap[instance @1898466120 of java.lang.Integer]
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
╰→ com.konda.viewpagertest.ui.TestFragment instance
Leaking: YES (ObjectWatcher was watching this because com.konda.viewpagertest.ui.TestFragment received
Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
Retaining 2,0 kB in 69 objects
key = ef189c4d-bb87-4f4f-b30c-3d31f5ede56b
watchDurationMillis = 54122
retainedDurationMillis = 49122
====================================`
CODE
HomeFragment
`override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
viewPager2 = view.findViewById(R.id.viewPager)
viewPager2.adapter = RadioStationFragmentAdapter(initChannels(), childFragmentManager, viewLifecycleOwner.lifecycle)
viewPager2.registerOnPageChangeCallback(callback)
viewPager2.offscreenPageLimit = 2
viewPager2.isSaveEnabled = false
radioSwitcher = view.findViewById(R.id.radioSwitcher)
return view
}
override fun onDestroyView() {
viewPager2.adapter = null
super.onDestroyView()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
radioSwitcher.setChannels(initChannels())
radioSwitcher.setSwipe(swipeListener)
}
private val swipeListener = object : SwipeListener {
override fun right() {
Log.e("Swiped", "${viewPager2?.currentItem}")
viewPager2?.currentItem = viewPager2?.currentItem!! - 1
}
override fun left() {
viewPager2?.currentItem = viewPager2?.currentItem!! + 1
Log.e("Swiped", "rf")
}
}
private val callback = object : ViewPager2.OnPageChangeCallback(){
override fun onPageSelected(position: Int) {
radioSwitcher.changePosition(position, RadioSwitcher.SwipeDirection.LEFT)
}
}
private fun initChannels(): ArrayList<Channel>{
val list = ArrayList<Channel>()
list.add(Channel("Test"))
list.add(Channel("Test2"))
list.add(Channel("Test3"))
list.add(Channel("Test4"))
list.add(Channel("Test5"))
list.add(Channel("Test6"))
list.add(Channel("Test7"))
return list
}`
FragmentStateAdapter
`
class RadioStationFragmentAdapter(var data: ArrayList, fragmentManager: FragmentManager, lifecycle: Lifecycle): FragmentStateAdapter(fragmentManager, lifecycle) {
private val hashMap = HashMap<Int, Fragment>()
override fun getItemCount(): Int = if (data.isNotEmpty()) Int.MAX_VALUE / 6 else 0
override fun createFragment(position: Int): Fragment {
when(position % data!!.size){
0 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag);
return mFrag
}
1 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag);
return mFrag
}
2 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag);
return mFrag
}
3 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag);
return mFrag
}
4 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag)
return mFrag
}
5 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag);
return mFrag
}
6 -> {
val mFrag = TestFragment()
hashMap.put(position, mFrag);
return mFrag
}
}
return Fragment()
}
}
`
TestFragment
`
class TestFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_test, container, false)
}
override fun onResume() {
super.onResume()
Log.e("TAG", "RESUME")
}
override fun onDestroyView() {
super.onDestroyView()
}
}`
Does anyone have an idea how this can be fixed? I want to avoid memory leaks in code, espcially working with primitive views like viewPager. I would be glad for any suggestion.
same to you ,viewpager2 make memory leak,but i not find any useful issues to fix it