为什么TextView的"layout_width" 属性可以判断后台线程访问UI组件是否成功?

Why a TextView's "layout_width" property can determine whether accessing UI components from background threads will succeed?

我是 Android 的新手。我了解到不允许在后台线程中访问 UI 组件,但我遇到的情况让我感到困惑。我做了以下。

(1) 我创建了一个新的 Android Studio 项目,包名称为 com.example.myapplication,SDK 版本最低为 29,使用“空 Activity”模板。

(2)我改了activity_main.xml,添加了一个按钮和一个TextView,其中TextView的android:layout_width设置为wrap_content

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />
</LinearLayout>

(3)我改了MainActivity.kt,给按钮添加了一个点击监听器,它会启动一个线程,改变线程中TextView的文本。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val textView = findViewById<TextView>(R.id.textView)
        val button = findViewById<Button>(R.id.button)

        button.setOnClickListener {
            thread {
                textView.text = "Accessing textView from ${Thread.currentThread().name}"
            }
        }
    }
}

(4) 我在 API 31 模拟器中启动了该应用程序,然后单击了按钮。该应用程序因以下错误而崩溃:

2022-03-29 23:48:42.364 24327-24360/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.myapplication, PID: 24327
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

这是预期的行为。但是,当我将 TextView 的 android:layout_width 属性 更改为 match_parent(或固定宽度,如 100dp)时,重新构建并启动应用程序,然后再次单击按钮, TextView 的文本已成功更改。 线程名称表明我肯定是从后台线程访问 TextView。

为什么会这样?为什么TextView的layout_width属性可以判断后台线程访问UI组件是否成功?

您绝对可以从后台线程访问 UI 线程。

您应该注意,如果您将其设置为 match_parent

,则保存 TextView 的 ViewGroup (LinearLayout) 负责它的宽度和高度

所以这意味着 运行 时 TextView 的宽度或高度的每个动态变化 必须 运行 在 UI 线程不是来自后台线程。这解释了这个例外

2022-03-29 23:48:42.364 24327-24360/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.example.myapplication, PID: 24327
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

上述Exception中的基调是:

Only the original thread that created a view hierarchy can touch its views

由于 UI 线程创建了 ViewGroup (LinearLayout),因此只有 UI 线程可以 运行 更改布局ViewGroup 及其子视图。

现在,当您为 android:layout_width 提供有限值(例如 100dp 时,异常得到解决的原因是有限值意味着 [= 不会有任何动态布局更改56=]线程(创建了ViewGroup)要担心。

您在 运行 时要做的唯一更改是 TextView 中显示的值,这不会更改它的布局结构(因为您设置了 android:layout_width 有限 100dpandroid:layout_heightwrap_content).

你唯一应该担心 android:layout_height 的时候是当你动态更改字体大小或 TextView 的高度时 在另一个线程 中不是 UI 主题。

现在访问另一个线程中的 UI 线程。这样做:

button.setOnClickListener {
        thread {
           runOnUiThread {
            textView.text = "Accessing textView from ${Thread.currentThread().name}"
           }
        }
    }

上面的代码片段从后台线程访问 UI 线程。

希望对您有所帮助。