一次Android Fragment内存泄露的bug解决记录|Fragment not attach to an Activity

news2025/5/11 21:27:44

Bug描述

前些天出现了一个 bug。Activity 页面里放了一个 ViewPager2,其中的每一页是一个 Fragment。其中第一页的 Fragment 实现了一个监听器,当事件发生和首次添加到监听器管理者 listener manager 时,manager 会通知所有监听者,监听器的回调需要用到当前 Activity 实现一些逻辑。但是在调用requireActivity()获取 activity 时,页面偶尔会发生 crash,报错提示Exeception: Fragment not attach to an Activity

因为是偶现的,于是排先了两个小时的原因😭,在这里记录下来,或许能给大家提供些经验。

场景复现

页面如图一所示
在这里插入图片描述

图二 页面布局

简化代码如下

class MyActivity:AppCompatActivity(){
    ...
    overide fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        // 添加Fragment
        val myList = mutableListOf<MyFragment>()
        myList.add(MyFragment())
        myList.add(MyFragment())
        myList.add(MyFragment())
        val myAdapter = ViewPagerTwoAdapter() // 一个继承了FragmentStateAdapter
        val viewPager = findViewById(R.id.vp)
        viewPager.adapter = myAdapter
        ...
        
    }
    ...

}

class MyFragment:Fragment(), MyChangeListenr{
    ...
    override fun onStart(){
        super.onStart()
        myChangeListenerManager.add(this) // 当事件发生和首次添加时,manager会通知所有监听者
    }
    
    override fun doOnChange(){
        val act = requireActivity() // 这行代码报错not attach to an activity,偶现
        ...
    }
    ...
    
}

interface MyChangeListenr{
    fun doOnChange()
}

其实看到这里,相信大家已经觉得有些不对劲了,在 Fragment 的 onStart() 函数中,把此 Fragment 作为监听器添加到了 manager 中,但是没有发现相关的 remove,也许就是因为这个,manager 始终持有此 Fragment的 引用,导致发生了内存泄露。但我们还需要一些证据。

分析调试

刚开始遇到这个bug时,我首先想到的:会不会是事件发生的时机正好处在Fragment拿不到Activity的生命周期?于是我再去复习 Fragment 的生命周期。

Fragment的主要生命周期方法依次是onAttachonCreateonCreateViewonViewCreatedonStartonResumeonPauseonStoponDestroyViewonDestoryonDetach

在 onAttach 执行之后就可以通过 requireActivity 获取 Fragment 所在的 Activity 了,上面我们的时间监听是在 onStart 里才添加的,onStart之后早已可以获取 Activity。接下来通过添加日志,再次检查事件的触发时机。

class MyFragment:Fragment(), MyChangeListenr{
    ...
    overide fun onAttach(context: Context){
        super.onAttach(context)
        Log.d("lxl","onAttach")
    }
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        Log.d("lxl","onCreate")
    }
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?){
        super.onCreateView(inflater, container, savedInstanceState)
        Log.d("lxl","onCreateView")
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?){
        super.onViewCreated(view, savedInstanceState)
        Log.d("lxl","onViewCreated")
    }
    override fun onStart(){
        super.onStart()
        Log.d("lxl","onViewCreated")
        myChangeListenerManager.add(this) // 当事件发生和首次添加时,manager会通知所有监听者
    }
    override fun onResume(){
        super.onResume()
        Log.d("lxl","onResume")
    }
    override fun onPause(){
        super.onPause()
        Log.d("lxl","onPause")
    }
    override fun onDestoryView(){
        super.onDestoryView()
        Log.d("lxl","onDestoryView")
    }
    override fun onDestry(){
        super.onDestry()
        Log.d("lxl","onDestry")
    }
    override fun onDetach(){
        super.onDetach()
        Log.d("lxl","onDetach")
    }
    
    override fun doOnChange(){
        if(isAdd){ // Fragment里的判断是否attach到Activity的方法
            Log.d("lxl","已绑定")
        }else{
            Log.d("lxl","未绑定")
        }
        
        val act = requireActivity() // 这行代码报错not attach to an activity,偶现
        ...
    }
    ...
    
}

我们尝试运行,第一次进入页面,显示如下

onAttach
onCreate
onCreateView
onViewCreated
onStart
onResume
已绑定

关闭再打开Activity,第二次进入页面crash,显示如下

onAttach
onCreate
onCreateView
onViewCreated
onStart
onResume
未绑定
已绑定

关闭再打开Activity,第三次进入页面crash,显示如下

onAttach
onCreate
onCreateView
onViewCreated
onStart
onResume
未绑定
未绑定
已绑定

