7

我正在尝试recyclerview使用我想要异步获取的网络数据填充 a。

我有一个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
    }
}
4

4 回答 4

13

调用后,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
    }
于 2019-04-05T14:57:03.733 回答
3

调用后,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
}
于 2019-04-05T13:46:06.163 回答
2

更改Dispatchers.DefaulttoDispatchers.Main并升级我的 to 版本就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,他们有想法检查我的依赖项。

于 2019-04-05T20:57:59.497 回答
0

UI 相关的语句应该在 UI 线程中执行,我不能仅从链接的代码中看出,但也许你正在更改 thsi 函数中的 UI get_top_books()

只需像这样将相关的UI代码放在UI线程中

runOnUiThread(
    object : Runnable {
        override fun run() {
            Log.i(TAG, "runOnUiThread")
        }
    }
)
于 2019-04-05T13:39:08.387 回答