继续记录我的疑难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生命周期都回调了,却不见其页面展示相关推荐

  1. Lifecycle Activity和Fragment生命周期感知组件 LifecycleObserver MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. android fragment 生命周期

    今天,简单讲讲fragment 的生命周期. 其实,对于fragment ,大家都很熟悉.我也经常使用,但是最近做一个功能时,在activity里面调用fragment 的函数,发现变量居然没有初始化 ...

  3. Android Fragment 生命周期及其正确使用(建议使用自定义View替换Fragment)

    使用Fragment 官方例子中显示: 例如:一个学生Fragment,需要传入studentId,进行http请求显示,那么setArguments后防止杀掉Fragment后,参数为0,显示不了数 ...

  4. Android零基础入门第86节:探究Fragment生命周期

    2019独角兽企业重金招聘Python工程师标准>>> 一个Activity可以同时组合多个Fragment,一个Fragment也可被多个Activity 复用.Fragment可 ...

  5. android屏幕旋转生命周期,Activity、Fragment生命周期---横竖屏切换的生命周期

    先贴出一张大家众所周知activity流程图 onCreate():创建Activity调用,用于Activity的初始化,还有个Bundle类型的参数,可以访问以前存储的状态.onStart():A ...

  6. Android:Fragment生命周期(结合Activity的生命周期进行分析)

    文章目录 前言 一.Fragment生命周期概述 启动 退出 二.Fragment生命周期细述 1.onCreate(Bundle) 2.onCreateView(LayoutInflater, Vi ...

  7. android gilde生命周期管理,Glide原理之Activity、Fragment生命周期监听(三)

    Glide中一个重要特性是Request可以随Activity或Fragment的onStart而resume,onStop而pause,onDestroy而clear,从而节约流量和内存,并且防止内 ...

  8. Fragment生命周期以及使用时的小问题

    前言- 昨天在写UI的时候用到了FRAGMENT,发现自己对此还不是非常了解,借此机会记录一下 Fragment的生命周期- 官方生命周期图: Fragment每个生命周期方法的意义.作用- onVi ...

  9. Fragment生命周期详解

    关于Fragment的生命周期,博主写过Activity与Fragment生命周期详解,基本上把Fragment的生命周期详细介绍过,但是那仅仅是创建一个Fragmnet时的生命周期,而事实上Frag ...

最新文章

  1. java 重载 参数子类_java - Java中带有子类参数的函数重载 - 堆栈内存溢出
  2. php数组重复值销毁,如何从PHP中删除数组中的重复值
  3. 人工智能十年回顾:CNN、AlphaGo、GAN……它们曾这样改变世界
  4. PHP防QQ列表右划,react native 实现类似QQ的侧滑列表效果
  5. svn搭建本地服务端
  6. 分布式技术一周技术动态 2016.07.10
  7. AIX上增加逻辑卷时报错误0516-787 extendlv: Maximum allocation for logical volume
  8. 开发自测,到底该从哪里做起?
  9. 台积电南京12寸厂址 落脚江北新区
  10. [JavaEE] Hibernate连接池配置测试
  11. Jquery截取中文字符串
  12. 老板要我开发一个简单的工作流引擎 !
  13. 数据治理--元数据--元数据的作用
  14. 计算机培训ppt课件,计算机基础操作培训ppt课件.ppt
  15. 第四章 平稳序列的拟合与预测
  16. STM3库文件 hal_uart.c的使用
  17. Ubuntu 建立局域网
  18. Your application has presented a UIAlertController (UIAlertController: 0x100b79
  19. 一个邮箱联结全球?也许不会是遥不可及的梦想
  20. 基于Ti Omap3x 分析v4l2架构

热门文章

  1. MFC开发-垂直滚动条一直处于底部
  2. iPhone 14“感叹号”设计没跑:屏下Face ID要等到2024年
  3. 爱奇艺首届“黑客马拉松“落幕 极客变身“大娱乐家”
  4. 特斯拉AI Day首秀:FSD终极进化?AI超算Dojo、D1芯片、人形机器人亮相!
  5. 微信新表情戒烟了!腾讯:雪茄大佬成了歪嘴战神
  6. Redmi K40 Pro渲染图曝光:后置相机模组成最大焦点
  7. 《原神》月入16亿,米哈游为何仍然被嫌弃?
  8. 马斯克非常有信心:SpaceX将在2026年前让人类登陆火星
  9. 小米集团:副董事长林斌承诺5年内不出售公司股份 已作安排的除外
  10. 官宣!华为主导首个软件定义摄像机国际标准诞生