首页 > 系统相关 >使用Andorid Studio解决app内存泄漏问题方法与实践

使用Andorid Studio解决app内存泄漏问题方法与实践

时间:2024-03-29 15:30:41浏览次数:19  
标签:XXXFragment return newInstance app add Studio 内存 Andorid fragments

某项目的app运行一段时间(切换页面、触发交互事件等)后就开始严重卡顿,使用top查看内存的使用情况,发现每次操作过后内存都有小幅增长,且永远不下降,存在内存泄露问题。

目录

1 Andorid Studio内存泄露检测工具使用方法

2 内存泄露实例分析

2.1 页面切换后未主动释放

​编辑

2.2 回调未释放

2.3 被更长生命周期的对象持有

2.4 嵌套fragment+viewpager引起的内存泄露

2.5 主页onresume两次


1 Andorid Studio内存泄露检测工具使用方法

android studio中提供了内存泄漏检测工具Profiler,位于下方工具栏中:

点击展开:

电脑端通过usb连接要调试设备后,点击+号,选择对应设备及调试的apk:

可以看到CPU及Memory的实时使用情况,选择的apk包信息将显示在左上角。

点击图片中红框部分可以进入到memory详情部分:

这里提供了更加精细的实时内存抖动情况,并且提供了追踪c/c++、kt、java对象以及heap dump的方法。分析内存泄露问题主要用到第一个 Capture heap dump。

选中后点击Record,输出变化如下:

右侧窗口中会断开一部分,此时正在抓heap dump,设备上同步无法操作。

抓包后分析过程:

输出结果:

关注红框部分,这里是0说明当前状态下没有内存泄露问题。这也是最终想要达到的目标状态。

接下来介绍几个项目中遇到的内存泄露实例是如何使用profiler解决的。

 

2 内存泄露实例分析

这个项目app端的框架代码是其他人负责的,刚拿到的时候里面存在着几十处内存泄漏点,主要可以分为以下类型:页面切换后未主动释放(1)、回调未释放(2)、被更长生命周期的对象持有(3)、嵌套fragment+viewpager引起的内存泄露(4)、主页onresume两次(5)。(按照个人判断归类,如有错误,欢迎指正)

其中套fragment+viewpager引起的内存泄露(4)也算是一个比较重要的知识点。

2.1 页面切换后未主动释放

这是最简单问题,实际上在code review的时候就可以发现。使用Android Studio分析如下:

点击Leaks

依次点击要分析的泄露项,泄露项的References

勾选Show nearest GC root only

这样就得到了需要分析的泄露信息(后续例子只展示Reference,不重复抓取过程),上图的泄露原因比较简单:

instance in Class@xxxxxxxx

类的实例没有释放。

在代码中:当退出当前fragment页面时对实例进行释放。

...

+    override fun onDestroyView() {
+        super.onDestroyView()
+        releaseInstance()
+    }
+

...
 
             return instance!!
         }
+        fun releaseInstance() {
+            instance = null
+        }
     }
 
...

2.2 回调未释放

也是一个比较初级的问题,注册回调后,在页面消失时没有unregister

解决代码:

+        fun unregisterCallback(){
+            this.Callback = null
+        }

2.3 被更长生命周期的对象持有

这里可以看到同一个fragment有两处内存泄漏,上面是刚刚提到的回调未释放,红框部分通过提示信息,可以看到很多lifecycle相关的信息,mLifecycleRegistry in MainActivity。

那就要查这个fragment的生命周期究竟是如何设置的

在创建fragment的地方找到了这样的代码:

adapter = ViewPagerAdapter(this.requireActivity())

直接把当前fragment和整个activity声明周期挂在一起了,问题就出在这里。

修改类定义:

