关于viewpage + FragmentPagerAdapter 当调用 notifydatasetchanged 造成其中的fragment内存泄漏

ViewPager 和 FragmentPagerAdapter 的使用中,调用 notifyDataSetChanged() 可能导致内存泄漏,这通常与 Android 系统管理 Fragment 的方式有关。为了深入理解这个问题,我们需要从 FragmentPagerAdapter 和 ViewPager 的源码入手。

FragmentPagerAdapter 和 FragmentStatePagerAdapter 的源码分析

FragmentPagerAdapter 源码分析

FragmentPagerAdapter 是 PagerAdapter 的子类,它主要用于管理少量静态页面。它的工作原理是将每个页面作为一个 Fragment 保存在内存中,并在需要时显示或隐藏它们。

以下是 FragmentPagerAdapter 中相关的部分源码:

 public abstract class FragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentPagerAdapter";
    private static final boolean DEBUG = false;
    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private Fragment mCurrentPrimaryItem = null;

    public FragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        // 获取 Fragment 的标签
        final long itemId = getItemId(position);

        // 检查是否已经存在这个 Fragment
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            mCurTransaction.add(container.getId(), fragment, name);
        }

        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        mCurTransaction.detach((Fragment) object);
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }

    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }
}

FragmentStatePagerAdapter 源码分析

FragmentStatePagerAdapter 是 FragmentPagerAdapter 的一个子类,更适合大量页面或动态变化的场景。它会在页面销毁时,销毁整个 Fragment 实例,而不仅仅是视图。

以下是 FragmentStatePagerAdapter 中相关的部分源码:

public abstract class FragmentStatePagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentStatePagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;

    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();

    public FragmentStatePagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }

        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, fragment.isAdded() ?
                mFragmentManager.saveFragmentInstanceState(fragment) : null);
        mFragments.set(position, null);

        mCurTransaction.remove(fragment);
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }
}

内存泄漏的根本原因

从上述源码分析可以看到,内存泄漏的根本原因是 FragmentPagerAdapter 并不会销毁 Fragment 实例,只会分离它们的视图,而 FragmentStatePagerAdapter 则会销毁整个 Fragment 实例。

  1. FragmentPagerAdapter 的行为
    FragmentPagerAdapter 在 destroyItem 方法中调用 detach,只是分离了 Fragment 的视图,没有销毁 Fragment 实例。这意味着 Fragment 实例仍然存在于内存中,可能会导致内存泄漏,尤其是在频繁更新页面内容时。
  2. FragmentStatePagerAdapter 的行为
    FragmentStatePagerAdapter 在 destroyItem 方法中调用 remove,销毁整个 Fragment 实例,并保存其状态。在需要时重新创建 Fragment 实例。这种方式更适合动态页面的情况,可以避免内存泄漏。

解决方案一:使用 FragmentStatePagerAdapter

对于页面数量较多或内容动态变化的情况,使用 FragmentStatePagerAdapter 可以有效避免内存泄漏,因为它会销毁整个 Fragment 实例。
在 FragmentPagerAdapter 中手动管理 Fragment 的生命周期

如果必须使用 FragmentPagerAdapter,确保在调用 notifyDataSetChanged() 时,手动管理 Fragment 的生命周期,正确移除不再需要的 Fragment 实例。

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    mCurTransaction.remove(fragment);
}

避免对 Activity 或 Fragment 的强引用
在 Fragment 中避免持有对 Activity 的强引用,使用弱引用或在适当的时候手动清理引用。

解决方案二: 手动管理 Fragment 的生命周期

虽然 FragmentPagerAdapter 不会自动销毁 Fragment 实例,我们可以在notifydatasetchanged 之前,对adapter中的 fragment 进行手动移除 但你可以在 destroyItem() 方法中手动移除 Fragment。这样可以确保 Fragment 不会继续占用内存。

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;
    FragmentManager fragmentManager = fragment.getFragmentManager();
    if (fragmentManager != null) {
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.remove(fragment);
        fragmentTransaction.commitNowAllowingStateLoss();
    }
}

最后

我们需要注意:

1.在 Fragment 中避免持有对 Activity 的强引用

Fragment 持有 Activity 的强引用会导致内存泄漏。在 Fragment 中可以使用弱引用来持有 Activity。

