为什么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
有限 100dp
和 android:layout_height
在 wrap_content
).
你唯一应该担心 android:layout_height
的时候是当你动态更改字体大小或 TextView
的高度时 在另一个线程 中不是 UI 主题。
现在访问另一个线程中的 UI 线程。这样做:
button.setOnClickListener {
thread {
runOnUiThread {
textView.text = "Accessing textView from ${Thread.currentThread().name}"
}
}
}
上面的代码片段从后台线程访问 UI 线程。
希望对您有所帮助。
我是 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
。
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
有限 100dp
和 android:layout_height
在 wrap_content
).
你唯一应该担心 android:layout_height
的时候是当你动态更改字体大小或 TextView
的高度时 在另一个线程 中不是 UI 主题。
现在访问另一个线程中的 UI 线程。这样做:
button.setOnClickListener {
thread {
runOnUiThread {
textView.text = "Accessing textView from ${Thread.currentThread().name}"
}
}
}
上面的代码片段从后台线程访问 UI 线程。
希望对您有所帮助。