【问题标题】:How to use AsyncLayoutInflater with Kotlin coroutines without blocking UI如何在不阻塞 UI 的情况下将 AsyncLayoutInflater 与 Kotlin 协程一起使用
【发布时间】:2018-12-20 20:09:50
【问题描述】:

我已经编写了适合我需要的扩展函数:

suspend fun AsyncLayoutInflater.inflateSuspended(@LayoutRes resid: Int, parent: ViewGroup?): View {
    return suspendCoroutine { continuation ->
        inflate(resid, parent) { view, _, parent ->
            continuation.resume(view)
        }
    }
}

但我不确定如何在不阻塞 UI 的情况下使用它。我尝试了 Dispatchers.IO,但我得到 RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

我应该如何使用这个功能?

更新:我发现 inflate 不会冻结 UI,但 addView 会。 calendarHolderScrollView 内。在 calendarView 显示在屏幕上之前,滚动冻结。

val inflater = AsyncLayoutInflater(this)

val startTime = System.currentTimeMillis()
coroutineScope.launch {
    val startTimeInside = System.currentTimeMillis()
    repeat(100) {
        calendarView = inflater.inflateSuspended(R.layout.layout_calendar, calendarHolder)
                as MaterialCalendarView
    }

    val addViewStart = System.currentTimeMillis()
    calendarHolder.addView(calendarView)

    val endTimeInside = System.currentTimeMillis()

    Timber.i("inflate: ${endTimeInside - startTimeInside}")
    Timber.i("addView: ${endTimeInside - addViewStart}")

    setupCalendar()
}

val endTime = System.currentTimeMillis()

Timber.i("outside: ${endTime - startTime}")

即使日志显示:

外部:2 膨胀:2105 添加视图:5

【问题讨论】:

  • 根据定义,它不会阻塞 UI,那么为什么需要协程呢? AsyncLayoutInflater.OnInflateFinishedListener 将在 mainThread 上异步执行
  • 现在,我正在扩充日历,并在OnInflateFinishedListener 中发出网络请求以填充该日历的事件。我打算对它们使用异步等待。
  • 不要直接从监听器发出阻塞网络请求。将其设置为协程后,在 withContext(Dispatchers.IO) 块内进行网络调用。这与在IO 中启动整个协程不同。
  • @osrl 我更新了回答以使用 Volley 发出网络请求,并包含一秒的延迟以表明 UI 从未被阻塞
  • 感谢@MarkoTopolnik,但这已经是我正在做的事情了。实际上,如果我在回调中进行网络调用,它会抛出 NetworkOnMainThreadException

标签: android kotlin layout-inflater kotlinx.coroutines


【解决方案1】:

就像我在评论中提到的那样,AsyncLayoutInflater 按定义是异步的,并且必须在主线程中创建实例,这就是为什么如果更改 Dispatcher 会出现错误。不过,可以将回调样式转换为协程样式。

示例:更新以显示协程的组成

class MainActivity : AppCompatActivity(), CoroutineScope {

    private val activityJob = Job()
    private lateinit var requestQueue: RequestQueue

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + activityJob

    suspend fun AsyncLayoutInflater.inflate(@LayoutRes resid: Int, parent: ViewGroup?): View =
        suspendCoroutine { continuation -> inflate(resid, parent) { view, _, _ -> continuation.resume(view) } }

    suspend fun getTodo(id: Int): String = suspendCoroutine { continuation ->
        val request =  StringRequest(Request.Method.GET, "https://jsonplaceholder.typicode.com/todos/$id",
            Response.Listener { continuation.resume(it) },
            Response.ErrorListener { continuation.resumeWithException(it) }
        )
        requestQueue.add(request)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        requestQueue = Volley.newRequestQueue(this)

        val parent = findViewById<ViewGroup>(R.id.frameLayout)
        val asyncLayoutInflater = AsyncLayoutInflater(this)

        launch {
            val view = asyncLayoutInflater.inflate(R.layout.async_layout, parent) as TextView
            parent.addView(view)

            delay(1000)

            val todo = getTodo(1)
            view.text = todo
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        activityJob.cancel()
        requestQueue.cancelAll { true }
    }
}

More information

【讨论】:

  • 我试过这个,但是它阻塞了主线程。 ui上有明显的冻结。使用Dispatchers.Main 使其在我假设的主线程上运行。
  • 当然是让协程在主线程上运行,这才是重点。但它不会使布局膨胀在主线程上运行。顺便说一句,文档明确指出您永远不应该 GlobalScope.launch 协程。让您的活动实现CoroutineScope,这将同时改进您的代码:您不必显式指定Dispatchers.Main
  • 这正是我正在做的。你是在实际设备上运行的吗?膨胀 textview 也不会花费太多时间。尝试扩充一个非常复杂的视图。就我而言,就像github.com/prolificinteractive/material-calendarview
  • inflate 函数应该切换线程并返回主线程。但是主线程因此而冻结。尝试多次运行 inflate 以获得更好的效果
猜你喜欢
  • 2017-05-24
  • 2011-09-01
  • 2020-12-01
  • 1970-01-01
  • 2013-09-12
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
  • 1970-01-01
相关资源
最近更新 更多