异步更新视图
Updating Views asynchronously
我正在尝试使用我想异步获取的网络数据填充 recyclerview
。
我有一个名为onCreateView()
的函数loadData()
,它首先使加载指示器可见,然后调用挂起函数加载数据,然后尝试通知视图适配器进行更新。
但此时我得到以下异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the
original thread that created a view hierarchy can touch its views.
这让我感到惊讶,因为我的理解是只有我的 get_top_books()
函数在不同的线程上被调用,而之前当我显示加载指示器时,我显然是在正确的线程上。
那么为什么会引发这个 运行 时间异常?
我的代码:
class DiscoverFragment: Fragment() {
lateinit var loadingIndicator: TextView
lateinit var viewAdapter: ViewAdapter
var books = Books(arrayOf<String>("no books"), arrayOf<String>("no books"), arrayOf<String>("no books"))
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val viewFrame = layoutInflater?.inflate(R.layout.fragment_discover, container, false)
val viewManager = GridLayoutManager(viewFrame!!.context, 2)
viewAdapter = ViewAdapter(books)
loadingIndicator = viewFrame.findViewById<TextView>(R.id.loading_indicator)
val pxSpacing = (viewFrame.context.resources.displayMetrics.density * 8f + .5f).toInt()
val recyclerView = viewFrame.findViewById<RecyclerView>(R.id.recycler).apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
addItemDecoration(RecyclerViewDecorationSpacer(pxSpacing, 2))
}
loadData()
return viewFrame
}
fun loadData() = CoroutineScope(Dispatchers.Default).launch {
loadingIndicator.visibility = View.VISIBLE
val task = async(Dispatchers.IO) {
get_top_books()
}
books = task.await()
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
}
UI 相关语句应该在 UI 线程中执行,仅从链接代码我无法判断,但也许您正在更改 UI 这个函数 get_top_books()
.
只需将相关 UI 代码放在 UI 线程中,就像这样
runOnUiThread(
object : Runnable {
override fun run() {
Log.i(TAG, "runOnUiThread")
}
}
)
调用 books = task.await()
之后,您处于 UI 线程之外。您应该 运行 所有 UI 主线程中的相关代码。为此,您可以使用 Dispatchers.Main
.
CoroutineScope(Dispatchers.Main).launch {
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
或使用Handler
Handler(Looper.getMainLooper()).post {
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
或者你可以使用Activty
实例来调用runOnUiThread
方法。
activity!!.runOnUiThread {
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
调用 books = task.await()
之后,您处于 UI 线程之外。那是因为你用了CoroutineScope(Dispatchers.Default)
。将其更改为 Dispatchers.Main
:
fun loadData() = CoroutineScope(Dispatchers.Main).launch {
loadingIndicator.visibility = View.VISIBLE
val task = async(Dispatchers.IO) {
get_top_books()
}
books = task.await()
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
将 Dispatchers.Default
更改为 Dispatchers.Main
并将我的 kotlinx-coroutines-android
版本升级到 1.1.1
成功了。
改变
val task = async(Dispatchers.IO) {
get_top_books()
}
books = task.await()
到
books = withContext(Dispatchers.IO) {
get_top_books()
}
也更优雅一点。感谢所有回复的人,尤其是@DominicFischer,他有想法检查我的依赖关系。
我正在尝试使用我想异步获取的网络数据填充 recyclerview
。
我有一个名为onCreateView()
的函数loadData()
,它首先使加载指示器可见,然后调用挂起函数加载数据,然后尝试通知视图适配器进行更新。
但此时我得到以下异常:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
这让我感到惊讶,因为我的理解是只有我的 get_top_books()
函数在不同的线程上被调用,而之前当我显示加载指示器时,我显然是在正确的线程上。
那么为什么会引发这个 运行 时间异常?
我的代码:
class DiscoverFragment: Fragment() {
lateinit var loadingIndicator: TextView
lateinit var viewAdapter: ViewAdapter
var books = Books(arrayOf<String>("no books"), arrayOf<String>("no books"), arrayOf<String>("no books"))
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val viewFrame = layoutInflater?.inflate(R.layout.fragment_discover, container, false)
val viewManager = GridLayoutManager(viewFrame!!.context, 2)
viewAdapter = ViewAdapter(books)
loadingIndicator = viewFrame.findViewById<TextView>(R.id.loading_indicator)
val pxSpacing = (viewFrame.context.resources.displayMetrics.density * 8f + .5f).toInt()
val recyclerView = viewFrame.findViewById<RecyclerView>(R.id.recycler).apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
addItemDecoration(RecyclerViewDecorationSpacer(pxSpacing, 2))
}
loadData()
return viewFrame
}
fun loadData() = CoroutineScope(Dispatchers.Default).launch {
loadingIndicator.visibility = View.VISIBLE
val task = async(Dispatchers.IO) {
get_top_books()
}
books = task.await()
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
}
UI 相关语句应该在 UI 线程中执行,仅从链接代码我无法判断,但也许您正在更改 UI 这个函数 get_top_books()
.
只需将相关 UI 代码放在 UI 线程中,就像这样
runOnUiThread(
object : Runnable {
override fun run() {
Log.i(TAG, "runOnUiThread")
}
}
)
调用 books = task.await()
之后,您处于 UI 线程之外。您应该 运行 所有 UI 主线程中的相关代码。为此,您可以使用 Dispatchers.Main
.
CoroutineScope(Dispatchers.Main).launch {
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
或使用Handler
Handler(Looper.getMainLooper()).post {
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
或者你可以使用Activty
实例来调用runOnUiThread
方法。
activity!!.runOnUiThread {
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
调用 books = task.await()
之后,您处于 UI 线程之外。那是因为你用了CoroutineScope(Dispatchers.Default)
。将其更改为 Dispatchers.Main
:
fun loadData() = CoroutineScope(Dispatchers.Main).launch {
loadingIndicator.visibility = View.VISIBLE
val task = async(Dispatchers.IO) {
get_top_books()
}
books = task.await()
viewAdapter.notifyDataSetChanged()
loadingIndicator.visibility = View.INVISIBLE
}
将 Dispatchers.Default
更改为 Dispatchers.Main
并将我的 kotlinx-coroutines-android
版本升级到 1.1.1
成功了。
改变
val task = async(Dispatchers.IO) {
get_top_books()
}
books = task.await()
到
books = withContext(Dispatchers.IO) {
get_top_books()
}
也更优雅一点。感谢所有回复的人,尤其是@DominicFischer,他有想法检查我的依赖关系。