关于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 的生命周期中正确管理资源和引用,可以显著提高应用的内存使用效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/740227.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测评:【ONLYOFFICE】版本更迭与AI加持下的最新ONLYOFFICE桌面编辑器8.1

你是否还在为没有一款合适的在线桌面编辑器而苦恼&#xff1f;你是否还在因为办公软件的选择过少而只能使用WPS或者office&#xff1f;随着办公需求的不断变化和发展&#xff0c;办公软件也在不断更新和改进。ONLYOFFICE 作为一款全功能办公软件&#xff0c;一直致力于为用户提…

android-aidl4

转&#xff1a;Android Aidl的使用_android aidl使用-CSDN博客 一.准备 Parcelable&#xff0c;可以理解成只是把car整个对象在aidl中进行传递&#xff0c;就理解成一个car的一个类吧&#xff0c;和其他类使用一样就行了&#xff0c;回调&#xff1a;把接口作为参数放在函数参…

场外期权交易流程以及参与方式是什么?

今天带你了解场外期权交易流程以及参与方式是什么&#xff1f;场外期权&#xff0c;是非标准化的期权合约&#xff0c;由买卖双方私下协商达成&#xff0c;灵活性较高。由于这种合约的条款可以根据双方的具体需求进行定制&#xff0c;因此它提供了比交易所交易的标准化期权更多…

多功能推拉力测试机可实现焊球推力测试

LB-8100A 多功能推拉力测试机广泛应于与 LED 封装测试、IC 半导体封 装测试、TO 封装测试、IGBT 功率模块封装测试、光电子元器件封装测试、汽 车领域、航天航空领域、军工产品测试、研究机构的测试及各类院校的测试 研究等应用。 多功能推拉力测试机设置主要结构&#xff1a;…

医疗器械3D全景展会在线漫游创造数字化时代的展览新篇章

在数字化浪潮的引领下&#xff0c;VR虚拟网上展会正逐渐成为企业展示品牌实力、吸引潜在客户的首选平台。我们与广交会携手走过三年多的时光&#xff0c;凭借优质的服务和丰富的经验&#xff0c;赢得了客户的广泛赞誉。 面对传统展会活动繁多、企业运营繁忙的挑战&#xff0c;许…

【MySQL】数据类型和表的约束

1. 数据类型 分类数据类型解释数值类型BIT (M)位类型。M位数&#xff0c;默认为1范围1-64BOOL01表示真假TINYINT [UNSIGNED]8位整型SMALLINT [UNDIGNED]16位短整型INT [UNSIGNED]32位整型BIGINT [UNSIGNED]64位长整型小数类型FLOAT [ (M, D) ] [UNSIGNED]32位浮点类型&#xf…

【机器学习】机器学习重要方法——深度学习:理论、算法与实践

文章目录 引言第一章 深度学习的基本概念1.1 什么是深度学习1.2 深度学习的历史发展1.3 深度学习的关键组成部分 第二章 深度学习的核心算法2.1 反向传播算法2.2 卷积神经网络&#xff08;CNN&#xff09;2.3 循环神经网络&#xff08;RNN&#xff09; 第三章 深度学习的应用实…

群晖NAS部署VoceChat私人聊天系统并一键发布公网分享好友访问

文章目录 前言1. 拉取Vocechat2. 运行Vocechat3. 本地局域网访问4. 群晖安装Cpolar5. 配置公网地址6. 公网访问小结 7. 固定公网地址 前言 本文主要介绍如何在本地群晖NAS搭建一个自己的聊天服务Vocechat&#xff0c;并结合内网穿透工具实现使用任意浏览器远程访问进行智能聊天…

android adb常用命令集

1、系统调试 #adb shell&#xff1a;进入设备的 shell 命令行界面&#xff0c;可以在此执行各种 Linux 命令和特定的 Android 命令。 #adb shell dumpsys&#xff1a;提供关于系统服务和其状态的详细信息。 #adb logcat&#xff1a;实时查看设备的日志信息。可以使用过滤条件来…