可以看出,之后的每一次进入都会打印出来一个未绑定,这说明有多个 Fragment 都收到了监听,那么这些 Fragment 是从哪来的呢?原来是前面退出的 Activity 的 Fragment 没有被释放掉,仍然处在manager 的监听器列表里,这是一种内存泄露。所以我们在不使用 Fragment 时需要移除监听,代码如下

class MyFragment:Fragment(), MyChangeListenr{
    ...
    override fun onStart(){
        super.onStart()
        myChangeListenerManager.add(this) // 当事件发生和首次添加时,manager会通知所有监听者
    }
    
    override fun onStop(){
        super.onStop()
        myChangeListenerManager.remove(this) // 当Fragment消失,需要移除监听。防止持有引用,仍然被通知,这是一种内存泄露。
    }
    
    override fun doOnChange(){
        val act = requireActivity() // 这行代码报错not attach to an activity,偶现
        ...
    }
    ...
    
}

留下几个疑惑有待分析

  1. Fragment 被 manager 持有引用无法释放,那么 Fragment 会不会持有 Activity 的引用,导致Activity 无法释放?在这里是 ViewPager2 把 Fragment 和 Activity 绑定,理论上 ViewPager2 会把两者的绑定去掉,可以去看 ViewPager2 源码和发生 bug 时任务栈的情况
  2. Fragment 的 Stop 函数的执行时机与可见性的关系

想说的话

  1. 内存泄露是一个新手听了感到棘手的问题,但实际上产生的原因可能很简单,不用生畏。
  2. 解决 bug 可以通过形式上的合规,修改不规范的地方,这样工作效率更快。如果有余力的情况下可以分析一下 bug 产生的原因,从根本上避免能积累更多经验,避免头痛医头脚痛医脚。
  3. 学习 Android / 移动端 很开心,请各位大佬批评指正,也可以交个朋友互相交流!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LVGL图像导入和解码

LVGL版本&#xff1a;8.1 概述 在LVGL中&#xff0c;可以导入多种不同类型的图像&#xff1a; 经转换器生成的C语言数组&#xff0c;适用于页面中不常改变的固定图像。存储系统中的外部图像&#xff0c;比较灵活&#xff0c;可以通过插卡或从网络中获取&#xff0c;但需要配置…

project从入门到精通(五)

目录 创建资源的基本信息 在project中创建资源工作表 ​编辑信息详解 最大单位 标准费率与加班费率 每次使用成本 成本累算 基准日历 三类资源工作表的总结——不同的资源必须要设置的属性 除了资源名称是必须设置的之外&#xff0c;剩余的资源的可设置选项如下图所…

第3.2.3节 Android动态调用链路的获取

3.2.3 Android App动态调用链路 在Android应用中&#xff0c;动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系&#xff0c;特别是在应用的复杂逻辑中&#xff0c;理解这些调用链路对于调试和性能优化非常重要。 1&#xff0c;动态调用链路获…

亿级流量系统架构设计与实战(六)

微服务架构与网络调用 当某个业务从单体服务架构转变为微服务架构后,多个服务之间会通过网络调用形式形成错综复杂的依赖关系。 在微服务架构中 , 一个微服务正常工作依赖它与其他微服务之间的多级网络调用。 网络是脆弱的 , RPC 请求有较大的概率会遇到超时 、 抖动 、 断…

浅聊find_package命令的搜索模式(Search Modes)

背景 find_package应该算是我们使用最多的cmake命令了。但是它是如何找到上游库的.cmake文件的&#xff1f; 根据官方文档&#xff0c;整理下find_package涉及到的搜索模式。 搜索模式 find_package涉及到的搜索模式有两种&#xff1a;模块模式(Module mode)和配置模式(Conf…

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B 本地环境说明禁用开源驱动nouveau安装nvidia-smi安装Git环境安装Anaconda(conda)环境下载DeepSeek-R1-Distill-Qwen-7B模型安装LLaMA-Factory下载LLaMA-Factory安装LLaMA-Factory依赖修改环境变量安装deepspeedA…

使用lldb查看Rust不同类型的结构

目录 前言 正文 标量类型 复合类型——元组 复合类型——数组 函数 &str struct 可变数组vec Iter String Box Rc Arc RefCell Mutex RwLock Channel 总结 前言 笔者发现这个lldb挺好玩的&#xff0c;可以查看不同类型的结构&#xff0c;虽然这好像是C的东…

【Linux】线程POSIX信号量

目录 1. 整体学习思维导图 2. 信号量的概念 3. 基本接口 4. 基于环形队列的生产者消费者模型(信号量) 1. 整体学习思维导图 2. 信号量的概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的。但 POSIX可以用于线…

MySQL事务和JDBC中的事务操作

