Android 解决XXX Layout leaked 使用Navigation 踩坑 XML内存泄漏

  • 报错日志
  • 排查过程
  • 泄漏原因
  • 解决方案

最近维护一个项目,一个内存泄漏的的原因查了很久,这里记录一下。
文章开始建议简单看一下排查过程和错误原因,再去看解决结果,避免浪费大家时间

报错日志

打开项目后LeakCanary检测出一个内存泄漏,地址指向的也和之前的不太一样,指向的是一个layout,具体信息如下

排查过程

场景是这样的 ,项目只有一个Activity,里面使用 Navigation,其中包含两个fragment,一个MainFragment(Default Destination),一个SettingDragment
项目第一次打开 会因为没有设置过服务器地址而跳转到SettingFragment 也就是这一步的时候报错的。
分析一下,我们知道Navigation源码里默认每次navigate一个新界面走的是replace,从而销毁了一个fragment的 所以LeakCanary第一个路线指向的是MainFragment。
继续向下,指向的是MainFragment.bind变量

 // ViewModel & DataBindingprivate val viewModel: MainViewModel by viewModels()private lateinit var binding: MainFragmentBindingoverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?,): View {binding = MainFragmentBinding.inflate(inflater, container, false).also {it.lifecycleOwner = thisit.viewModel = viewModel}return binding.root}

也没问题啊,之前代码都是这么写的,安卓开发者官网也是这么写的没有错啊,应该不是这里的问题吧(就是这的问题!!如果你和我写的一样就要留意了!!!这里有大坑!等下说)
那继续向下看leakCancary的日志吧 下一条指向的是DataBinding生成的MainFragmentBindingImpl类中一个叫做 mboundView0的变量,贴一下相关代码

    @NonNullprivate final androidx.constraintlayout.widget.ConstraintLayout mboundView0;static {sIncludes = null;sViewsWithIds = new android.util.SparseIntArray();sViewsWithIds.put(R.id.background, 2);sViewsWithIds.put(R.id.vessel, 3);sViewsWithIds.put(R.id.state, 4);sViewsWithIds.put(R.id.et_healthCode, 5);}//这是mboundView具体的赋值private DasBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {super(bindingComponent, root, 0);this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];this.mboundView0.setTag(null);setRootTag(root);// listenersinvalidateAll();}

里面不过是把XML中的View添加进去而已,再正常不过了。其中这个0位置就是根布局的ConstraintLayout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data><import type="com.chinz.mms.client.ui.main.MainViewModel" /><variablename="viewModel"type="MainViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><ImageView  android:id="@+id/background"/><FrameLayout   android:id="@+id/vessel"/><com.***.MarqueeView android:id="@+id/marquee_tv"/><TextView  android:id="@+id/state"/><EditText android:id="@+id/et_healthCode"/></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

ConstraintLayout? WTF?!!
这个我代码根本就没用到啊,他甚至连个id都没有!其中的mContext更是雨我无瓜啊
源码里都是隐藏这个变量的
那是不是ConstraintLayout的子View有使用到MainFragment的引用呢?那就一个个全删了,只留一个ConstraintLayout 。。无果,还是一样泄漏日志
那就换一种Layout呗。。失败告终,一模一样的错误!
会不会是ViewModel的里有占用,虽然和这日志看起来没关系,也删了排除一下。。。无果
各种百度,谷歌一顿无果,最后猜测是不是Navtigation的原因,虽然看起来好像没给他传过Context,但是只有他可以被怀疑了
我的代码

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)listenModels()//initialized是VM中的一个变量,检查有没有设置过服务器地址,第一次运行肯定是false//重点怀疑(这里是错误的)if (viewModel.initialized.not()) {//findNavController().navigate(MainFragmentDirections.actionMainToSetting())}}

泄漏原因

推荐一下这个好文,这方面说的很详细,有兴趣的可以点进去详细看看,这里引用一下,简单地说就是两个
1 一般情况下,就是在使用Navigation 之前,Fragment的生命周期和View的是同步的,Fragment replace后被OnDesroy后 View也OnDestroy了,所以我们之前那么些不会有事,
使用了Navigation后,View (就是MainFragmentBinding)被销毁,但是fragment 不会被销毁,从而导致内存泄漏。
2. 虽然Android官方文档里介绍LiveData 不会造成内存泄漏,但是如果用了Navgation的话。LiveData 未必会在 lifecycleOwner 销毁的时候进行反注册,内存泄漏还是会发生。
如果这个页面马上跳到下一个的页面,之前订阅的 LiveData 就不会进行反注册。原因出在当跳出这个页面的时候,页面还处于生命周期的状态 INITIALIZED,但是反注册的条件是这个页面的生命周期状态至少是 CREATED。就像我之前在MainFragment的onViewCreated中的操作就会造成。看下面fragment中的源码

void performDestroyView() {mChildFragmentManager.dispatchDestroyView();if (mView != null && mViewLifecycleOwner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.CREATED)) {mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);}......
}

可能是因为使用jetPack这些库的原因,leakCanary定位表达的并不直观,Navigation还不够完善吧

Navigation 相关的坑,都有个中心。一般情况下,Fragment 就是一个 View,View 的生命周期就是 Fragment 的生命周期,但是在 Navigation 的架构下,Fragment 的生命周期和 View 的生命周期是不一样的。当 navigate 到新的 UI,被覆盖的 UI,View 被销毁,但是保留了 fragment 实例(未被 destroy),当这个 fragment 被 resume 的时候,View 会被重新创建。这是“罪恶”之源。
————————————————
版权声明:本文为CSDN博主「 字节跳动技术团队」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ByteDanceTech/article/details/120052166

