核心内容摘要
tao-8k Embedding模型部署实操:Kubernetes Helm Chart自动化部署方案
Coroutines协程在Android中的简易应用与解析替代传统多线程的优秀方案
前言Coroutines协程因何而出现现如今随着项目开发的规模逐渐增大项目在性能上也会愈发接近瓶颈在解决性能方面的问题时常常会考虑两种方案一种常常是以算法为导向的算法复杂度的性能优化另一种常常是以提升系统并发程度为导向为导向的系统并发程度的优化。
而后者是在项目开发中最为常见的。
在Android开发中初具规模的项目一般都绕不开异步编程——网络请求、文件IO、数据库操作等耗时任务必须脱离主线程否则会导致ANR。
在项目中我们常常会用到ThreadHandler的传统处理异步方法虽然它技术出现早、使用相对成熟但其中的痛点显而易见回调嵌套形成“回调地狱”代码可读性差线程创建/切换开销大高并发场景易耗尽资源手动管理生命周期稍不注意就内存泄漏异常处理分散调试难度高。
这些痛点虽然可以通过合理的设计进行排除但整体而言过程稍显繁琐。
并且当项目开发人员的开发经验不足时或许排查问题本身或许就已经苦不堪言。
自然Google官方也早已察觉这些在项目开发过程中的繁琐之处并一步步着手进行相关的改进比如使用推动Kotlin语言替代Java语言作为Android开发主要语言、推动JetPack替代XML开发体系……从中我们可以看出Android开发一步步从繁琐向精简发展的过程。
而在异步方案的部分官方以Kotlin协程作为语言级轻量级异步方案开发出基于Coroutines协程的解决方案。
该方案完美解决了这些问题用同步代码的写法实现异步逻辑轻量级、生命周期安全、并发控制灵活也是Google官方主推的异步编程模型。
本文将从Coroutines协程的相关介绍开始再从“传统Thread/Handler示例”入手一步步改造成协程版本解析协程的核心原理、项目中的具体用法。
并从中窥探Coroutines协程在Android开发中的应用与前景。
Coroutines协程简介核心概要Kotlin协程Coroutines是Kotlin 语言提供的语言级异步编程方案也是Google官方推荐的Android异步编程核心技术。
它并非替代线程而是对线程的 “轻量级封装与优化”旨在解决传统多线程Thread/Handler、回调式异步带来的可读性差、资源开销大、生命周期管理复杂等痛点让开发者以 “同步代码的写法实现异步逻辑”。
特点
结构化并发Structured Concurrency协程的核心设计原则所有协程必须运行在指定的CoroutineScope中作用域明确绑定组件生命周期如ViewModelScope随 ViewModel 销毁而取消父协程取消时所有子协程自动取消从根本上避免异步任务导致的内存泄漏异常可向上传播至作用域层面便于集中捕获和处理。
极致轻量级单个协程仅占用几十 KB 内存单进程可创建数万个协程而无性能压力线程仅能创建数百个协程切换由用户态框架调度无需操作系统内核参与切换开销仅为线程的1/1000。
协作式调度与线程的 “抢占式调度”操作系统强制切换不同协程仅在主动调用suspend函数如delay、withContext时让出线程无强制上下文切换减少CPU开销适配移动端低功耗场景。
同步写法实现异步逻辑彻底告别 “回调地狱”传统Thread/Handler需通过Handler.sendMessage传递结果逻辑分散协程用线性的同步代码书写异步流程可读性与维护性大幅提升。
灵活的并发控制launch启动无返回值的协程适用于 “执行操作但无需结果” 的场景async/await启动带返回值的协程支持多任务并行执行、结果聚合Flow基于协程的响应式数据流支持实时数据监听、分页加载、背压处理。
在Android 开发中协程的几个核心概念——协程作用域Scope、挂起函数suspend、调度器Dispatchers、挂起与恢复基本可覆盖所有异步场景。
传统异步项目的搭建在传统项目中以一个典型场景为例。
覆盖了绝大部分知识点点击按钮→模拟网络请求(2秒)→模拟数据库存储(1秒)→更新UI要求处理异常、管理生命周期。
1 布局文件activity_main.xml?xml version
0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:gravitycenter android:padding20dp ProgressBar android:idid/progressBar android:layout_widthwrap_content android:layout_heightwrap_content android:visibilitygone/ TextView android:idid/tv_result android:layout_widthmatch_parent android:layout_heightwrap_content android:marginTop20dp android:textSize16sp/ Button android:idid/btn_execute android:layout_widthwrap_content android:layout_heightwrap_content android:marginTop20dp android:text执行耗时任务/ /LinearLayout
2 Thread/Handler示例代码import android.os.Bundle import android.os.Handler import android.os.Looper import android.os.Message import android.view.View import android.widget.Button import android.widget.ProgressBar import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import java.lang.Exception class MainActivity : AppCompatActivity() { // UI控件 private lateinit var progressBar: ProgressBar private lateinit var tvResult: TextView private lateinit var btnExecute: Button // 标记Activity是否销毁用于终止线程 private var isDestroyed false // 主线程Handler用于子线程更新UI private val mainHandler object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { super.handleMessage(msg) when (msg.what) { MSG_LOADING - { progressBar.visibility View.VISIBLE tvResult.text 加载中... btnExecute.isEnabled false } MSG_SUCCESS - { progressBar.visibility View.GONE tvResult.text 任务成功${msg.obj} btnExecute.isEnabled true } MSG_ERROR - { progressBar.visibility View.GONE tvResult.text 任务失败${msg.obj} btnExecute.isEnabled true } } } } // 消息类型常量 companion object { private const val MSG_LOADING 1 private const val MSG_SUCCESS 2 private const val MSG_ERROR 3 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initView() setClickEvent() } private fun initView() { progressBar findViewById(R.id.progressBar) tvResult findViewById(R.id.tv_result) btnExecute findViewById(R.id.btn_execute) } private fun setClickEvent() { btnExecute.setOnClickListener { // 发送加载中消息 mainHandler.sendEmptyMessage(MSG_LOADING) // 启动子线程执行耗时任务 val taskThread Thread { try { // 模拟网络请求2秒 val userData simulateNetworkRequest() if (isDestroyed) returnThread // 销毁则终止 // 模拟数据库存储1秒 val saveResult simulateSaveToDb(userData) if (isDestroyed) returnThread // 发送成功消息 val successMsg Message.obtain() successMsg.what MSG_SUCCESS successMsg.obj 用户数据$userData存储结果$saveResult mainHandler.sendMessage(successMsg) } catch (e: Exception) { // 发送错误消息 val errorMsg Message.obtain() errorMsg.what MSG_ERROR errorMsg.obj e.message ?: 未知错误 mainHandler.sendMessage(errorMsg) } } taskThread.start() } } /** * 模拟网络请求耗时2秒 */ private fun simulateNetworkRequest(): String { Thread.sleep(
// 阻塞线程 if (Math.random()
0.
{ throw Exception(网络请求超时) } return userId1001, userName张三 } /** * 模拟数据库存储耗时1秒 */ private fun simulateSaveToDb(data: String): Boolean { Thread.sleep(
// 阻塞线程 return true } override fun onDestroy() { super.onDestroy() // 标记销毁清空Handler消息 isDestroyed true mainHandler.removeCallbacksAndMessages(null) } }
传统异步项目的具体分析由上述的代码可以观察我们可以分析出相关问题
1 回调函数由上述的代码可以观察到Thread在代码的分布相对分散并且在线程的创建、销毁等方面的回调函数的创建与使用都相对分散难以较好的把握相关线程的变化可能导致由线程资源分配问题带来的问题。
2 资源开销问题由于每个线程只能用于处理特定的事务本实践中仅以一个相同的事务为例线程只能通过创建去处理相应的事务不具备可复用性因此需要不断地消耗系统的资源去达成相关任务的进行。
由于服务器资源有限在高并发的场景下极有可能带来资源的耗尽和服务器的宕机。
3 生命周期管理问题每次线程的创建与销毁都需要调用相对应的create或destroy等方法去管理相对应的线程的生命周期。
因此要求项目中必须对线程的管理非常清晰否则易导致线程管理不当造成的内存泄漏。
4 线程阻塞线程阻塞时不会释放相关的线程资源仍然造成相关资源的消耗一旦线程阻塞在系统上存在较多仍可能造成相关的内存泄露。
5 异常处理复杂在捕获线程异常后仍需要Message进行异常的传递相关逻辑仍然比较分散。
Coroutines协程提供的解决方案接下来将使用Coroutines协程的核心概念结合项目进行相关改造。
1 引入协程依赖在app/build.gradle中添加相关依赖dependencies { // 协程核心库 implementation org.jetbrains.kotlinx:kotlinx-coroutines-core:
1.
3 // 协程Android适配 implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:
1.
3 // Lifecycle协程扩展 implementation androidx.lifecycle:lifecycle-runtime-ktx:
2.
2 }
2 替换项目中的ThreadHandler部分代码使用LifecycleScope替代Thread、withContext替代Handler线程切换。
其中核心API包括lifecycleScope.launch、withContext(Dispatchers.IO)、suspend挂起函数、结构化并发分别实现1协程作用域绑定Activity生命周期销毁时自动取消所有子协程2切换到IO线程池复用线程执行完自动切回原调度器Main3标记耗时操作delay替代Thread.sleep非阻塞释放线程4协程自动跟随作用域生命周期无需手动管理isDestroyedimport android.os.Bundle import android.view.View import android.widget.Button import android.widget.ProgressBar import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import java.lang.Exception class MainActivity : AppCompatActivity() { private lateinit var progressBar: ProgressBar private lateinit var tvResult: TextView private lateinit var btnExecute: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initView() setClickEvent() } private fun initView() { progressBar findViewById(R.id.progressBar) tvResult findViewById(R.id.tv_result) btnExecute findViewById(R.id.btn_execute) } private fun setClickEvent() { btnExecute.setOnClickListener { // 主线程直接更新加载UI progressBar.visibility View.VISIBLE tvResult.text 加载中... btnExecute.isEnabled false // 启动协程绑定Activity生命周期自动取消 lifecycleScope.launch { // 默认Dispatchers.Main主线程 try { // 切换到IO线程执行耗时任务非阻塞 val result withContext(Dispatchers.IO) { println(当前线程${Thread.currentThread().name}) // 模拟网络请求delay替代Thread.sleep非阻塞 val userData simulateNetworkRequest() // 模拟数据库存储 val saveResult simulateSaveToDb(userData) 用户数据$userData存储结果$saveResult } // 自动切回主线程更新UI progressBar.visibility View.GONE tvResult.text 任务成功$result btnExecute.isEnabled true } catch (e: Exception) { // 集中捕获异常更新UI progressBar.visibility View.GONE tvResult.text 任务失败${e.message ?: 未知错误} btnExecute.isEnabled true } } } } /** * 改造为挂起函数仅能在协程/其他挂起函数中调用 */ private suspend fun simulateNetworkRequest(): String { delay(
// 非阻塞挂起释放线程 if (Math.random()
0.
{ throw Exception(网络请求超时) } return userId1001, userName张三 } private suspend fun simulateSaveToDb(data: String): Boolean { delay(
return true } }
3 进一步优化使用async/await实现并发任务在原先ThreadHandler版本中任务是串行执行的现在可以使用async/await实现并发执行。
// 定义全局异常处理器 private val coroutineExceptionHandler CoroutineExceptionHandler { _, throwable - runOnUiThread { progressBar.visibility View.GONE tvResult.text 全局捕获异常${throwable.message ?: 未知错误} btnExecute.isEnabled true } } // 启动协程时绑定 lifecycleScope.launch(coroutineExceptionHandler) { }//
封装Repository类 class UserRepository { suspend fun fetchAndSaveUser(): PairString, Boolean { return withContext(Dispatchers.IO) { val userDataDeferred async { simulateNetworkRequest() } val saveResultDeferred async { val userData userDataDeferred.await() simulateSaveToDb(userData) } Pair(userDataDeferred.await(), saveResultDeferred.await()) } } private suspend fun simulateNetworkRequest(): String { delay(
if (Math.random()
0.
{ throw Exception(网络请求超时) } return userId1001, userName张三 } private suspend fun simulateSaveToDb(data: String): Boolean { delay(
return true } } //
Activity中调用 class MainActivity : AppCompatActivity() { private val userRepository UserRepository() // ...其他代码 private fun setClickEvent() { btnExecute.setOnClickListener { progressBar.visibility View.VISIBLE tvResult.text 加载中... btnExecute.isEnabled false lifecycleScope.launch(coroutineExceptionHandler) { // 调用封装的挂起函数UI与业务解耦 val (userData, saveResult) userRepository.fetchAndSaveUser() progressBar.visibility View.GONE tvResult.text 任务成功用户数据$userData存储结果$saveResult btnExecute.isEnabled true } } } }
传统ThreadHandler与Coroutines协程的差异维度Thread/Handler协程本质内核态线程OS调度用户态轻量级线程Kotlin框架调度开销MB级内存最多数百个KB级内存数万个无压力调度抢占式OS强制切换协作式仅挂起时让出线程编程模型回调式嵌套混乱同步式线性逻辑生命周期手动管理易泄漏结构化并发自动取消异常处理分散传递繁琐集中捕获统一处理
七、
总结协程彻底解决了传统 Thread/Handler 的痛点轻量级数万个协程无性能压力适配高并发场景简洁同步写法实现异步告别回调地狱安全结构化并发自动管理生命周期杜绝内存泄漏高效协程挂起释放线程资源利用率提升数倍。
目前协程已成为 Android异步编程的事实标准Retrofit、Room、Jetpack Compose等都已深度集成。
未来随着Kotlin Multiplatform的发展协程还将实现跨平台异步逻辑统一成为移动端开发的核心技能。
作者卢志伟原文链接:Coroutines协程在Android中的简易应用与解析替代传统多线程的优秀方案