一、什么是事务 事务是数据库操作的最小逻辑单元&#xff0c;具有"全有或全无"的特性。以银行转账为例&#xff1a; 典型场景&#xff1a; 从A账户扣除1000元 向B账户增加1000元 这两个操作必须作为一个整体执行&#xff0c;要么全部成功&#xff0c;要么全部失败…

每日脚本学习5.10 - XOR脚本

xor运算的简介 异或就是对于二进制的数据可以 进行同0异1 简单的演示 &#xff1a; 结果是 这个就是异或 异或的作用 1、比较两数是否相等 2、可以进行加密 加密就是需要key 明文 :0b010110 key : 0b1010001 这个时候就能进行加密 明文 ^ key密文 还有这个加密比…

【编译原理】总结

核心 闭包&#xff0c;正则闭包 产生式&#xff08;规则&#xff09; 文法 G[S](&#xff0c;&#xff0c;P&#xff0c;S) 一组规则的集合 &#xff1a;非终结符 &#xff1a;终结符 P&#xff1a;产生式 S&#xff1a;开始符号 推导 归约 规范&#xff08;最右&#xff…

docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤

备忘&#xff1a;后续偶尔忘记了docker虚拟机与宿主机的端口映射关系&#xff0c;来这里查看即可&#xff1a; docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…

OpenVLA:开源的视觉-语言-动作模型

1. 简介 让我们先来介绍一下什么是OpenVLA&#xff0c;在这里&#xff1a; https://openvla.github.io/ 可以看到他们的论文、数据、模型。 OpenVLA 是一个拥有 70亿参数的开源 **视觉-语言-动作&#xff08;VLA&#xff09;**模型。它是在 Open X-Embodiment 数据集 中的 97万…

Matlab/Simulink的一些功能用法笔记(4)

水一篇帖子 01--MATLAB工作区的保护眼睛颜色设置 默认的工作区颜色为白色 在网上可以搜索一些保护眼睛的RGB颜色参数设置 在MATLAB中按如下设置&#xff1a; ①点击预设 ②点击颜色&#xff0c;点击背景色的三角标符号 ③点击更多颜色&#xff0c;找到RGB选项 ④填写颜色参数…

Elasticsearch:我们如何在全球范围内实现支付基础设施的现代化?

作者&#xff1a;来自 Elastic Kelly Manrique SWIFT 和 Elastic 如何应对基础设施复杂性、误报问题以及日益增长的合规要求。 金融服务公司在全球范围内管理实时支付方面面临前所未有的挑战。SWIFT&#xff08;Society for Worldwide Interbank Financial Telecommunication -…

matlab介绍while函数

MATLAB 中的 while 语句介绍 在 MATLAB 中&#xff0c;while 语句是一种循环结构&#xff0c;用于在满足特定条件时反复执行一段代码块。与 for 循环不同&#xff0c;while 循环的执行次数是动态的&#xff0c;取决于循环条件是否为真。 语法 while condition% 循环体代码 e…

如何解决 PowerShell 显示 “此系统上禁用了脚本运行” 的问题

在 Windows 11 或 10 的 PowerShell 中运行脚本时,你可能会遇到一个错误,提示系统上禁用了脚本运行。这是一种安全功能,而不是系统问题,旨在防止可能有害的脚本自动运行。然而,如果你需要运行脚本来完成某些任务,或者你在系统上做了软件开发或测试的环境,那么你需要在 P…

深入浅出之STL源码分析4_类模版

1.引言 我在上面的文章中讲解了vector的基本操作&#xff0c;然后提出了几个问题。 STL之vector基本操作-CSDN博客 1.刚才我提到了我的编译器版本是g 11.4.0&#xff0c;而我们要讲解的是STL&#xff08;标准模板库&#xff09;&#xff0c;那么二者之间的关系是什么&#x…

初学者入门指南:什么是网络拓扑结构?

初学者入门指南&#xff1a;什么是网络拓扑结构&#xff1f; 在构建或学习计算机网络时&#xff0c;一个绕不开的核心概念便是“网络拓扑结构”&#xff08;Network Topology&#xff09;。它决定了网络中各个设备如何连接、通信以及如何扩展。理解网络拓扑不仅有助于我们更清…

Satori:元动作 + 内建搜索机制,让大模型实现超级推理能力

Satori&#xff1a;元动作 内建搜索机制&#xff0c;让大模型实现超级推理能力 论文大纲一、背景&#xff1a;LLM 推理增强的三类方法1. 基于大规模监督微调&#xff08;SFT&#xff09;的推理增强2. 借助外部机制在推理时进行搜索 (RLHF / 多模型 / 工具)3. 现有局限性总结 二…