浅析Vite本地构建原理

前言 随着Vue3的逐渐普及以及Vite的逐渐成熟&#xff0c;我们有必要来了解一下关于vite的本地构建原理。 对于webpack打包的核心流程是通过分析JS文件中引用关系&#xff0c;通过递归得到整个项目的依赖关系&#xff0c;并且对于非JS类型的资源&#xff0c;通过调用对应的loade…

使用 Reqable 在 MuMu 模拟器进行App抓包(https)

1、为什么要抓包&#xff1f; 用开发手机应用时&#xff0c;查看接口数据不能像在浏览器中可以直接通过network查看&#xff0c;只能借助抓包工具来抓包&#xff0c;还有一些线上应用我们也只能通过抓包来排查具体的问题。 2、抓包工具 实现抓包&#xff0c;需要一个抓包工具…

Java8使用Stream流实现List列表查询、统计、排序、分组、合并

Java8使用Stream流实现List列表查询、统计、排序以及分组 目录 一、查询方法1.1 forEach1.2 filter(T -> boolean)1.3 filterAny() 和 filterFirst()1.4 map(T -> R) 和 flatMap(T -> Stream)1.5 distinct()1.6 limit(long n) 和 skip(long n) 二、判断方法2.1 anyMa…

G7 - Semi-Supervised GAN 理论与实战

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目录 理论知识模型实现引用、配置参数初始化权重定义算法模型模型配置模型训练训练模型 模型效果总结与心得体会 理论知识 在条件GAN中&#xff0c;判别器只用…

轻松搞定数据可视化配色,这份指南助你一臂之力!

配色是数据可视化图表的主要因素。一组合适的配色可以表达数据的重点和趋势&#xff0c;而不良的配色会削弱可视化表达的有效性。在本文中&#xff0c;我将梳理数据可视化中使用的配色板类型&#xff0c;通过案例揭示数据可视化配色技巧&#xff0c;并介绍可生成配色板的插件&a…

Day 32:503. 下一个更大的元素Ⅱ

Leetcode 503. 下一个更大的元素Ⅱ 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个比它…

嵌入式实验---实验七 SPI通信实验

一、实验目的 1、掌握STM32F103SPI通信程序设计流程&#xff1b; 2、熟悉STM32固件库的基本使用。 二、实验原理 1、使用STM32F103R6通过74HC595控制一位LID数码管&#xff0c;实现以下两个要求&#xff1a; &#xff08;1&#xff09;数码管从0到9循环显示&#xff1b; …

[leetcode]add-strings 字符串相加

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:string addStrings(string num1, string num2) {int i num1.length() - 1, j num2.length() - 1, add 0;string ans "";while (i > 0 || j > 0 || add ! 0) {int x i > 0 ? num1[i…

[word] word 如何在文档中进行分栏排版? #媒体#其他#媒体

word 如何在文档中进行分栏排版&#xff1f; 目标效果 将唐代诗人李白的组诗作品《清平调词》进行分栏排版&#xff0c;共分三栏&#xff0c;每一首诗作为一栏&#xff0c;参考效果如下图。

基于STM32的智能健康监测手表

目录 引言环境准备智能健康监测手表系统基础代码实现&#xff1a;实现智能健康监测手表系统 4.1 数据采集模块4.2 数据处理与分析4.3 通信模块实现4.4 用户界面与数据可视化应用场景&#xff1a;健康监测与管理问题解决方案与优化收尾与总结 1. 引言 智能健康监测手表通过使…

ONLYOFFICE 8.1版本桌面编辑器深度体验:创新功能与卓越性能的结合

ONLYOFFICE 8.1版本桌面编辑器深度体验&#xff1a;创新功能与卓越性能的结合 随着数字化办公的日益普及&#xff0c;一款高效、功能丰富的办公软件成为了职场人士的必备工具。ONLYOFFICE团队一直致力于为用户提供全面而先进的办公解决方案。最新推出的ONLYOFFICE 8.1版本桌面编…