Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fragment is leaking in ViewPager2 onDestroyView #239

Open
sergeykonar opened this issue May 4, 2022 · 1 comment
Open

Fragment is leaking in ViewPager2 onDestroyView #239

sergeykonar opened this issue May 4, 2022 · 1 comment

Comments

@sergeykonar
Copy link

sergeykonar commented May 4, 2022

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.

@SprrowZ
Copy link

SprrowZ commented Dec 8, 2022

same to you ,viewpager2 make memory leak,but i not find any useful issues to fix it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants
@SprrowZ @sergeykonar and others