【发布时间】:2021-04-26 00:11:42
【问题描述】:
仅供参考:问题很简短,但以防万一我在最后添加了更多可能相关的信息。
我需要一个无限滚动的 ViewPager2,我想重用项目中的 Fragment,因为它已经设计好并且调用已经很好地使用它的 viewLifeCycle.. 此外,我知道 VP 会回收屏幕外的 Fragments(显示的 Fragment 的 1 个偏移位置),并且在任何给定时刻至少有多达 3 个 Fragments,因此选择使用 Fragments。
问题在于,当转到第四页时,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);
MyPagerAdapter.class 扩展 FragmentStateAdapter:
@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 祖先)是我们从 Home Fragment 导航到的 BackStackEntry,此视图包含一个工具栏,其中包含有关应用程序的主要信息,下面的工具栏是主要的这个视图的内容,一个带有 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 的第一页(Fragment)可以采用 3 个不同的 Fragment 的形式(取决于 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
【问题讨论】:
标签: android-fragments view memory-leaks android-viewpager2 android-ondestroy