使用Flow时出现“Suspension function can only be called...”错误排查
Flow中emit在回调内报错:
Suspension functions can be called only within coroutine body 我正在学习响应式编程与Flow的使用,于是从联系人类中取出一个方法进行改写,尝试用Flow来发射数据。
代码如下:
val getMCxContactsFlow: Flow<MCxContact> = flow { val contact: MCxContact? = null val uri = RawContacts.CONTENT_URI .buildUpon() .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName) .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType) .build() val cursor = contentResolver.query( uri, arrayOf(Contacts._ID), null, null, null ) getCursorInformation(cursor) { result -> result.apply { try { val id = getStringOrNull(getColumnIndex(Contacts._ID)) ?: throw Exception("Cant find contact ID") Log.d(TAG, id) val query = contentResolver.query( Data.CONTENT_URI, null, "${Data.RAW_CONTACT_ID} =?", arrayOf(id), null ) val contacts = getContactFromQuery(query) emit(contacts) // 触发错误的emit } catch (e: Exception) { e.printStackTrace() } } } emit(contact!!) // 此处emit可正常工作 } private fun getCursorInformation( cursor: Cursor?, iterator: (cursor: Cursor) -> Unit ) { if (cursor == null) throw Exception("getCursorInformation: cursor is null") else { cursor.apply { if (count > 0) { while (moveToNext()) iterator(this) } else { throw Exception("Cursor data is empty") } close() } } }
Android在标注的emit处报错:Suspension functions can be called only within coroutine body,而末尾的emit却能正常工作,猜测是作用域问题,但不清楚具体哪里出错了。
问题出在哪
问题核心是挂起函数的调用上下文限制:
flow{}代码块本身是协程体,属于挂起上下文,所以末尾的emit能正常调用。- 你传给
getCursorInformation的iterator是普通的(Cursor) -> Unit函数,这个lambda的执行环境不在协程体内。而emit是挂起函数,只能在挂起函数或者协程体里调用,所以这里调用emit就会报错。
怎么改
把getCursorInformation改成挂起函数,同时把它的iterator参数也改成挂起函数类型,这样lambda里就能调用emit了:
修改后的代码:
val getMCxContactsFlow: Flow<MCxContact> = flow { val contact: MCxContact? = null val uri = RawContacts.CONTENT_URI .buildUpon() .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName) .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType) .build() val cursor = contentResolver.query( uri, arrayOf(Contacts._ID), null, null, null ) getCursorInformation(cursor) { result -> result.apply { try { val id = getStringOrNull(getColumnIndex(Contacts._ID)) ?: throw Exception("Cant find contact ID") Log.d(TAG, id) val query = contentResolver.query( Data.CONTENT_URI, null, "${Data.RAW_CONTACT_ID} =?", arrayOf(id), null ) val contacts = getContactFromQuery(query) emit(contacts) // 现在可以正常调用emit } catch (e: Exception) { e.printStackTrace() } } } emit(contact!!) } // 修改为挂起函数,同时将iterator参数改为挂起函数类型 private suspend fun getCursorInformation( cursor: Cursor?, iterator: suspend (cursor: Cursor) -> Unit ) { if (cursor == null) throw Exception("getCursorInformation: cursor is null") else { cursor.apply { if (count > 0) { while (moveToNext()) iterator(this) } else { throw Exception("Cursor data is empty") } close() } } }
简单说明
- 给
getCursorInformation加上suspend关键字,让它成为挂起函数,这样就能在flow{}的协程体内调用它。 - 把
iterator参数的类型改成suspend (Cursor) -> Unit,表示这个lambda是挂起函数,允许在内部调用emit这类挂起函数。
内容的提问来源于stack exchange,提问作者Chris Pi
相关产品推荐
相关产品推荐

