Kotlin 协程在 Android 中阻塞主线程
Kotlin coroutines block main thread in Android
我是 Kotlin 和协同程序的新手。我的 activity 和里面有一个 fun
,检查 User
用户名和密码,如果是真的,return Users
对象。
一切都好。但是当我按下按钮时,我的 activity 被阻止并等待 Users
登录的响应。
我用这个好玩:
private fun checkLogin() : Boolean {
runBlocking {
coroutineScope {
launch {
user = viewModel.getUserAsync(login_username.text.toString(), login_password.text.toString()).await()
}
}
if(user == null){
return@runBlocking false
}
return@runBlocking true
}
return false
}
这是我的视图模型:
class LoginViewModel(app: Application) : AndroidViewModel(app) {
val context: Context = app.applicationContext
private val userService = UsersService(context)
fun getUserAsync(username: String, password: String) = GlobalScope.async {
userService.checkLogin(username, password)
}
}
用户服务:
class UsersService(ctx: Context) : IUsersService {
private val db: Database = getDatabase(ctx)
private val api = WebApiService.create()
override fun insertUser(user: Users): Long {
return db.usersDao().insertUser(user)
}
override suspend fun checkLogin(username: String, pass: String): Users? {
return api.checkLogin(username, pass)
}
}
interface IUsersService {
fun insertUser(user: Users) : Long
suspend fun checkLogin(username: String, pass: String): Users?
}
这是我的 apiInterface:
interface WebApiService {
@GET("users/login")
suspend fun checkLogin(@Query("username") username: String,
@Query("password")password: String) : Users
如何解决在等待从服务器检索数据时阻止 activity 的问题?
您永远不应在 Android 应用程序中使用 runBlocking
。它仅用于 JVM 应用程序的 main
函数或测试中,以允许使用在应用程序退出之前完成的协程。否则它会破坏协程的目的,因为它会阻塞直到它的所有 lambda returns.
您也不应该使用 GlobalScope,因为当 Activity 关闭时取消您的作业会很棘手,并且它会在后台线程而不是主线程中启动协程。您应该为 Activity 使用本地范围。您可以通过在 activity (val scope = MainScope()
) 中创建一个 属性 并在 onDestroy()
(scope.cancel()
) 中取消它来实现。或者,如果您使用 androidx.lifecycle:lifecycle-runtime-ktx
库,您可以只使用现有的 lifecycleScope
属性.
如果你总是在返回之前await
你的异步作业,那么你的整个函数将阻塞直到你得到结果,所以你已经执行了一个后台任务并让它阻塞了主线程。
您可以通过多种方法解决此问题。
- 让 ViewModel 公开一个挂起函数,activity 从协程中调用它。
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
// withContext(Dispatchers.Default) makes the suspend function do something
// on a background thread and resumes the calling thread (usually the main
// thread) when the result is ready. This is the usual way to create a simple
// suspend function. If you don't delegate to a different Dispatcher like this,
// your suspend function runs its code in the same thread that called the function
// which is not what you want for a background task.
suspend fun getUser(username: String, password: String) = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
}
//In your activity somewhere:
lifecycleScope.launch {
user = viewModel.getUser(login_username.text.toString(), login_password.text.toString())
// do something with user
}
- 通过适当的视图模型封装,Activity 真的不应该像这样启动协程。
user
属性 应该是 activity 可以观察到的 ViewModel 中的 LiveData。那么协程只需要从 ViewModel 中启动:
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
init {
fetchUser()
}
private fun fetchUser(username: String, password: String) = viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
_user.value = result
}
}
//In your activity somewhere:
viewModel.user.observe(this) { user ->
// do something with user
}
我知道 internet/wifi 获取或 post 需要某种 background/asynctask。
您是否尝试过使用来自 Android 注释的 @Background/@Uithread。
它将要求您在 gradle 中放置一些依赖项。
但这是我处理服务的一种方式。
这是它的原始 DOC 的原始 link
https://github.com/androidannotations/androidannotations/wiki/WorkingWithThreads#background
我是 Kotlin 和协同程序的新手。我的 activity 和里面有一个 fun
,检查 User
用户名和密码,如果是真的,return Users
对象。
一切都好。但是当我按下按钮时,我的 activity 被阻止并等待 Users
登录的响应。
我用这个好玩:
private fun checkLogin() : Boolean {
runBlocking {
coroutineScope {
launch {
user = viewModel.getUserAsync(login_username.text.toString(), login_password.text.toString()).await()
}
}
if(user == null){
return@runBlocking false
}
return@runBlocking true
}
return false
}
这是我的视图模型:
class LoginViewModel(app: Application) : AndroidViewModel(app) {
val context: Context = app.applicationContext
private val userService = UsersService(context)
fun getUserAsync(username: String, password: String) = GlobalScope.async {
userService.checkLogin(username, password)
}
}
用户服务:
class UsersService(ctx: Context) : IUsersService {
private val db: Database = getDatabase(ctx)
private val api = WebApiService.create()
override fun insertUser(user: Users): Long {
return db.usersDao().insertUser(user)
}
override suspend fun checkLogin(username: String, pass: String): Users? {
return api.checkLogin(username, pass)
}
}
interface IUsersService {
fun insertUser(user: Users) : Long
suspend fun checkLogin(username: String, pass: String): Users?
}
这是我的 apiInterface:
interface WebApiService {
@GET("users/login")
suspend fun checkLogin(@Query("username") username: String,
@Query("password")password: String) : Users
如何解决在等待从服务器检索数据时阻止 activity 的问题?
您永远不应在 Android 应用程序中使用 runBlocking
。它仅用于 JVM 应用程序的 main
函数或测试中,以允许使用在应用程序退出之前完成的协程。否则它会破坏协程的目的,因为它会阻塞直到它的所有 lambda returns.
您也不应该使用 GlobalScope,因为当 Activity 关闭时取消您的作业会很棘手,并且它会在后台线程而不是主线程中启动协程。您应该为 Activity 使用本地范围。您可以通过在 activity (val scope = MainScope()
) 中创建一个 属性 并在 onDestroy()
(scope.cancel()
) 中取消它来实现。或者,如果您使用 androidx.lifecycle:lifecycle-runtime-ktx
库,您可以只使用现有的 lifecycleScope
属性.
如果你总是在返回之前await
你的异步作业,那么你的整个函数将阻塞直到你得到结果,所以你已经执行了一个后台任务并让它阻塞了主线程。
您可以通过多种方法解决此问题。
- 让 ViewModel 公开一个挂起函数,activity 从协程中调用它。
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
// withContext(Dispatchers.Default) makes the suspend function do something
// on a background thread and resumes the calling thread (usually the main
// thread) when the result is ready. This is the usual way to create a simple
// suspend function. If you don't delegate to a different Dispatcher like this,
// your suspend function runs its code in the same thread that called the function
// which is not what you want for a background task.
suspend fun getUser(username: String, password: String) = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
}
//In your activity somewhere:
lifecycleScope.launch {
user = viewModel.getUser(login_username.text.toString(), login_password.text.toString())
// do something with user
}
- 通过适当的视图模型封装,Activity 真的不应该像这样启动协程。
user
属性 应该是 activity 可以观察到的 ViewModel 中的 LiveData。那么协程只需要从 ViewModel 中启动:
class LoginViewModel(app: Application) : AndroidViewModel(app) {
//...
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
init {
fetchUser()
}
private fun fetchUser(username: String, password: String) = viewModelScope.launch {
val result = withContext(Dispatchers.Default) {
userService.checkLogin(username, password)
}
_user.value = result
}
}
//In your activity somewhere:
viewModel.user.observe(this) { user ->
// do something with user
}
我知道 internet/wifi 获取或 post 需要某种 background/asynctask。 您是否尝试过使用来自 Android 注释的 @Background/@Uithread。 它将要求您在 gradle 中放置一些依赖项。 但这是我处理服务的一种方式。
这是它的原始 DOC 的原始 link https://github.com/androidannotations/androidannotations/wiki/WorkingWithThreads#background