-class ViewPagerAdapter(fragmentActivity: FragmentActivity): FragmentStateAdapter(fragmentActivity) {

+class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle): FragmentStateAdapter(fragmentManager,lifecycle) {

调用处:

adapter = ViewPagerAdapter(childFragmentManager,lifecycle)

对lifecycle生命周期目前的理解还比较有限,只能遇到问题就问题去解决,后续看有没有时间系统的学习一下。

2.4 嵌套fragment+viewpager引起的内存泄露

这是一个使用方式不当造成的问题。在activity下有多个fragment,用viewpager组织,在这些fragment中的某一个中,还要再嵌套多个fragment,同样用viewpager组织。这时需要注意使用方式。

从图中可以看到fragments in xxxfragment,问题出在这里

先来看看代码原本的实现:

class XXXViewPagerAdapter(fm: FragmentManager, private val fragments: ArrayList<Fragment>) :
    FragmentPagerAdapter(fm) {
    override fun getCount(): Int {
        return fragments.size
    }
    override fun getItem(position: Int): Fragment {
        return fragments[position]
    }
}

是通过private val fragments: ArrayList<Fragment>,对多个嵌套的fragment进行管理。

使用处代码:

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        fragments.add(XXXFragment.newInstance())
        binding.viewPager.adapter = XXXViewPagerAdapter(childFragmentManager,fragments)
        binding.viewPager.setNoScroll(true)
        initView()
    }

是以List的形式存储了各个页面,这种方式是会存在内存泄露问题的,所以需要避免这样的使用方式。

改为

class XXXViewPagerAdapter(fm: FragmentManager) :
    FragmentPagerAdapter(fm) {
    override fun getCount(): Int {
        return 10
    }

    override fun getItem(position: Int): Fragment {
        when(position){
            0 -> {
                return XXXFragment.newInstance()
            }
            1 -> {
                return XXXFragment.newInstance()
            }
            2 -> {
                return XXXFragment.newInstance()
            }
            3 -> {
                return XXXFragment.newInstance()
            }
            4 -> {
                return XXXFragment.newInstance()
            }
            5 -> {
                return XXXFragment.newInstance()
            }
            6 -> {
                return XXXFragment.newInstance()
            }
            7 -> {
                return XXXFragment.newInstance()
            }
            8 -> {
                return XXXFragment.newInstance()
            }
            9 -> {
                return XXXFragment.newInstance()
            }
        }
        return XXXFragment.newInstance()

    }
}
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
//        fragments.add(CallSettingsFragment.newInstance())
//        fragments.add(AttendanceSettingsFragment.newInstance())
//        fragments.add(RTKInfoFragment.newInstance())
//        fragments.add(IMUSettingsFragment.newInstance())
//        fragments.add(V2XInfoFragment.newInstance())
//        fragments.add(BoxInfoFragment.newInstance())
//        fragments.add(SystemUpdateFragment.newInstance())
//        fragments.add(SystemSettingsFragment.newInstance())
        binding.viewPager.adapter = SettingsViewPagerAdapter(childFragmentManager)
        binding.viewPager.setNoScroll(true)
        initView()
    }

即可解决问题

2.5 MainActivity onresume两次

这个问题没有用profiler分析,是在使用时发现某个线程的log重复打印两次,进而追踪到这里,onresume了两次,在onresume中启的逻辑/线程自然就执行了两次,造成了双倍的资源消耗。

为什么出现这个问题仍然没有定位到根因。

查阅了很多资料,提到的:

android:configChanges=

把能加入的都加入了,也是这样的状态。

这个问题仅在插入sim卡时出现,也就是说,无卡时,一切正常,插入SIM后再启动,MainActivity将启动两次。

目前仅做了规避手段,加入了是否第一次执行判断。

如果有人知道是什么原因导致的此问题:

插入SIM卡时,设备开机启动,自开发的Launcher App执行两次onresume(),麻烦讲解下,谢谢!

标签:XXXFragment,return,newInstance,app,add,Studio,内存,Andorid,fragments
From: https://blog.csdn.net/hkj887tg/article/details/137136984

