非UI线程更新UI问题

我们都知道在android中我们想更新一个ui,一定要在主线程中更新,也就是UI线程。因为这和我们的安卓系统设计有关,安卓的系统设计是单线程模式。

规定 更新ui必须在主线程中

那么会存在一些情况违反了这种规定,但又偏偏没有报错,运行成功。

第一种情况,我们直接在onCreate中开启一个子线程更新ui,这是成功的。为什么呢?因为我们的视图绘制是由ViewRootImpl来管理,而ViewRootImpl是在我们onResume之后创建的,也就是说我们在onResume生命周期之前所进行的ui更新,是不会进行线程的判断。所以会导致出现在非ui线程中更新ui成功的情况。

第二种情况,比如我们创建了一个textview,我们开启一个子线程去更新它里面的文本内容,只要我们的文本内容不超出它设置的宽高,就可以更新ui成功。
这是为什么呢?
这里我们只能来看一下源码是怎么写的。

首先我们是调用了 setText 方法来改变文本内容
经过几层调用最后会执行这个setText方法,里面省掉了一些代码,只留下关键代码。
关键代码就在 checkForRelayout() 检查是否需要重新布局

private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
        mTextSetFromXmlOrResourceId = false;

		......

        if (mLayout != null) {
            checkForRelayout();
        }

        sendOnTextChanged(text, 0, oldlen, textLength);
        onTextChanged(text, 0, oldlen, textLength);

       ......
    }

我们进入 checkForRelayout 方法里面看一下:
这里的代码我没有进行省掉,原原本本的复制过来了。
我们出现第二种情况的关键就出现在这里。
由于我们文本内容没有超出控件设置的宽高,最后不会去调用requestLayout()方法

mEllipsize != TextUtils.TruncateAt.MARQUEE
这行代码的意思是 控件的文字是否动态赋值 这里没有设置肯定就是true了 所以进入判断最后执行了 autoSizeText(); invalidate(); 那么我们下一步的关键就在invalidate 里面,我们知道invalidate 是重绘,即刷新界面的意思。

private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
            // Static width, so try making a new text layout.

            int oldht = mLayout.getHeight();
            int want = mLayout.getWidth();
            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                          false);

            //控件的文字是否动态赋值
            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
                // In a fixed-height view, so use our new text layout.
                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                    autoSizeText();
                    invalidate();
                    return;
                }

                // Dynamic height, but height has stayed the same,
                // so use our new text layout.
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }

            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

在进入invalidate方法以后经过层层调用最后我们来到了 invalidateInternal 方法里面,这个方法里面也是我们第二步的关键。
我们这里调用了 invalidateChild 方法
因为ViewParent是一个接口,一般子view的ViewParent就是它的父View即ViewGroup,那么进ViewGroup的源码看看如何实现的

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        ......

            // 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);
            }

            // Damage the entire projection receiver, if necessary.
            if (mBackground != null && mBackground.isProjected()) {
                final View receiver = getProjectionReceiver();
                if (receiver != null) {
                    receiver.damageInParent();
                }
            }
        }
    }

viewgourp中的invalidateChild方法
这里我们先说第二种特殊情况,如果是第二种特殊情况,我们设置的文本内容不超出设置的宽高这里会直接进入 if (attachInfo != null && attachInfo.mHardwareAccelerated) { 判断 从而不会执行下面的代码,也就不会去检查线程是否是主线程。

正常来说会执行invalidateChildInParent方法 在invalidateChildInParent方法中会去检查线程是否是ui线程。
我们来看下invalidateChildInParent方法吧。

 public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // HW accelerated fast path
            onDescendantInvalidated(child, child);
            return;
        }

       ......
                //这一步调用了父类的invalidateChildInParent方法
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread(); //检查线程是否是主线程
         ....
        invalidateRectOnScreen(dirty); //最终调用  scheduleTraversals()方法,会调用performDraw()执行进行重绘的工作

        return null;
    }
  void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

以上就是我说的俩种特殊情况,也介绍了一下我们非ui线程更新ui检查报错的一个源码逻辑。

发布了20 篇原创文章 · 获赞 3 · 访问量 432

猜你喜欢

转载自blog.csdn.net/weixin_41078100/article/details/104326854