java - 修改 UI 线程 View 时 Android UI 不会崩溃

标签 java android multithreading kotlin

场景:

我在测试 fragment 中的线程时遇到了一个奇怪的问题。

我有一个用 Kotlin 编写的 fragment ,在 onResume() 中有以下 fragment :

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())
    }
}

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

private fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper()

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

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

我觉得这很奇怪,所以我创建了相同的 fragment ,但用 Java 编写,而不是使用来自 onResume() 的以下 fragment :

@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...");
        }
    });
}

应用程序按预期崩溃并出现以下异常:

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)

我做了一些研究,但我无法真正找到可以解释这一点的东西。另外,请假设我的观点都被正确夸大了。

问题:

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

如果某些文档中的某处解释了这一点,有人可以请我引用吗?

我实际上并没有尝试在 UI 线程之外修改我的 UI,我只是好奇为什么会这样。

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

更新: 正如@Hong Duan 所说, requestLayout() 没有被调用。这与 Kotlin/Java 无关,而与 TextView 本身有关。

我搞砸了,没有意识到我的 Kotlin fragment 中的 TextView 的 layout_width 为“match_parent”。而我的 Java fragment 中的 TextView 的 layout_width 为“wrap_content”。

TLDR:用户错误 + requestLayout(),线程检查并不总是发生。

最佳答案

CalledFromWrongThreadException 仅在必要时抛出,但并非总是如此。在您的情况下,它会在 ViewRootImpl.requestLayout() 期间调用 ViewRootImpl.checkThread() 时抛出,这是来自 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.");
    }
}

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

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

可以看到,在某些情况下,没有调用requestLayout(),所以没有引入主线程检查。

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

关于java - 修改 UI 线程 View 时 Android UI 不会崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51275494/

相关文章:

java - 将堆栈中的每个元素打印到文本文件中的新行

java - Siteminder:Java 代理 API

java - Seedstack 中的存储库和查找器有什么区别?

java - 如何在android中的字符串中隐藏应用程序网址

android - FragmentManager.put/getFragment 与 findFragmentByTag

c# - 创建大量系统线程并等待 MRE 的影响?

multithreading - Kotlin:如何调用可挂起函数而不等待其结果?

java - 将主体移动到特定点 [Box2D 、 Libgdx]

java - 列表正在用最新值替换所有旧值

java - volatile 引用是在线程之间传递 MotionEvents 的安全方式吗?