Android 解决XXX Layout leaked 使用Navigation 踩坑 XML内存泄漏
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内存泄漏相关推荐
- Android高德地图踩坑记录-内存泄漏问题
1.问题现象 最近做项目优化,在查找app可能存在的内存泄漏地方,项目中有用到高德地图SDK,有一个页面有展示地图,每次退出该页面的时候,LeakCanary老是提示有内存泄漏,泄漏的大概信息如下: ...
- 【问题解决】Android JDK版本不匹配导致崩溃踩坑记录
[问题解决]Android JDK版本不匹配导致崩溃踩坑记录 部分机型反馈崩溃问题 谷歌回复与解决方案 Android打包脱糖操作 对比与排查 总结 前几天同事遇到一个非常诡异的报错,紧急处理后,趁着 ...
- Android获取不到运动步数(踩坑)
Android获取不到运动步数(踩坑) 获取运动步数 某些手机获取不到步数 获取运动步数 使用SensorManager,也就是手机内置的传感器获取运动步数,通过该方法可获取到当前运动步数.开机后总运 ...
- Android开发在路上:少去踩坑,多走捷径
转:http://djt.qq.com/article/view/1193 作者:gzjay,腾讯MIG无线产品部 高级工程师 最近一朋友提了几个Android问题让我帮忙写个小分享,我觉得对新人还是 ...
- React Native之React Navigation踩坑
自动重装系统之后,已经很长一段时间没有来写React Native了,今天空闲之余,决定重新配置React Native的开发环境,继续踩坑... React Native的开发环境配置狠简单,只要依 ...
- 【转】Android开发在路上:少去踩坑,多走捷径
本文是我订阅"腾讯大讲堂"公众帐号时,他们推送的一篇文章,但在腾讯大讲堂官网上我并没有找到这篇文章,不过其它专门"爬"公众号文章的网站倒是有.我觉得写的很不错. ...
- Android安卓集成融云推送踩坑
此文档单单接入推送,暂时没有用IM或其他 如果您觉得可以帮助到您,麻烦帮我点个赞. -------------------------------- 写在前面,为什么要用这个,我并不想,实际接入过程中 ...
- zbar android解码错误,Android原生编解码接口 MediaCodec 之——踩坑
关键帧 MediaCodec 有两种方式触发输出关键帧,一是由配置时设置的 KEY_FRAME_RATE和KEY_I_FRAME_INTERVAL参数自动触发,二是运行过程当中经过 setParame ...
- Android集成阿里云旺即时通讯踩坑历程
下载云旺的demo,将demo中的OneSDK直接拷贝,作为Moudle进行依赖,具体操作就不说了,OneSDK是最新的,一定不要进行修改, 进行依赖后,可能会遇到buildToolsVersion ...
最新文章
- WCF系列之.net(4.0) 在网站使用Js调用Wcf Rest
- 【KVM】Ubuntu14.04 安装KVM
- 杭电2032杨辉三角
- DELPHI实现游戏内存的修改
- Leetcode-第 283 场周赛
- BZOJ.4516.[SDOI2016]生成魔咒(后缀自动机 map)
- 开启 JM 的 trace 功能
- 前向星及spfa大法
- 计算机键盘大赛活动总结,参加技能大赛的活动总结
- 美国广告市场:Facebook和Google将占据四分之一市场份额
- 在Linux中修复U盘
- matlab simulink 六自由度机械臂模糊控制pid
- 论fastadmin里面token加密方式
- 交叉线与直通线的区别
- 医院病案管理系统MRMS源码 病案管理 医院源码
- 只要7步,就能将任何魔方6面还原(留着以后教孩子玩)
- java打印 X XXX XXXXX
- 小游戏开发怎么选游戏引擎
- html开发android,使用HTML5开发Android本地应用(一)
- java中ws程序是什么意思_Java Web服务对象(JAX-WS)生命周期