项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示

继续记录我的疑难bug解决过程,这次要说的bug相比前几篇来说,更难定位,因为影响较大,直接导致不解决这个bug,根本就没有办法出版本,两三个同事定位了半天也没有结果,最后我自告奋勇的暂时放下手中的工作任务,去解决,因为我确实很喜欢解决疑难的bug,哈哈,这里面小小自吹一下哈~~
主要的现象是一些操作之后,添加fragment,正常应该会显示fragment中的页面,但是这个bug奇怪的地方就是fragment的生命周期都走了,onCreateView 也正常,页面却始终不出来。
业务流程:互动上课中,点击下课,回到主界面,再进去课表页面
bug是回到主界面再次进去课表页面,课表页面不见了。
一、现象描述:

1、我先贴出正常的页面操作流程,见下图。

项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示
2、bug的现象:
项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示
对比画面的最后一帧,正常现象是返回到课表页面,出问题的是进去课表没有了,显现的是摄像机画面。

二、问题定位
首先经过日志打印,fragment生命周期是走的,再次通过Android DeviceMonitor工具查看view的层级树,看看是怎么回事。
项目疑难杂症记录(五):fragment生命周期都回调了,却不见其页面展示
右边红框子里面的是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

  @RemotableViewMethod
    public 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) {
              // 设置flag
                mPrivateFlags |= 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.
     */
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;
          // 省略部分代码,只抓取最主要的代码
          //此处从当前view,开启循坏遍历,一直到达ViewRootImpl,调用每一个层级的invalidateChildInParent
            do {
                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;
            }
        }
        //没有找到父view
        return null;
    }

好了,到这里我们理一下,第四个方法,开启了循坏,一直找ViewParent,并且将这条路径上的每个view的flag都设置成CacheInvalid等状态。想象一下,就像一个view树,从底部结点一直找到根view。

我们看下,ViewRootImpl 的invalidateChildInParent

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        ///XXXX
        invalidateRectOnScreen(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,所以也根本就没有页面显示出来了。

好了,分析源码不易,但要抓住重点,这样更能锻炼自己的分析疑难问题的能力以及源码阅读能力