Android View系统中无效区管理的bug


碰到这么一个问题,我有一个activity,布局如下:

<RelativeLayout    xmlns:Android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
    android:background="@drawable/page_bg" >
    <com.ebensz.widget.SVGEditor android:id="@+id/svg_editor"
        android:layout_width="fill_parent"
          android:layout_height="fill_parent"/>

其中page_bg是一张jpg图片,在调试svg_editor的绘制速度的时候,发现有三处主要的绘制,分别是ColorDrawable、BitmapDrawable和svg_editor,后面两个自然是RelativeLayout和我自定义的控件,ColorDrawable就很奇怪了,我没有一处使用,只能是窗口饰件(decoView)了,这点很快就得到了确认。但我的RelativeLayout使用JPG图片做背景,而JPG图片是不可能有透明色的,再画RelativeLayout的父视图不是画蛇添足么?

我首先把怀疑的目光集中到了RelativeLayout上,是不是因为我没有指明它不透明,系统把它当透明的了呢,于是查看了VIEW类的isOpaque和computeOpaqueFlags函数,它是根据背景是否透明设定视图的透明属性的,问题很快被澄清了,RelativeLayout确实是不透明的。那问题出在哪呢?

于是我又把View的invalidate和绘制流程梳理了一遍。invalidate的流程很简单,先是设置无效标记,然后调用parent view的invalidateChild,它的主要代码如下:

// Check whether the child that requests the invalidate is fully opaque
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
        child.getAnimation() != null;
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;

do {
    View view = null;
    if (parent instanceof View) {
        view = (View) parent;
    }

    if (drawAnimation) {
        if (view != null) {
            view.mPrivateFlags |= DRAW_ANIMATION;
        } else if (parent instanceof ViewRoot) {
            ((ViewRoot) parent).mIsAnimating = true;
        }
    }

    // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
    // flag coming from the child that initiated the invalidate
    if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
        view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
    }

    parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);

invalidateChildInParent只是简单的向上遍历,最终把无效区交给rootView引起窗口重绘,关键是DIRTY_OPAQUE 和DIRTY的意思。查看一下绘制流程,当DIRTY_OPAQUE 标记为真时,跳过了本view的绘制,直接绘制childview,可见它表示非透明的子窗口无效引起的绘制,这种情况下parent view是不需要绘制的。上述代码也体现了这种思想,但当引起绘制的view本身是透明的,而它的某个parent view非透明时,上述代码并没有继续检查,而是要求所有parent view重新绘制,问题的根源就在这里。

究竟是google的一个bug,还是考虑到某种需要,又或者有某种途径能解决这个问题?问题尚未解决,同志仍需努力。

相关内容