Kotlin中不使用GlobalScope.launch调用async/await方法的实现方式
问题根源
你当前实现直接使用GlobalScope.async启动协程存在明显隐患:该作用域和应用进程生命周期绑定,不受Activity、ViewModel的生命周期约束,页面销毁、ViewModel被回收时任务不会自动终止,极易引发内存泄漏、无意义的资源消耗、甚至UI操作空指针。
完全不需要依赖GlobalScope,按照协程的最佳实践改造后,直接使用官方提供的生命周期绑定作用域即可实现安全调用。
第一步:改造Service层代码
网络请求属于数据层逻辑,不应该在这一层持有协程作用域,只需要将方法声明为挂起函数,通过withContext指定IO调度器执行阻塞请求即可,协程的生命周期管控交给上层调用方:
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import java.net.URL class Service { private val client: OkHttpClient = OkHttpClient() suspend fun getRequest(sUrl: String): String? = withContext(Dispatchers.IO) { // 替换原来的Thread.sleep,避免阻塞IO线程 delay(2000) var result: String? = null try { val url = URL(sUrl) val request = Request.Builder().url(url).build() // OkHttp同步请求本身是阻塞调用,放在IO调度器中执行不会卡住主线程 val response = client.newCall(request).execute() result = response.body?.string() } catch (e: Exception) { println("Get request execute error: ${e.localizedMessage}") } result } }
注意:异常捕获只需要捕获
Exception即可,不要捕获Error,后者是虚拟机层面的系统级错误(如OOM、栈溢出),捕获后也无法正常恢复。
第二步:对应组件中调用
ViewModel场景调用
AndroidX ViewModel组件自带生命周期绑定的viewModelScope,该作用域会在ViewModel被清除时自动取消所有启动的协程,不需要手动管理生命周期:
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch class DemoViewModel : ViewModel() { private val requestService = Service() fun loadContent(url: String) { viewModelScope.launch { // 协程默认运行在主线程调度器,拿到结果可以直接更新UI相关的状态 val content = requestService.getRequest(url) // 后续业务逻辑:比如更新LiveData、StateFlow给Activity/Fragment观察 } } }
Activity/Fragment场景调用
AndroidX的Activity、Fragment组件自带lifecycleScope,该作用域和组件生命周期绑定,页面销毁时会自动取消协程:
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.launch class DemoActivity : AppCompatActivity() { private val requestService = Service() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_demo) // 触发请求 fetchPageContent("https://your-api-address.com/test") } private fun fetchPageContent(url: String) { lifecycleScope.launch { val content = requestService.getRequest(url) // 拿到结果直接更新UI即可,比如给TextView赋值 } } }
额外注意事项
- 永远不要在业务/页面相关的逻辑中使用
GlobalScope,除非你明确需要启动一个和应用进程生命周期一致的全局任务 - 所有阻塞式操作(网络请求、数据库读写、大文件处理)都要通过
withContext切换到对应调度器执行,不要在主线程调度器中运行阻塞逻辑 - 如果需要并行请求多个接口,可以在对应生命周期作用域内使用
async启动子协程,再通过await()拿到结果,不要把async逻辑写在数据层。
内容的提问来源于stack exchange,提问作者sofnomic cr
相关产品推荐
相关产品推荐