解决方案

class MainFragment : BaseFragment() {// ViewModel & DataBindingprivate val viewModel: MainViewModel by viewModels()private var _binding: MainFragmentBinding? = nullprivate val binding get() = _binding!!override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?,): View {_binding = MainFragmentBinding.inflate(inflater, container, false).also {// 重点  解决问题1  不是this  是viewLifecycleOwnerit.lifecycleOwner = viewLifecycleOwnerit.viewModel = viewModel}// 返回binding对象的root return binding.root}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)Handler().postDelayed({//解决问题2 liveData 在直接跳转界面会造成内存泄漏,1S不被感知能等到创建完后状态变成CREATED 有更好的方案欢迎补充if (viewModel.initialized.not()) {findNavController().navigate(R.id.action_main_to_setting)}}, 1000)}//不要在onDestory写override fun onDestroyView() {super.onDestroyView()//重点 解决问题1_binding = null}
}

Android 解决XXX Layout leaked 使用Navigation 踩坑 XML内存泄漏相关推荐

  1. Android高德地图踩坑记录-内存泄漏问题

    1.问题现象 最近做项目优化,在查找app可能存在的内存泄漏地方,项目中有用到高德地图SDK,有一个页面有展示地图,每次退出该页面的时候,LeakCanary老是提示有内存泄漏,泄漏的大概信息如下: ...

  2. 【问题解决】Android JDK版本不匹配导致崩溃踩坑记录

    [问题解决]Android JDK版本不匹配导致崩溃踩坑记录 部分机型反馈崩溃问题 谷歌回复与解决方案 Android打包脱糖操作 对比与排查 总结 前几天同事遇到一个非常诡异的报错,紧急处理后,趁着 ...

  3. Android获取不到运动步数(踩坑)

    Android获取不到运动步数(踩坑) 获取运动步数 某些手机获取不到步数 获取运动步数 使用SensorManager,也就是手机内置的传感器获取运动步数,通过该方法可获取到当前运动步数.开机后总运 ...

  4. Android开发在路上:少去踩坑,多走捷径

    转:http://djt.qq.com/article/view/1193 作者:gzjay,腾讯MIG无线产品部 高级工程师 最近一朋友提了几个Android问题让我帮忙写个小分享,我觉得对新人还是 ...

  5. React Native之React Navigation踩坑

    自动重装系统之后,已经很长一段时间没有来写React Native了,今天空闲之余,决定重新配置React Native的开发环境,继续踩坑... React Native的开发环境配置狠简单,只要依 ...

  6. 【转】Android开发在路上:少去踩坑,多走捷径

    本文是我订阅"腾讯大讲堂"公众帐号时,他们推送的一篇文章,但在腾讯大讲堂官网上我并没有找到这篇文章,不过其它专门"爬"公众号文章的网站倒是有.我觉得写的很不错. ...

  7. Android安卓集成融云推送踩坑

    此文档单单接入推送,暂时没有用IM或其他 如果您觉得可以帮助到您,麻烦帮我点个赞. -------------------------------- 写在前面,为什么要用这个,我并不想,实际接入过程中 ...

  8. zbar android解码错误,Android原生编解码接口 MediaCodec 之——踩坑

    关键帧 MediaCodec 有两种方式触发输出关键帧,一是由配置时设置的 KEY_FRAME_RATE和KEY_I_FRAME_INTERVAL参数自动触发,二是运行过程当中经过 setParame ...

  9. Android集成阿里云旺即时通讯踩坑历程

    下载云旺的demo,将demo中的OneSDK直接拷贝,作为Moudle进行依赖,具体操作就不说了,OneSDK是最新的,一定不要进行修改, 进行依赖后,可能会遇到buildToolsVersion ...

最新文章

  1. WCF系列之.net(4.0) 在网站使用Js调用Wcf Rest
  2. 【KVM】Ubuntu14.04 安装KVM
  3. 杭电2032杨辉三角
  4. DELPHI实现游戏内存的修改
  5. Leetcode-第 283 场周赛
  6. BZOJ.4516.[SDOI2016]生成魔咒(后缀自动机 map)
  7. 开启 JM 的 trace 功能
  8. 前向星及spfa大法
  9. 计算机键盘大赛活动总结,参加技能大赛的活动总结
  10. 美国广告市场:Facebook和Google将占据四分之一市场份额
  11. 在Linux中修复U盘
  12. matlab simulink 六自由度机械臂模糊控制pid
  13. 论fastadmin里面token加密方式
  14. 交叉线与直通线的区别
  15. 医院病案管理系统MRMS源码 病案管理 医院源码
  16. 只要7步,就能将任何魔方6面还原(留着以后教孩子玩)
  17. java打印 X XXX XXXXX
  18. 小游戏开发怎么选游戏引擎
  19. html开发android,使用HTML5开发Android本地应用(一)
  20. java中ws程序是什么意思_Java Web服务对象(JAX-WS)生命周期

热门文章

  1. 批量网址自动提取文字(newspaper)
  2. YOLOV5源码解读(数据集加载和增强)
  3. 射极跟随器负载过重引起的失真问题(摘抄)
  4. 增量迭代模型,瀑布模型,螺旋模型,快速原型模型
  5. Python一键清空购物车
  6. Flutter AnimatedIcon 图标也可以动画
  7. 如何用PS制作一寸照片
  8. 手把手教你爬取任意日期全部股票分时数据~
  9. ajax请求存在不安全的问题有哪些?如何解决这些不安全的很问题
  10. Oracle HFM OHS服务无法启动