android / views-widgets-samples

Multiple samples showing the best practices in views-widgets on Android.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  1. MainActivity - here I initialize the views and setup Navigation component to navigate using bottom navigation view.
  2. HomeFragment and Profile Fragment
  3. In HomeFragment I have ViewPager2
  4. 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