某项目的app运行一段时间(切换页面、触发交互事件等)后就开始严重卡顿,使用top查看内存的使用情况,发现每次操作过后内存都有小幅增长,且永远不下降,存在内存泄露问题。
目录
2.4 嵌套fragment+viewpager引起的内存泄露
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将启动两次。
目前仅做了规避手段,加入了是否第一次执行判断。
标签:XXXFragment,return,newInstance,app,add,Studio,内存,Andorid,fragments From: https://blog.csdn.net/hkj887tg/article/details/137136984如果有人知道是什么原因导致的此问题:
插入SIM卡时,设备开机启动,自开发的Launcher App执行两次onresume(),麻烦讲解下,谢谢!