提问人:Joschka Goes 提问时间:4/5/2019 最后编辑:Rob KieltyJoschka Goes 更新时间:4/6/2019 访问量:6129
异步更新视图
Updating Views asynchronously
问:
我正在尝试用我想异步获取的 Web 数据填充 a。recyclerview
我有一个调用的函数,它首先使加载指示器可见,然后调用挂起函数加载数据,然后尝试通知视图适配器进行更新。loadData()
onCreateView()
但是在这一点上,我得到以下异常:
android.view.ViewRootImpl$CalledFromWrongThreadException:仅 创建视图层次结构的原始线程可以触摸其视图。
这让我感到惊讶,因为我的理解是只有我的函数是在不同的线程上调用的,而以前当我显示加载指示器时,我显然在正确的线程上。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")
}
}
)
评论
get_top_books()
目前只是一个函数,它创建了一个类型的空数据类,然后休眠了一段时间,所以这应该不是问题。有没有办法从我的片段中调用?快速查找给我的印象是,这是一个活动的东西。Books
runOnUiThread()
调用后,您将在 UI 线程之外。您应该在主线程中运行所有与 UI 相关的代码。为此,您可以使用 .books = task.await()
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
}
评论
Handler
调用后,您将在 UI 线程之外。这是因为你使用 .将其更改为:books = task.await()
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
}
评论
await()
java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize
Caused by: java.lang.AbstractMethodError: abstract method "java.lang.String kotlinx.coroutines.internal.MainDispatcherFactory.hintOnError()"
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0
更改 to 并升级我的 to 版本成功了。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有想法检查我的依赖项的人。
评论
withContext()
评论
Dispatchers.Main
loadData()
withContext(Dispatchers.Main)
?GlobalScope.launch(Dispatchers.Default)
CoroutineScope(Dispatchers.Default).launch
books = withContext(Dispatchers.IO) { get_top_books() }