项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示
继续记录我的疑难bug解决过程,这次要说的bug相比前几篇来说,更难定位,因为影响较大,直接导致不解决这个bug,根本就没有办法出版本,两三个同事定位了半天也没有结果,最后我自告奋勇的暂时放下手中的工作任务,去解决,因为我确实很喜欢解决疑难的bug,哈哈,这里面小小自吹一下哈~~
主要的现象是一些操作之后,添加fragment,正常应该会显示fragment中的页面,但是这个bug奇怪的地方就是fragment的生命周期都走了,onCreateView 也正常,页面却始终不出来。
业务流程:互动上课中,点击下课,回到主界面,再进去课表页面
bug是回到主界面再次进去课表页面,课表页面不见了。
一、现象描述:
1、我先贴出正常的页面操作流程,见下图。
2、bug的现象:
对比画面的最后一帧,正常现象是返回到课表页面,出问题的是进去课表没有了,显现的是摄像机画面。
二、问题定位
首先经过日志打印,fragment生命周期是走的,再次通过Android DeviceMonitor工具查看view的层级树,看看是怎么回事。
右边红框子里面的是fragment的根view,左边的参数表明这个view的宽和高都是0,说明虽然已经添加,但是没有测量,是不是很奇怪?
遂又从日志中分析,发现了一个 令我警惕的日志。
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views
很明显,在子线程当中操作了View的状态,应该就是和这个有关系了,这个操作代码是隐藏的子线程,不容易发现,是其他同事加的,好了,找到直接原因了,版本可以顺利发布了,得到了同事们的夸赞,
后面 改为在主线程中操作,果然问题就没有了,
<但是子线程操作是如何导致问题产生的呢?是什么样的机制原理呢?这个才是本篇博客的重点
三、本质原因分析
我们正常子线程操作view,程序会崩溃,这位同事加的代码用异常捕获掉了,所以没有那么容易发现。
这个代码中,子线程是做了view.setVisibility操作,继续源码分析模式~。
1、 setVisilibity
@RemotableViewMethodpublic void setVisibility(@Visibility int visibility) {setFlags(visibility, VISIBILITY_MASK);}
2、setFlags
void setFlags(int flags, int mask) {final boolean accessibilityEnabled =AccessibilityManager.getInstance(mContext).isEnabled();final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();int old = mViewFlags;mViewFlags = (mViewFlags & ~mask) | (flags & mask);//省去部分代码final int newVisibility = flags & VISIBILITY_MASK;if (newVisibility == VISIBLE) {if ((changed & VISIBILITY_MASK) != 0) {// 设置flagmPrivateFlags |= PFLAG_DRAWN;// 走这里方法,重新绘制invalidate(true);}}}
3、invalidate 方法
void invalidate(boolean invalidateCache) {invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {// 省略代码if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {if (fullInvalidate) {mLastIsOpaque = isOpaque();mPrivateFlags &= ~PFLAG_DRAWN;}mPrivateFlags |= PFLAG_DIRTY;if (invalidateCache) {mPrivateFlags |= PFLAG_INVALIDATED;// 设置 PFLAG_DRAWING_CACHE_VALID 的flag,此处是重点;mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}// Propagate the damage rectangle to the parent view.final AttachInfo ai = mAttachInfo;final ViewParent p = mParent;if (p != null && ai != null && l < r && t < b) {final Rect damage = ai.mTmpInvalRect;damage.set(l, t, r, b);p.invalidateChild(this, damage);}}
4、ViewGroup 的 invalidateChild方法
/*** Don't call or override this method. It is used for the implementation of* the view hierarchy.*/@Overridepublic final void invalidateChild(View child, final Rect dirty) {ViewParent parent = this;// 省略部分代码,只抓取最主要的代码//此处从当前view,开启循坏遍历,一直到达ViewRootImpl,调用每一个层级的invalidateChildInParentdo {View view = null;if (parent instanceof View) {view = (View) parent;}parent = parent.invalidateChildInParent(location, dirty);}} while (parent != null);}}
5、ViewGroup 的 invalidateChildInParent 方法
这个方法是根据当前的flag等条件,返回父view
这个方法很关键,也是后面导致添加fragment不显示的方法原因所在。
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {// 条件判断。是否是有效的cache或者可以绘制的。if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=FLAG_OPTIMIZE_INVALIDATE) {//设置flag的cache无效,需要走绘制流程mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;location[CHILD_LEFT_INDEX] = left;location[CHILD_TOP_INDEX] = top;if (mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;}return mParent;} else {mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;if (mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;}return mParent;}}//没有找到父viewreturn null;}
好了,到这里我们理一下,第四个方法,开启了循坏,一直找ViewParent,并且将这条路径上的每个view的flag都设置成CacheInvalid等状态。想象一下,就像一个view树,从底部结点一直找到根view。
我们看下,ViewRootImpl 的invalidateChildInParent
@Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();///XXXXinvalidateRectOnScreen(dirty);return null;}
首先进行了 线程检测,非主线程操作,抛出异常。
只不过我们代码中被我们捕获了异常,程序没有崩溃,下次我继续添加view,没有显现,我刚才已经讲了,如果正常流程走下来,会触发ViewRootImpl的performTraversals,然后View布局绘制测量三大方法,每一层的view的flag都会设置成正常状态。
但是怪就怪在程序没有崩溃,上面视图检测工具中我红色框框标出来的ViewGroup的flag被设置成了PFLAG_DRAWING_CACHE_VALID 以及 PFLAG_DRAWN,所以下次调用该ViewGroup的addView的时候,又走到了上面第五步invalidateChildInParent方法,该方法首先判断 flag,不满足,返回null,所以addView 也一直想找到ViewRootImpl的这条路径被掐断了,所以我们看到视图工具里面虽然add上去了,但是根本就没有onMeasure,宽和高都为0,所以也根本就没有页面显示出来了。
好了,分析源码不易,但要抓住重点,这样更能锻炼自己的分析疑难问题的能力以及源码阅读能力
项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示相关推荐
- Lifecycle Activity和Fragment生命周期感知组件 LifecycleObserver MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- android fragment 生命周期
今天,简单讲讲fragment 的生命周期. 其实,对于fragment ,大家都很熟悉.我也经常使用,但是最近做一个功能时,在activity里面调用fragment 的函数,发现变量居然没有初始化 ...
- Android Fragment 生命周期及其正确使用(建议使用自定义View替换Fragment)
使用Fragment 官方例子中显示: 例如:一个学生Fragment,需要传入studentId,进行http请求显示,那么setArguments后防止杀掉Fragment后,参数为0,显示不了数 ...
- Android零基础入门第86节:探究Fragment生命周期
2019独角兽企业重金招聘Python工程师标准>>> 一个Activity可以同时组合多个Fragment,一个Fragment也可被多个Activity 复用.Fragment可 ...
- android屏幕旋转生命周期,Activity、Fragment生命周期---横竖屏切换的生命周期
先贴出一张大家众所周知activity流程图 onCreate():创建Activity调用,用于Activity的初始化,还有个Bundle类型的参数,可以访问以前存储的状态.onStart():A ...
- Android:Fragment生命周期(结合Activity的生命周期进行分析)
文章目录 前言 一.Fragment生命周期概述 启动 退出 二.Fragment生命周期细述 1.onCreate(Bundle) 2.onCreateView(LayoutInflater, Vi ...
- android gilde生命周期管理,Glide原理之Activity、Fragment生命周期监听(三)
Glide中一个重要特性是Request可以随Activity或Fragment的onStart而resume,onStop而pause,onDestroy而clear,从而节约流量和内存,并且防止内 ...
- Fragment生命周期以及使用时的小问题
前言- 昨天在写UI的时候用到了FRAGMENT,发现自己对此还不是非常了解,借此机会记录一下 Fragment的生命周期- 官方生命周期图: Fragment每个生命周期方法的意义.作用- onVi ...
- Fragment生命周期详解
关于Fragment的生命周期,博主写过Activity与Fragment生命周期详解,基本上把Fragment的生命周期详细介绍过,但是那仅仅是创建一个Fragmnet时的生命周期,而事实上Frag ...
最新文章
- java 重载 参数子类_java - Java中带有子类参数的函数重载 - 堆栈内存溢出
- php数组重复值销毁,如何从PHP中删除数组中的重复值
- 人工智能十年回顾:CNN、AlphaGo、GAN……它们曾这样改变世界
- PHP防QQ列表右划,react native 实现类似QQ的侧滑列表效果
- svn搭建本地服务端
- 分布式技术一周技术动态 2016.07.10
- AIX上增加逻辑卷时报错误0516-787 extendlv: Maximum allocation for logical volume
- 开发自测,到底该从哪里做起?
- 台积电南京12寸厂址 落脚江北新区
- [JavaEE] Hibernate连接池配置测试
- Jquery截取中文字符串
- 老板要我开发一个简单的工作流引擎 !
- 数据治理--元数据--元数据的作用
- 计算机培训ppt课件,计算机基础操作培训ppt课件.ppt
- 第四章 平稳序列的拟合与预测
- STM3库文件 hal_uart.c的使用
- Ubuntu 建立局域网
- Your application has presented a UIAlertController (UIAlertController: 0x100b79
- 一个邮箱联结全球?也许不会是遥不可及的梦想
- 基于Ti Omap3x 分析v4l2架构
热门文章
- MFC开发-垂直滚动条一直处于底部
- iPhone 14“感叹号”设计没跑:屏下Face ID要等到2024年
- 爱奇艺首届“黑客马拉松“落幕 极客变身“大娱乐家”
- 特斯拉AI Day首秀:FSD终极进化?AI超算Dojo、D1芯片、人形机器人亮相!
- 微信新表情戒烟了!腾讯:雪茄大佬成了歪嘴战神
- Redmi K40 Pro渲染图曝光:后置相机模组成最大焦点
- 《原神》月入16亿,米哈游为何仍然被嫌弃?
- 马斯克非常有信心:SpaceX将在2026年前让人类登陆火星
- 小米集团:副董事长林斌承诺5年内不出售公司股份 已作安排的除外
- 官宣!华为主导首个软件定义摄像机国际标准诞生