问题描述
起初,我在 BottomNavigationView
和数据绑定的选项卡中遇到了 ViewPager2
的问题,数据绑定也随 ViewPager2
泄漏,应该在 onDestroyView
中为空,泄漏并设法将问题缩小到 ViewPager2
,同时使用 findNavController()从包含ViewPager2的片段导航到另一个片段.导航
.
At first, i had the issue for ViewPager2
inside a tab of BottomNavigationView
and data binding , data binding also leaks with ViewPager2
and should be nulled in onDestroyView
, leaking and managed to narrow the issue to ViewPager2
while navigating from fragment that contains ViewPager2 to another fragment using findNavController().navigate
.
这是它的发生方式,当我导航到另一个用ViewPager2替换当前片段的片段时,就会发生这种情况.
Here is how it occurs, it occurs when i navigate to another fragment that replaces the current one with ViewPager2.
这是代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph_parent"
app:startDestination="@id/parent_dest">
<fragment
android:id="@+id/parent_dest"
android:name="com.smarttoolfactory.tutorial6_7navigationui_memoryleakcheck.viewpagerfragment.ViewPagerContainerFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_viewpager_container">
<!-- Login -->
<action
android:id="@+id/action_main_dest_to_loginFragment2"
app:destination="@id/loginFragment2" />
</fragment>
<!-- Login -->
<fragment
android:id="@+id/loginFragment2"
android:name="com.smarttoolfactory.tutorial6_7navigationui_memoryleakcheck.blankfragment.LoginFragment2"
android:label="LoginFragment2"
tools:layout="@layout/fragment_login2"/>
</navigation>
包含 ViewPager2
和 TabLayout
class ViewPagerContainerFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_viewpager_container, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ViewPager2
val viewPager = view.findViewById<ViewPager2>(R.id.viewPager)
/*
Set Adapter for ViewPager inside this fragment using this Fragment,
more specifically childFragmentManager as param
*/
viewPager.adapter = ChildFragmentStateAdapter(this)
// TabLayout
val tabLayout = view.findViewById<TabLayout>(R.id.tabLayout)
// Bind tabs and viewpager
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
when (position) {
0 -> tab.text = "Home"
1 -> tab.text = "Dashboard"
2 -> tab.text = "Notification"
3 -> tab.text = "Login"
}
}.attach()
}
}
fragment_viewpager_container
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabMode="scrollable" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
片段没有什么特别之处,但我添加了一种布局,也许Material widgets正在泄漏,我不知道
Nothing special with fragments but i add one of the layouts, maybe Material widgets are leaking, i don't know
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fragment_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorHome1"
android:padding="8dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Home Fragment1"
android:textColor="#fff"
android:textSize="32sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnNextPage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Next Page"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
从Leak Canary堆放垃圾
And heap dump from Leak Canary
┬───
│ GC Root: System class
│
├─ android.app.ActivityThread class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static ActivityThread.sCurrentActivityThread
├─ android.app.ActivityThread instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ActivityThread.mTopActivityClient
├─ android.app.ActivityThread$ActivityClientRecord instance
│ Leaking: NO (MainActivity↓ is not leaking)
│ ↓ ActivityThread$ActivityClientRecord.activity
├─ com.smarttoolfactory.tutorial6_7navigationui_memoryleakcheck.MainActivity instance
│ Leaking: NO (NavHostFragment↓ is not leaking and Activity#mDestroyed is false)
│ ↓ MainActivity.mFragments
├─ androidx.fragment.app.FragmentController instance
│ Leaking: NO (NavHostFragment↓ is not leaking)
│ ↓ FragmentController.mHost
├─ androidx.fragment.app.FragmentActivity$HostCallbacks instance
│ Leaking: NO (NavHostFragment↓ is not leaking)
│ ↓ FragmentActivity$HostCallbacks.mFragmentManager
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: NO (NavHostFragment↓ is not leaking)
│ ↓ FragmentManagerImpl.mPrimaryNav
├─ androidx.navigation.fragment.NavHostFragment instance
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking and Fragment#mFragmentManager is not null)
│ ↓ NavHostFragment.mChildFragmentManager
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking)
│ ↓ FragmentManagerImpl.mFragmentStore
├─ androidx.fragment.app.FragmentStore instance
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking)
│ ↓ FragmentStore.mActive
├─ java.util.HashMap instance
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking)
│ ↓ HashMap.table
├─ java.util.HashMap$Node[] array
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking)
│ ↓ HashMap$Node[].[0]
├─ java.util.HashMap$Node instance
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking)
│ ↓ HashMap$Node.value
├─ androidx.fragment.app.FragmentStateManager instance
│ Leaking: NO (ViewPagerContainerFragment↓ is not leaking)
│ ↓ FragmentStateManager.mFragment
├─ com.smarttoolfactory.tutorial6_7navigationui_memoryleakcheck.viewpagerfragment.ViewPagerContainerFragment instance
│ Leaking: NO (Fragment#mFragmentManager is not null)
│ ↓ ViewPagerContainerFragment.mLifecycleRegistry
│ ~~~~~~
├─ androidx.lifecycle.LifecycleRegistry instance
│ Leaking: UNKNOWN
│ ↓ LifecycleRegistry.mObserverMap
│ ~~~~
├─ androidx.arch.core.internal.FastSafeIterableMap instance
│ Leaking: UNKNOWN
│ ↓ FastSafeIterableMap.mEnd
│ ~~
├─ androidx.arch.core.internal.SafeIterableMap$Entry instance
│ Leaking: UNKNOWN
│ ↓ SafeIterableMap$Entry.mKey
│ ~~
├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3 instance
│ Leaking: UNKNOWN
│ Anonymous class implementing androidx.lifecycle.LifecycleEventObserver
│ ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.this$1
│ ~~
├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer instance
│ Leaking: UNKNOWN
│ ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer.mViewPager
│ ~~~~
├─ androidx.viewpager2.widget.ViewPager2 instance
│ Leaking: YES (View detached and has parent)
│ mContext instance of com.smarttoolfactory.tutorial6_7navigationui_memoryleakcheck.MainActivity with mDestroyed = false
│ View#mParent is set
│ View#mAttachInfo is null (view detached)
│ View.mID = R.id.viewPager
│ View.mWindowAttachCount = 1
│ ↓ ViewPager
我还添加了 github链接希望自己检查一下或重新创建问题.
I also add github link if you wish to check for yourself or recreate the issue.
推荐答案
从片段的 onDestroyView
方法中的ViewPager2删除适配器,使用 FragmentStateAdapter
Removing adapter from ViewPager2 in onDestroyView
method of fragment solved the memory leak issue with FragmentStateAdapter
override fun onDestroyView() {
val viewPager2 = dataBinding?.viewPager
viewPager2?.let {
it.adapter = null
}
super.onDestroyView()
}
还要在片段的 onDestroyView
中将数据绑定设置为null,我在基本片段中进行了设置,这导致了与数据绑定相关的内存泄漏.或按照此处所述将其用于viewBinding,它适用到数据绑定.
Also set data binding to null in onDestroyView
of fragment, i did it in base fragment, which was causing data binding related memory leak. Or use it as mentioned here for viewBinding, it applies to data binding.
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
防止片段中使用 ViewPager2
导致内存泄漏的另一件事是使用 viewLifeCycleOwner
的lifeCycle,该生命周期介于 onCreateView
和之间在此处中,使用FragmentStateAdapter代替onDestroyView
代替 this
.
Another thing to prevent memory leaks with ViewPager2
inside a fragment is to use viewLifeCycleOwner
's lifeCycle which is between onCreateView
and onDestroyView
instead of this
with FragmentStateAdapter as mentioned here.
FragmentManager fm = getChildFragmentManager();
Lifecycle lifecycle = getViewLifecycleOwner().getLifecycle();
fragmentAdapter = new FragmentAdapter(fm, lifecycle);
这篇关于片段中的ViewPager2在被导航组件Navigation替换它所在的片段后泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!