修改UI线程视图时,Android UI不会崩溃

Dun*_* Ta 5 java multithreading android kotlin

场景:

在测试片段中的线程时遇到一个奇怪的问题。

我有一个用Kotlin编写的片段,其onResume()中包含以下片段:

override fun onResume() {
    super.onResume()

    val handlerThread = HandlerThread("Stuff")
    handlerThread.start()
    val handler = Handler(handlerThread.looper)
    handler.post {
        Thread.sleep(2000)
        tv_name.setText("Something something : " + isMainThread())
    }
}
Run Code Online (Sandbox Code Playgroud)

is MainThread()是一个检查当前线程是否为主线程的函数,如下所示:

private fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper()
Run Code Online (Sandbox Code Playgroud)

我看到我的TextView在2秒后更新为“ Something something:false”

看到false告诉我该线程当前不是UI / Main线程。

我以为这很奇怪,因此我创建了相同的片段,但用onResume()的以下代码段用Java编写:

@Override
public void onResume() {
    super.onResume();

    HandlerThread handlerThread = new HandlerThread("stuff");
    handlerThread.start();
    new Handler(handlerThread.getLooper()).post(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            textView.setText("Something something...");
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

该应用程序崩溃,但出现以下异常:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7313)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1161)
Run Code Online (Sandbox Code Playgroud)

我做了一些研究,但我找不到真正能解释这一点的东西。另外,请假设我的观点全部正确。

题:

当我在以Kotlin编写的Fragment中用我的UI线程运行的可运行对象中修改TextView时,为什么我的应用程序不会崩溃?

如果某处文档中的某些内容可以解释这一点,那么有人可以请我参考一下吗?

我实际上并没有试图从UI线程修改UI,我只是很好奇为什么会这样。

如果您需要更多信息,请告诉我。非常感谢!

更新: 根据@Hong Duan提到的内容,requestLayout()未得到调用。这与Kotlin / Java无关,但与TextView本身无关。

我无所适从,但没有意识到Kotlin片段中的TextView的layout_width为“ match_parent”。而我的Java片段中的TextView的layout_width为“ wrap_content”。

TLDR:用户错误+ requestLayout(),并非总是进行线程检查。

Hon*_*uan 4

唯一CalledFromWrongThreadException在必要时抛出,但并非总是如此。在您的情况下,当ViewRootImpl.checkThread()在期间调用时它会抛出ViewRootImpl.requestLayout(),以下是来自的代码ViewRootImpl.java

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

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

对于TextView,当我们更新它的文本时并不总是需要重新布局,我们可以在源代码中看到逻辑:

/**
 * Check whether entirely new text requires a new view layout
 * or merely a new text layout.
 */
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();

        /*
         * No need to bring the text into view, since the size is not
         * changing (unless we do the requestLayout(), in which case it
         * will happen at measure).
         */
        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; // return with out relayout
            }

            // 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; // return with out relayout
            }
        }

        // 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

可以看到,在某些情况下,requestLayout()不会调用 ,因此不会引入主线程检查。

所以我认为关键点不是关于 Kotlin 或 Java,而是关于TextViews 的布局参数,它决定是否requestLayout()被调用。