Android多线程问题
安卓主线程跟新ui,主线程不能做耗时工作,有很多传统写法不再赘述,截止目前已经有了很多完善的现成方案,先学会使用。
一、更完善的现成方案
1. 使用协程(Coroutines)------ Kotlin 官方推荐方案
适用场景 :Kotlin 项目,现代 Android 开发首选
优势:
- 结构化并发 :协程天然支持线程切换,避免手动
Handler
或Executor
管理。 - **
Dispatchers
机制**:Kotlin 提供Dispatchers.Main
(UI 线程)、Dispatchers.IO
(IO 操作)、Dispatchers.Default
(CPU 密集型任务)等,自动管理线程切换。 - **
suspend
函数**:可以挂起协程,避免阻塞线程,适用于异步任务。
示例:
kotlin
// 在 ViewModel 或 Repository 中使用协程
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) { // 在 IO 线程执行
val data = fetchDataFromNetwork()
withContext(Dispatchers.Main) { // 切换回主线程更新 UI
updateUI(data)
}
}
}
}
对比动态代理:
- 更简洁:不需要手动写代理逻辑,协程自动管理线程切换。
- 更安全 :
suspend
函数天然支持挂起,避免回调地狱。 - 更灵活:可以精确控制每个任务的线程调度。
适用性:如果项目使用 Kotlin,协程是最佳选择。
2. 使用 RxJava / RxKotlin ------ 响应式编程方案
适用场景 :需要复杂异步数据流的项目
优势:
- **
subscribeOn
和observeOn
**:可以精确控制每个操作符的线程。 - 链式调用:适合处理复杂的数据流(如网络请求 + 数据处理 + UI 更新)。
- 错误处理 :提供
onErrorResumeNext
、retry
等机制。
示例:
scss
// 使用 RxJava 切换线程
Observable.fromCallable {
// 在 IO 线程执行
fetchDataFromNetwork()
}
.subscribeOn(Schedulers.io()) // 指定订阅在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定观察者在主线程
.subscribe { data ->
updateUI(data) // 在主线程更新 UI
}
对比动态代理:
- 更灵活:可以控制每个操作符的线程,而不仅仅是方法调用。
- 更强大:支持背压(Backpressure)、错误重试、数据转换等高级功能。
- 更复杂:学习曲线较陡,需要理解响应式编程概念。
适用性:适合需要复杂异步数据流的项目(如网络请求 + 数据处理 + UI 更新)。
3. 使用 LiveData
+ ViewModel
------ Android 官方架构组件
适用场景 :MVVM 架构的 Android 项目
优势:
- 自动线程切换 :
LiveData
默认在主线程更新 UI,开发者无需手动切换。 - 生命周期感知 :
ViewModel
和LiveData
会自动处理 Activity/Fragment 的生命周期。 - 与协程/RxJava 集成:可以结合协程或 RxJava 使用。
示例:
kotlin
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) {
val result = fetchDataFromNetwork()
_data.postValue(result) // 自动切换到主线程
}
}
}
// 在 Activity/Fragment 中观察
viewModel.data.observe(this) { data ->
updateUI(data) // 自动在主线程执行
}
对比动态代理:
- 更符合 Android 架构 :与
ViewModel
、LiveData
无缝集成。 - 更安全:避免手动线程切换错误。
- 更易维护:代码结构清晰,职责分离。
适用性:适合采用 MVVM 架构的 Android 项目。
二、我提出的改进方案
1. 结合注解 + AOP(面向切面编程)
思路:
- 使用 注解 标记需要特定线程执行的方法(如
@MainThread
、@IoThread
)。 - 使用 AOP 框架 (如 AspectJ 或 Kotlin 的
@JvmName
+ 反射)在编译期或运行期拦截方法调用,自动切换线程。
示例:
less
// 定义注解
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class MainThread
// 使用 AOP 拦截方法调用
@Aspect
class ThreadSwitchAspect {
@Around("@annotation(MainThread)")
fun switchToMainThread(joinPoint: ProceedingJoinPoint) {
Handler(Looper.getMainLooper()).post {
joinPoint.proceed() // 在主线程执行
}
}
}
// 使用方式
class MyService {
@MainThread
fun updateUI() {
// 自动在主线程执行
}
}
优势:
- 更灵活:可以精确控制每个方法的线程。
- 更可维护:不需要修改业务代码,只需添加注解。
- 更符合现代架构:与依赖注入(如 Hilt)结合使用效果更好。
缺点:
- 需要 AOP 支持:可能需要引入 AspectJ 或 Kotlin 编译时插件。
- 学习成本较高:需要理解 AOP 概念。
适用性:适合需要精细控制线程调用的项目。
2. 结合 CoroutineDispatcher
+ Dependency Injection
(DI)
思路:
- 使用 依赖注入框架 (如 Hilt 或 Koin)提供
CoroutineDispatcher
实例。 - 在业务代码中直接使用
viewModelScope
或lifecycleScope
,避免手动创建CoroutineScope
。
示例:
less
// 使用 Hilt 提供 CoroutineDispatcher
@Module
@InstallIn(SingletonComponent::class)
object DispatcherModule {
@Provides
@Named("ioDispatcher")
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
@Named("mainDispatcher")
fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
}
// 在 ViewModel 中使用
class MyViewModel @Inject constructor(
@Named("ioDispatcher") private val ioDispatcher: CoroutineDispatcher,
@Named("mainDispatcher") private val mainDispatcher: CoroutineDispatcher
) : ViewModel() {
fun fetchData() {
viewModelScope.launch(ioDispatcher) {
val data = fetchDataFromNetwork()
withContext(mainDispatcher) {
updateUI(data)
}
}
}
}
优势:
- 更符合 DI 最佳实践 :避免硬编码
CoroutineDispatcher
。 - 更易测试 :可以 Mock
CoroutineDispatcher
进行单元测试。 - 更灵活 :可以动态切换
Dispatcher
(如测试时使用Dispatchers.Unconfined
)。
适用性:适合使用依赖注入的项目。
三、总结:如何选择最佳方案?
方案 | 适用场景 | 优势 | 缺点 |
---|---|---|---|
协程 | Kotlin 项目 | 简洁、安全、灵活 | 需要 Kotlin |
RxJava | 复杂异步数据流 | 强大、灵活 | 学习曲线陡 |
LiveData + ViewModel | MVVM 架构 | 符合 Android 官方推荐 | 需要架构支持 |
注解 + AOP | 精细线程控制 | 灵活、可维护 | 需要 AOP 支持 |
DI + CoroutineDispatcher | 依赖注入项目 | 符合 DI 最佳实践 | 需要 DI 框架 |
推荐选择:
- Kotlin 项目 → 协程(最佳选择)
- Java/Kotlin 复杂异步任务 → RxJava
- MVVM 架构 → LiveData + ViewModel
- 需要精细线程控制 → 注解 + AOP
- 依赖注入项目 → DI + CoroutineDispatcher
协程 + DI + 架构组件 的组合通常是更完善、更推荐的方式。
协程 + 依赖注入(DI) + Android 架构组件 综合培训指南
本培训将带你掌握 Kotlin 协程(Coroutines) 、依赖注入(DI,如 Hilt/Koin) 和 Android 架构组件(ViewModel/LiveData) 的结合使用,构建 现代、高效、可维护的 Android 应用。
一、培训目标
- 理解协程的核心概念 (
CoroutineScope
、Dispatcher
、suspend
函数) - 掌握依赖注入(DI)的最佳实践(Hilt/Koin)
- 学会使用 Android 架构组件 (
ViewModel
、LiveData
、Lifecycle
) - 构建一个完整的示例项目,整合协程 + DI + 架构组件
二、培训内容
1. Kotlin 协程(Coroutines)基础
1.1 协程是什么?
- 协程 = 轻量级线程,比传统线程更高效,适合异步任务。
- **
suspend
函数**:可以挂起协程,避免阻塞线程。 - **
CoroutineScope
**:管理协程的生命周期(避免内存泄漏)。
1.2 核心概念
概念 | 说明 |
---|---|
launch |
启动一个协程(不返回结果) |
async |
启动一个协程(返回 Deferred ,可获取结果) |
suspend |
标记一个函数可以挂起 |
CoroutineDispatcher |
控制协程运行的线程(Dispatchers.Main 、Dispatchers.IO 、Dispatchers.Default ) |
withContext |
切换协程的线程 |
1.3 示例代码
kotlin
// 在 ViewModel 中使用协程
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) { // 在 IO 线程执行
val data = fetchDataFromNetwork() // 挂起函数
withContext(Dispatchers.Main) { // 切换回主线程
updateUI(data) // 更新 UI
}
}
}
private suspend fun fetchDataFromNetwork(): String {
delay(1000) // 模拟网络请求
return "Data from network"
}
private fun updateUI(data: String) {
// 更新 UI
}
}
关键点:
viewModelScope
:自动绑定ViewModel
的生命周期,避免内存泄漏。Dispatchers.IO
:用于网络/IO 操作。withContext(Dispatchers.Main)
:切换回主线程更新 UI。
2. 依赖注入(DI)------ Hilt
2.1 为什么需要 DI?
- 解耦 :避免手动创建对象(如
Repository
、ApiService
)。 - 可测试性 :可以轻松替换依赖(如 Mock
ApiService
进行单元测试)。 - 统一管理 :避免重复创建对象(如
Retrofit
实例)。
2.2 Hilt 基础
Hilt 是 Google 官方推荐的 DI 框架,基于 Dagger,但更简单。
步骤:
-
添加依赖 (
build.gradle
):arduinoimplementation "com.google.dagger:hilt-android:2.48" kapt "com.google.dagger:hilt-compiler:2.48"
-
在
Application
中初始化 Hilt:kotlin@HiltAndroidApp class MyApplication : Application()
-
定义依赖 (如
ApiService
):less@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun provideApiService(): ApiService { return Retrofit.Builder() .baseUrl("https://api.example.com/") .build() .create(ApiService::class.java) } }
-
注入依赖:
kotlin@AndroidEntryPoint class MyActivity : AppCompatActivity() { @Inject lateinit var apiService: ApiService // 自动注入 }
2.3 结合协程使用 DI
kotlin
@HiltViewModel
class MyViewModel @Inject constructor(
private val apiService: ApiService // 通过 DI 注入
) : ViewModel() {
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) {
val data = apiService.fetchData() // 使用注入的 ApiService
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
优势:
@HiltViewModel
:自动绑定ViewModel
的生命周期。@Inject
:自动注入依赖,避免手动创建ApiService
。
3. Android 架构组件(ViewModel + LiveData)
3.1 ViewModel
的作用
- 管理 UI 相关数据,避免因配置变更(如旋转屏幕)导致数据丢失。
- 与
LiveData
结合,实现数据驱动 UI。
3.2 LiveData
的作用
- 自动感知生命周期,避免内存泄漏。
- 默认在主线程更新 UI,开发者无需手动切换线程。
3.3 示例代码
kotlin
@HiltViewModel
class MyViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data // 暴露给 UI 层
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) {
val result = apiService.fetchData()
_data.postValue(result) // 自动切换到主线程
}
}
}
// 在 Activity/Fragment 中观察
class MyActivity : AppCompatActivity() {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(application as MyApplication).appComponent.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory).get(MyViewModel::class.java)
viewModel.data.observe(this) { data ->
// 自动在主线程执行
updateUI(data)
}
viewModel.fetchData() // 触发数据加载
}
}
关键点:
LiveData
自动在主线程更新 UI,无需手动切换线程。ViewModel
管理数据,避免因配置变更丢失状态。
三、完整项目示例
1. 项目结构
kotlin
app/
├── data/
│ ├── api/
│ │ └── ApiService.kt
│ └── repository/
│ └── MyRepository.kt
├── di/
│ └── NetworkModule.kt
├── ui/
│ └── MyViewModel.kt
└── MainActivity.kt
2. 关键代码
(1) ApiService
(网络请求)
kotlin
interface ApiService {
@GET("data")
suspend fun fetchData(): String
}
(2) MyRepository
(数据层)
kotlin
class MyRepository @Inject constructor(
private val apiService: ApiService
) {
suspend fun getData(): String {
return apiService.fetchData()
}
}
(3) MyViewModel
(业务逻辑)
kotlin
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: MyRepository
) : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.getData()
_data.postValue(result)
}
}
}
(4) MainActivity
(UI 层)
kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(application as MyApplication).appComponent.inject(this)
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
viewModel.data.observe(this) { data ->
// 更新 UI
}
viewModel.loadData()
}
}
四、总结
技术 | 作用 | 推荐实践 |
---|---|---|
协程 | 异步编程,避免回调地狱 | 使用 viewModelScope + Dispatchers |
DI(Hilt) | 解耦依赖,提高可测试性 | @HiltViewModel + @Inject |
ViewModel | 管理 UI 数据,避免配置变更问题 | 结合 LiveData 使用 |
LiveData | 数据驱动 UI,自动感知生命周期 | 在 Activity/Fragment 中观察 |
最佳实践:
- 协程 + DI + ViewModel/LiveData 是现代 Android 开发的黄金组合。
- 避免手动线程切换 ,让协程和
LiveData
自动管理线程。 - 使用 Hilt 管理依赖,提高代码可维护性和可测试性。
下一步:
- 尝试在项目中整合协程 + Hilt + ViewModel/LiveData。
- 学习更高级的协程用法(如
Flow
、Channel
)。 - 探索 Hilt 的模块化设计(如
@Module
、@InstallIn
)。
希望这个培训能帮助你掌握 协程 + DI + 架构组件 的最佳实践! 🚀