修改UI线程外的视图时,Android UI不会崩溃 [英] Android UI Not Crashing When Modifying View off UI Thread

查看:94
本文介绍了修改UI线程外的视图时,Android UI不会崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

场景:

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

I ran into a strange issue while testing out threads in my fragment.

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

I have a fragment written in Kotlin with the following snippet in 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())
    }
}

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

is MainThread() is a function that checks if the current thread is the main thread like so:

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

我看到我的TextView在2秒后更新为文本东西:错误"

I am seeing my TextView get updated after 2 seconds with the text "Something something : false"

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

Seeing false tells me that this thread is currently not the UI/Main thread.

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

I thought this was strange so I created the same fragment but written in Java instead with the following snippet from onResume():

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

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

The app crashes with the following exception as expected:

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)

我做了一些研究,但我找不到真正能解释这一点的东西.另外,请假设我的观点都正确地夸大了.

I did some research but I couldn't really find something that explains this. Also, please assume that my views are all inflated correctly.

问题:

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

Why does my app not crash when I modify my TextView in the runnable that's running off my UI thread in the Fragment written in Kotlin?

如果某些文档中的某些内容对此有所解释,有人可以请我参考一下吗?

If there's something in some documentation somewhere that explains this, can someone please refer me to this?

我实际上并没有试图从UI线程中修改UI,我只是很好奇为什么会发生这种情况.

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

Please let me know if you guys need any more information. Thanks a lot!

更新: 按照@Hong Duan提到的,requestLayout()没有被调用.这与Kotlin/Java无关,但与TextView本身无关.

Update: As per what @Hong Duan mentioned, requestLayout() was not getting called. This has nothing to do with Kotlin/Java but with the TextView itself.

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

I goofed and didn't realize that the TextView in my Kotlin fragment has a layout_width of "match_parent." Whereas the TextView in my Java fragment has a layout_width of "wrap_content."

TLDR:用户错误+ requestLayout(),其中并不总是进行线程检查.

TLDR: User error + requestLayout(), where thread checking doesn't always occur.

推荐答案

CalledFromWrongThreadException仅在必要时抛出,但并不总是抛出.在您的情况下,在ViewRootImpl.requestLayout()期间调用ViewRootImpl.checkThread()时会抛出该错误,这是ViewRootImpl.java中的代码:

The CalledFromWrongThreadException only throws when necessary, but not always. In your cases, it throws when the ViewRootImpl.checkThread() is called during ViewRootImpl.requestLayout(), here is the code from 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,更新文本时并不一定总是需要重播,我们可以在

And for TextView, it's not always necessary to relayout when we update it's text, we can see the logic in the 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(),因此不会引入主线程检查.

As you can see, in some cases, the requestLayout() is not called, so the main thread check is not introduced.

因此,我认为关键不是与Kotlin或Java有关,而是与确定requestLayout()是否被调用的TextView s的布局参数有关.

So I think the key point is not about Kotlin or Java, it's about the TextViews' layout params which determined whether requestLayout() is called or not.

这篇关于修改UI线程外的视图时,Android UI不会崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