public class MyFragment extends Fragment {
    private WeakReference<Activity> activityReference;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        activityReference = new WeakReference<>(activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        activityReference.clear();
    }

    private Activity getActivityReference() {
        return activityReference.get();
    }
}
2.在 Fragment 中避免静态引用

避免在 Fragment 中使用静态变量持有 Activity 或 View 的引用,因为静态变量的生命周期与应用程序一致,可能会导致内存泄漏。

3.清理未使用的 Fragment

确保在不再需要某个 Fragment 时,手动清理它。例如,在 onDestroy() 或其他适当的位置,确保引用被清理。

@Override
public void onDestroy() {
    super.onDestroy();
    // 清理未使用的 Fragment
    if (myFragment != null) {
        FragmentManager fragmentManager = getFragmentManager();
        if (fragmentManager != null) {
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.remove(myFragment);
            fragmentTransaction.commitNowAllowingStateLoss();
        }
    }
}
4.适当地使用 setRetainInstance(false)

如果 Fragment 设置了 setRetainInstance(true),会导致 Fragment 在 Activity 重建时保留,可能会导致内存泄漏。在不需要保留 Fragment 实例时,确保 setRetainInstance(false)。

复制代码
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(false); // 确保 Fragment 不被保留
}
5.使用 WeakReference 或其他方式避免对外部资源的强引用

在 Fragment 中,尽量使用 WeakReference 或其他方式避免对外部资源(如大对象、静态资源、上下文等)的强引用。

public class MyFragment extends Fragment {
    private WeakReference<Context> contextReference;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        contextReference = new WeakReference<>(context);
    }

    private Context getContextReference() {
        return contextReference.get();
    }
}
6.使用 FragmentManager 正确管理 Fragment

在需要替换或移除 Fragment 时,使用 FragmentManager 和 FragmentTransaction 正确管理 Fragment 的生命周期。

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragment = fragmentManager.findFragmentByTag("TAG");
if (fragment != null) {
    fragmentTransaction.remove(fragment);
}

fragmentTransaction.commitNowAllowingStateLoss();

7. 避免 Fragment 中长生命周期的任务

在 Fragment 中避免执行长生命周期的任务,如异步任务、后台线程等。如果必须执行,确保在 Fragment 销毁时取消或停止这些任务。

@Override
public void onDestroy() {
    super.onDestroy();
    // 取消或停止长生命周期的任务
    if (asyncTask != null) {
        asyncTask.cancel(true);
    }
}

通过以上措施,可以有效减少或避免在使用 FragmentPagerAdapter 时导致的内存泄漏问题。确保在 Fragment 和 Activity 的生命周期中正确管理资源和引用,可以显著提高应用的内存使用效率。

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ViewPage FragmentAndroid 中的一个工具类,用于在一个 Activity 中显示多个 Fragment,并且可以通过滑动来切换不同的 Fragment。仿微信的话,可以使用 ViewPage Fragment 来实现类似微信的主界面,其中每个 Fragment 分别对应微信的不同功能模块。 首先,我们可以创建一个主界面的 Activity,该 Activity 包含一个 ViewPage,用于显示不同的 Fragment。然后,创建多个 Fragment,每个 Fragment 分别负责显示微信的不同功能模块,比如聊天列表、联系人列表、发现等。 在每个 Fragment 中,可以使用 RecyclerView 来展示列表数据,并根据不同的业务需求进行相应的逻辑处理。比如,在聊天列表中,可以显示每个聊天会话的头像、昵称、最近一条消息等信息,并通过点击监听实现跳转到聊天界面的功能。 另外,可以为每个 Fragment 添加相应的菜单选项,仿微信的底部导航栏,用于在不同的 Fragment 之间进行切换。通过监听导航栏的点击事件,并配合 ViewPage 的滑动,可以实现类似微信的切换效果。 此外,可以通过 FragmentManager 来管理 Fragment 的生命周期,并实现 Fragment 之间的通信。比如,在聊天界面中发送一条消息后,可以通过调用 FragmentManager 的方法刷新聊天列表界面,并更新最近一条消息的显示。 总而言之,使用 ViewPage Fragment 可以很方便地实现一个仿微信的界面,通过不同的 Fragment 展示不同的功能模块,使用户可以在同一个 Activity 中进行多个功能的切换和操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值