不止是“叫外卖”:构建生命周期安全的现代化 Android 网络请求

342 阅读3分钟

一句话总结:

不要让你的 Activity(前台服务员)亲自去“点外卖”和“等外卖”。正确的做法是让 ViewModel(餐厅经理)在后台通过协程(最高效的订餐系统)去处理,然后将做好的“菜品”(LiveData/StateFlow)直接送到前台,服务员只管上菜(更新 UI)即可。


第一篇章:“经典”但危险的模式——在 Activity 中直接请求

你的文章已经出色地演示了这种经典模式。它直观、易于理解,但背后隐藏着三大风险:

  1. 屏幕旋转后数据丢失/App 崩溃。
  2. 退出页面后网络请求仍在继续,造成内存泄漏和资源浪费。
  3. 所有逻辑耦合在 Activity 中,代码难以测试和维护。

这种模式适用于快速原型验证,但严禁用于生产环境


第二篇章:现代化的“三层架构”——解耦你的网络请求

一个健壮的网络请求,应该被清晰地划分为三层。

1. 数据层 (Repository):真正的“外卖员”

创建一个 Repository 类,专门负责所有的数据获取逻辑。它应该使用 Kotlin 协程的 suspend 函数 来封装 OkHttp 的调用,将回调地狱转换为干净的同步代码。

Kotlin

// OkHttp + Coroutine 的扩展函数,将回调转换为 suspend 函数
suspend fun Call.await(): Response {
    return suspendCancellableCoroutine { continuation ->
        enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                continuation.resume(response)
            }
            override fun onFailure(call: Call, e: IOException) {
                if (continuation.isCancelled) return
                continuation.resumeWithException(e)
            }
        })
        continuation.invokeOnCancellation {
            cancel()
        }
    }
}

// 数据仓库
class PostRepository {
    private val client = OkHttpClient()
    private val gson = Gson()

    // 这是一个挂起函数,它必须在协程中被调用
    suspend fun getPost(): Post {
        val request = Request.Builder()
            .url("https://jsonplaceholder.typicode.com/posts/1")
            .build()
        
        // 在 IO 线程中执行网络请求
        return withContext(Dispatchers.IO) {
            val response = client.newCall(request).await()
            val json = response.body?.string()
            gson.fromJson(json, Post::class.java)
        }
    }
}

2. 业务逻辑层 (ViewModel):“餐厅经理”

ViewModel 是这场变革的核心。它能在屏幕旋转后存活,是处理业务逻辑和持有 UI 状态的完美场所。

Kotlin

class MainViewModel : ViewModel() {
    private val repository = PostRepository()

    // 使用 LiveData 向 UI 暴露数据。LiveData 是生命周期感知的。
    private val _post = MutableLiveData<Post>()
    val post: LiveData<Post> = _post

    private val _error = MutableLiveData<String>()
    val error: LiveData<String> = _error

    fun fetchPost() {
        // viewModelScope 保证协程在 ViewModel 销毁时自动取消,杜绝内存泄漏
        viewModelScope.launch {
            try {
                _post.value = repository.getPost()
            } catch (e: Exception) {
                _error.value = "请求失败: ${e.message}"
            }
        }
    }
}

3. 表现层 (Activity):只负责展示的“服务员”

Activity 的代码变得极其简单和干净。它不再关心网络请求如何发起、在哪个线程执行,只负责观察 ViewModel 中的数据并更新 UI。

Kotlin

class MainActivity : AppCompatActivity() {
    private lateinit var textView: TextView
    // 使用 by viewModels() 委托属性来获取 ViewModel 实例
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.text_view)

        // 观察 post 数据的变化
        viewModel.post.observe(this) { post ->
            textView.text = post.title
        }

        // 观察错误信息的变化
        viewModel.error.observe(this) { errorMessage ->
            Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
        }

        // 触发数据加载
        viewModel.fetchPost()
    }
    
    // 注意:不再需要 onDestroy 中手动取消网络请求!
}

三、新旧模式的终极对比

维度旧模式 (Activity 中直接请求)现代 MVVM 架构
生命周期管理手动onDestroy 中取消,极易遗忘,导致泄漏自动viewModelScopeViewModel 生命周期绑定
配置变更数据丢失,甚至崩溃ViewModel 存活,数据无缝恢复
线程切换手动调用 runOnUiThread自动LiveData/Flow 自动将结果分发到主线程
代码结构高度耦合,所有逻辑混在 Activity高度解耦,职责清晰 (Repository -> ViewModel -> Activity)
可测试性极差,无法对 Activity 进行单元测试极佳,可以轻松地对 RepositoryViewModel 进行单元测试

最终结论:

使用 OkHttp 发起异步请求,技术实现本身非常简单。但构建一个能在真实、复杂场景下(如屏幕旋转、频繁进出页面)稳定工作的网络请求功能,则必须超越“如何调用 API”的层面,转向现代化的、生命周期安全的架构设计。