异步更新视图

Updating Views asynchronously

提问人:Joschka Goes 提问时间:4/5/2019 最后编辑:Rob KieltyJoschka Goes 更新时间:4/6/2019 访问量:6129

问:

我正在尝试用我想异步获取的 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
    }
}
Android Kotlin kotlin-coroutines 线程异常

评论

0赞 y.allam 4/5/2019
尝试使用 for 函数Dispatchers.MainloadData()
0赞 EpicPandaForce 4/5/2019
withContext(Dispatchers.Main)?
0赞 Dominic Fischer 4/5/2019
考虑使用 代替 。GlobalScope.launch(Dispatchers.Default)CoroutineScope(Dispatchers.Default).launch
0赞 Dominic Fischer 4/5/2019
还要考虑 ,因为您不需要并发。books = withContext(Dispatchers.IO) { get_top_books() }

答:

0赞 Amine 4/5/2019 #1

UI相关语句应该在UI线程中执行,仅从链接的代码中我无法判断,但也许您正在更改此函数中的UI。get_top_books()

只需将相关的 UI 代码放在 UI 线程中,如下所示

runOnUiThread(
    object : Runnable {
        override fun run() {
            Log.i(TAG, "runOnUiThread")
        }
    }
)

评论

0赞 Joschka Goes 4/6/2019
get_top_books()目前只是一个函数,它创建了一个类型的空数据类,然后休眠了一段时间,所以这应该不是问题。有没有办法从我的片段中调用?快速查找给我的印象是,这是一个活动的东西。BooksrunOnUiThread()
3赞 Andrew Churilo 4/5/2019 #2

调用后,您将在 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
}

或者你可以使用实例来调用方法。ActivtyrunOnUiThread

activity!!.runOnUiThread {
    viewAdapter.notifyDataSetChanged()
    loadingIndicator.visibility = View.INVISIBLE
}

评论

0赞 Joschka Goes 4/6/2019
感谢该方法有效,但是如果我使用Dispatchers.Main,则会出现运行时异常Handler
13赞 Andrew Churilo 4/5/2019 #3

调用后,您将在 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
    }

评论

0赞 Joschka Goes 4/6/2019
谢谢你的回答。所以调用将我从 UI 线程中删除了?如果我将 Dispatcher.Default 更改为 Dispatchers.Main,则会出现以下运行时异常: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()"
1赞 Dominic Fischer 4/6/2019
@JoschkaGoes 您是否已添加到依赖项中?org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0
0赞 Joschka Goes 4/6/2019
@DominicFischer我刚刚检查过,我使用的是 1.0.1,升级到 1.1.0 解决了我的问题!谢谢。
2赞 Joschka Goes 4/6/2019 #4

更改 to 并升级我的 to 版本成功了。Dispatchers.DefaultDispatchers.Mainkotlinx-coroutines-android1.1.1

改变

val task = async(Dispatchers.IO) {
    get_top_books()
}
books = task.await()

books = withContext(Dispatchers.IO) {
    get_top_books()
}

也多了几分优雅。感谢所有回复的人,尤其是@DominicFischer有想法检查我的依赖项的人。

评论

0赞 Boken 12/31/2020
请记住,挂起函数只能从协程或其他挂起函数中调用withContext()