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

某项目的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(),麻烦讲解下,谢谢!

相关推荐

  1. SpringBoot项目启动内存泄漏问题排查解决

    2024-04-01 15:20:01       14 阅读
  2. 内存泄露解决

    2024-04-01 15:20:01       19 阅读
  3. python内存泄漏解决

    2024-04-01 15:20:01       13 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-01 15:20:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-01 15:20:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-01 15:20:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-01 15:20:01       20 阅读

热门阅读

  1. 获取 PostgreSQL 某个表的定义

    2024-04-01 15:20:01       14 阅读
  2. Python笔记|列表对象方法

    2024-04-01 15:20:01       14 阅读
  3. day14-二叉树part01(递归法/迭代法)

    2024-04-01 15:20:01       18 阅读
  4. How to use pandoc in Ubuntu 22.04

    2024-04-01 15:20:01       14 阅读
  5. 缓存的常见问题及其解法

    2024-04-01 15:20:01       10 阅读
  6. 巧克力(蓝桥杯)

    2024-04-01 15:20:01       20 阅读
  7. 本学期学习计划

    2024-04-01 15:20:01       22 阅读
  8. 【Docker笔记06】【容器编排】

    2024-04-01 15:20:01       15 阅读
  9. Qt 中 :deleteLater 总结

    2024-04-01 15:20:01       17 阅读
  10. VLAN配置及原理

    2024-04-01 15:20:01       14 阅读