相关文章

  • Bootloader/IAP零基础入门(1.1) —— 设计一个Bootloader引导进入APP的程序,包含中断向量
    前言(1)如果有嵌入式企业需要招聘湖南区域日常实习生,任何区域的暑假Linux驱动/单片机/RTOS的实习岗位,可C站直接私聊,或者邮件:[email protected],此消息至2025年1月1日前均有效(2)在上一章节中,我们详细介绍了如何让Bootloader引导进入APP程序。但是上一章节的工程是无法使用......
  • 关于各种app的推荐算法有浅感
    2024-03-2913:12:43不知道从什么开始,或许一开始就一直是如此,总是期待着有人可以把解决问题的方法给喂到自己的嘴边,当遇到学习上的缺乏动力时,竟然会觉得是抖音和社交软件戒得太久,以至于总是沉浸在自己的世界里面。这种想法并不全错,长期不与人交流,不与同行或者同学交流,会不知道自......
  • ssm基于Android的XX校园交流APP
    摘要随着互联网时代的发展,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,随着各行业的不断发展,XX校园交流APP建设也逐渐进入了信息化的进程。这个App的设计主要包括前台页面的设计和方便用户互动的后端数据库,而前端软件......
  • uniapp(全端兼容) - 最新移动端评论区讨论点赞回复功能,可发表文字或图片评论|点赞|回
    效果图在uniapp小程序/h5网页网站/安卓苹果app/nvue等(全平台完美兼容)开发中,实现评论区、讨论区功能详细教程,uniapp评论区用户可发布图片、视频、文字进行评论,其他用户可进行“无限级|盖楼评论区”,点赞评论、回复评论、删除评论(自动计算刷新,不影响布局),当评论大于n条时自......
  • 【全开源】JAVA游戏陪玩系统源码陪练APP源码H5源码电竞系统源码支持Android+IOS+H5_博
    “游戏陪玩系统源码、陪练APP源码、H5源码及电竞系统源码”,这些创新技术的融合,为电竞爱好者与游戏玩家带来前所未有的互动体验。通过我们的源码,您可以轻松搭建起一个功能完备的游戏陪玩平台,无论是寻找技术高超的陪练,还是与志同道合的玩家组队竞技,都能在这个平台上得到满足。同......
  • 【全开源】JAVA二手车交易二手车市场系统源码支持微信小程序+微信公众号+H5+APP_博纳
    二手车交易二手车市场系统源码微信小程序——买卖无忧,交易更便捷在日益繁荣的二手车市场中,寻找一个高效、可靠的交易平台成为了买卖双方共同的需求。为满足这一需求,我们推出了二手车交易二手车市场系统源码微信小程序,让买卖无忧,交易更便捷。这款微信小程序整合了丰富的二手车......
  • H5网页调用APP原生分享菜单 方法:mcloudshare://advert?imgUrl=图标链接&link=分享的链
    要在H5网页中调用APP原生的分享菜单,你可以通过以下步骤实现:创建分享按钮或触发分享的交互元素,例如一个按钮或链接。在按钮的点击事件处理程序中,使用JavaScript生成一个调用APP分享功能的URL。根据你提供的信息,生成的URL格式如下:mcloudshare://advert?imgUrl=图......
  • 【毕业设计】基于SpringBoot 和uniapp的食堂点餐系统
    效果源码下载文档一、概述由于互联网的飞速发展,饭店的点餐也要进行时代化的创新,由以前的人工点餐到现在的系统点单,大大减少了人力资源的利用。总体设计图由于互联网的飞速发展,饭店的点餐也要进行时代化的创新,由以前的人工点餐到现在的系统点单,大大减少了......
  • yolov部署到app
    今天把模型部署到app上        ......
  • Android studio打开Device File Explore(文件管理器)的方法
    方法View>ToolWindows>AndroidProfiler   AndroidStudio对文件的读写操作,可以看这里的文件,data是我自己取的名字,你们对应打开的文件名字不一样是正常的 下课!!......