仅供参考:这个问题很简短,但以防万一我在最后添加了更多可能相关的信息。
我需要一个无限滚动的 ViewPager2,并且我想重用项目中的一个 Fragment,因为它已经被设计并且调用已经很好地建立了它的 viewLifeCycle。我也知道 VP 回收了屏幕外的 Fragments(显示的 Fragment 的 1 个偏移位置)和在任何给定时刻至少有多达 3 个片段,因此使用片段是选择。
问题是,当转到第四页时,ViewPager2 尝试删除第一个 Fragment(如预期的那样),LeakCanary 向我显示了这一点(最后是整个诊断。):
D/LeakCanary: Watching instance of androidx.core.widget.NestedScrollView (com.****.****.ui.***.pages.add_element.SearchPageFragment2 received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks)) with key 294cd9eb-3d6a-4c98-a69c-5d20e4c1652f
诊断从不指向我的引用,只指向 android 库引用。
在下面的代码之前,我有更多的行,但我一直在修剪它们,直到保持最低限度并且泄漏仍然存在。
// ----- onViewCreated() ------
MyPagerAdapter mPa = new MyPagerAdapter(
getChildFragmentManager(),
getViewLifecycleOwner().getLifecycle()
);
vp.setAdapter(mPa);
扩展 FragmentStateAdapter 的 MyPagerAdapter.class:
@NonNull
@Override
public Fragment createFragment(int position) {
return new SearchPageFragment2(); //Test Fragment
}
@Override
public int getItemCount() {
return 8; //Test fixed number
}
泄漏片段:
public class SearchPageFragment2 extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return FragmentSearchPageBinding.inflate(inflater).getRoot();
}
}
是什么导致内存泄漏?
问题到此结束。
前言...
主视图(泄漏发生时显示的最远的 Fragment 祖先)是一个 BackStackEntry,我们从 Home Fragment 导航到该视图,该视图包含一个工具栏,其中包含有关应用程序的主要信息,下面的工具栏是这个的主要内容视图,一个具有 3 个片段的固定大小的 ViewPager2,在第一个片段上......我创建了一个“MutableFrameLayout”:
private final MutableFrameLayoutAdapter<ElementDBLoaderViewModel.FrameActions> adapter = new MutableFrameLayoutAdapter<>(
this, //Fragment owner
this::getChildFragmentManager, //FragmentManager supplier
() -> ElementDBLoaderViewModel.FrameActions.initiating, //initialValue
action -> { //Function<X, Fragment>
switch (action) {
case crossed:
return new AddElementExpandedFragment();
case not_crossed:
return new AddElementFragment();
case explore:
return new MainDBPaginationFragment2();
}
return null;
}
);
所以它可以像这样使用:
binding.fragmentContainer.setAdapter(adapter);
adapter.changeContent(ElementDBLoaderViewModel.FrameActions.crossed)
该组件是防漏的,在不同情况下进行了数小时的测试。
该组件的主要“引擎”:
……
if (oldFragment != null) {
FragmentTransaction ft = stackFm.beginTransaction();
ft.remove(oldFragment);
addCommit(ft, newFragment);
}
其中 "stackFm" 是在构造函数中使用childFragmentManager.get()
(" this::getChildFragmentManager
")获取 FragmentManager 供应商的结果
……
private void addCommit(FragmentTransaction ft, Fragment newFragment) {
fragmentCreated.get().fragmentCreated(newFragment); //stateless adapter interface reference
ft.add(getId(), newFragment);
ft.commit();
}
……
这个想法是有一个易于使用的组件,没有什么花哨的和直接的。
基本上,放置此 MutableFrameLayout 的固定大小的 ViewPager2 的第一页(片段)可以采用 3 个不同片段的形式(取决于 DB 大小)。
泄漏的 ViewPager2 位于内部MainDBPaginationFragment2.class
,但在到达 MainDBPaginationFragment2 片段之前,我们必须先通过AddElementExpandedFragment.class
.
泄漏诊断(没有参考文献是我的)
┬───
│ GC Root: System class
│
├─ android.view.WindowManagerGlobal class
│ Leaking: NO (DecorView↓ is not leaking and a class is never leaking)
│ ↓ static WindowManagerGlobal.sDefaultWindowManager
├─ android.view.WindowManagerGlobal instance
│ Leaking: NO (DecorView↓ is not leaking)
│ ↓ WindowManagerGlobal.mViews
├─ java.util.ArrayList instance
│ Leaking: NO (DecorView↓ is not leaking)
│ ↓ ArrayList.elementData
├─ java.lang.Object[] array
│ Leaking: NO (DecorView↓ is not leaking)
│ ↓ Object[].[0]
├─ 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.****.****.ui.MainActivity with mDestroyed = false
│ ↓ DecorView.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.elementData
├─ java.lang.Object[] array
│ Leaking: NO (ViewPager2$RecyclerViewImpl↓ is not leaking)
│ ↓ Object[].[2]
├─ 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.****.****.ui.MainActivity with
│ mDestroyed = false
│ ↓ ViewPager2$RecyclerViewImpl.mRecycler
│ ~~~
├─ androidx.recyclerview.widget.RecyclerView$Recycler instance
│ Leaking: UNKNOWN
│ Retaining 48453 bytes in 442 objects
│ ↓ RecyclerView$Recycler.mRecyclerPool
│ ~~~~~
├─ androidx.recyclerview.widget.RecyclerView$RecycledViewPool instance
│ Leaking: UNKNOWN
│ Retaining 46192 bytes in 424 objects
│ ↓ RecyclerView$RecycledViewPool.mScrap
│ ~~
├─ android.util.SparseArray instance
│ Leaking: UNKNOWN
│ Retaining 46176 bytes in 423 objects
│ ↓ SparseArray.mValues
│ ~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 46111 bytes in 421 objects
│ ↓ Object[].[0]
│ ~
├─ androidx.recyclerview.widget.RecyclerView$RecycledViewPool$ScrapData instance
│ Leaking: UNKNOWN
│ Retaining 46067 bytes in 420 objects
│ ↓ RecyclerView$RecycledViewPool$ScrapData.mScrapHeap
│ ~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ Retaining 46035 bytes in 419 objects
│ ↓ ArrayList.elementData
│ ~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 46015 bytes in 418 objects
│ ↓ Object[].[0]
│ ~
├─ androidx.viewpager2.adapter.FragmentViewHolder instance
│ Leaking: UNKNOWN
│ Retaining 43762 bytes in 400 objects
│ ↓ FragmentViewHolder.itemView
│ ~~~~
├─ android.widget.FrameLayout instance
│ Leaking: UNKNOWN
│ Retaining 43677 bytes in 399 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.null
│ View.mWindowAttachCount = 1
│ mContext instance of com.****.****.ui.MainActivity with
│ mDestroyed = false
│ ↓ FrameLayout.mMatchParentChildren
│ ~~~~~~~~
├─ java.util.ArrayList instance
│ Leaking: UNKNOWN
│ Retaining 41573 bytes in 385 objects
│ ↓ ArrayList.elementData
│ ~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ Retaining 41553 bytes in 384 objects
│ ↓ Object[].[0]
│ ~
╰→ androidx.core.widget.NestedScrollView instance
Leaking: YES (ObjectWatcher was watching this because com.****.
****.ui.****.pages.add_element.SearchPageFragment2
received Fragment#onDestroyView() callback (references to its views
should be cleared to prevent leaks))
Retaining 41549 bytes in 383 objects
key = 294cd9eb-3d6a-4c98-a69c-5d20e4c1652f
watchDurationMillis = 7979
retainedDurationMillis = 2978
View not part of a window view hierarchy
View.mAttachInfo is null (view detached)
View.mID = R.id.scrolling_content_table
View.mWindowAttachCount = 1
mContext instance of com.****.****.ui.MainActivity with
mDestroyed = false