android - 如何在不阻塞 UI 的情况下将 AsyncLayoutInflater 与 Kotlin 协程一起使用

标签 android kotlin layout-inflater kotlinx.coroutines

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

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 会。 calendarHolder 位于 ScrollView 内。滚动在 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 添加 View :5

最佳答案

就像我在评论中提到的,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

关于android - 如何在不阻塞 UI 的情况下将 AsyncLayoutInflater 与 Kotlin 协程一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53875367/

相关文章:

error-handling - 在RxJava/RxKotlin中,返回Completable.error(Exception())与抛出之间有什么区别?

kotlin - 如何在Kotlin中扩展Java静态字段

java - 在 SurfaceView 之上添加布局

java - 在 Android 中 inflatedView 后,自定义 View 子项为空

java - 警报对话框的 inflated layout 不起作用

android - 自定义摄像头常用解决方案

android - 在 Android TV Leanback 中实现文件对话框

java - zxing Intent "google goggles"无法识别条形码

java - &lt;![CDATA ["' "]]> 返回两个结果。某些应用程序版本打印 "' ",另一个应用程序版本打印 '.为什么?

java - 如何在 map View 上显示自定义信息窗口