第23章 依赖注入与架构:Dagger2/Hilt、MVVM、LiveData、DataBinding、Jetpack Compose
各位同学,今天我们来聊聊Android开发中真正“上强度”的东西——依赖注入和现代架构。说实话,我刚入行那会儿,项目里全是Activity写几千行代码,一个点击事件能牵扯出七八个对象,改个需求跟拆炸弹似的。后来接触了Dagger2和MVVM,才明白什么叫“代码写得舒心”。这一章,我会把Dagger2/Hilt、MVVM、LiveData、ViewModel、DataBinding和Jetpack Compose串起来讲,让你知道它们各自解决什么问题,又是怎么配合的。
23.1 依赖注入:从“手动造轮子”到“自动装配”
依赖注入,说白了就是“你需要什么,别人给你送过来”。不用自己在类里new对象。为什么这么做?因为解耦。你想想看,如果每个类都自己new依赖,那改一个实现类,所有地方都得改。我在项目中遇到过最惨的一次,就是因为网络库升级,全局搜“OkHttp”替换了三十多个文件……从那以后,我坚决用依赖注入。
23.1.1 为什么不用手动创建?
- 耦合度高:类与具体实现绑定,难以替换(比如测试时换Mock)。
- 生命周期管理混乱:Activity重建时,依赖对象可能被重复创建。
- 单例难以控制:自己写单例容易出线程安全问题。
核心思想:控制反转(IoC)。不是你主动去拿依赖,而是框架把依赖注入给你。
23.1.2 Dagger2 vs Hilt:我选哪个?
Dagger2是前辈,功能强大但配置繁琐。Hilt是Google基于Dagger2封装的“亲儿子”,专门为Android优化。我个人习惯:新项目直接用Hilt,老项目如果已经用了Dagger2,也没必要强行迁移。Hilt最大的好处是——你不用写那么多Component和Module的模板代码。
| 对比项 | Dagger2 | Hilt |
|---|---|---|
| 配置复杂度 | 高(需手动定义Component、Module、Scope) | 低(注解驱动,自动生成) |
| Android集成 | 需手动处理Activity/Fragment生命周期 | 内置@AndroidEntryPoint,自动管理 |
| 学习曲线 | 陡峭 | 平缓 |
| 编译速度 | 较慢(大量APT) | 相对快(优化了增量编译) |
我的建议:如果你团队里有人用过Dagger2,可以继续用;如果是新手团队,直接上Hilt,省下的时间够你喝好几杯咖啡。
23.2 MVVM架构模式:让代码各司其职
MVVM不是新东西,但Google官方推了之后,就成了Android开发的“标准答案”。它把界面、逻辑、数据拆成三层:View(Activity/Fragment)、ViewModel(状态与逻辑)、Model(数据源)。
为什么要这么分?我举个例子:以前我在一个Activity里同时处理网络请求、UI更新、数据库写入,结果Activity重建时,网络回调还在,但UI已经没了,直接空指针。用了MVVM之后,ViewModel持有数据,Activity只管观察,重建后自动恢复状态——舒服多了。
23.2.1 核心角色
- View:只负责渲染UI和分发事件。不处理业务逻辑。
- ViewModel:持有LiveData或StateFlow,管理UI状态。不持有View引用(避免内存泄漏)。
- Model:仓库层(Repository),封装数据来源(网络、数据库、缓存)。
注意:ViewModel里不要放Context!如果你需要Application上下文,用AndroidViewModel。我曾经见过有人把Activity传给ViewModel,结果屏幕旋转后Activity被销毁,ViewModel还持有旧引用——内存泄漏妥妥的。
23.3 LiveData与ViewModel:最佳拍档
LiveData是一个可观察的数据持有者,它感知生命周期。ViewModel配合LiveData,堪称“自动挡”数据管理。你不需要手动取消订阅,LiveData会在Activity销毁时自动清理。
23.3.1 基本用法
// ViewModel中定义LiveData
class MyViewModel : ViewModel() {
private val _userName = MutableLiveData<String>()
val userName: LiveData<String> get() = _userName
fun loadUser() {
// 模拟网络请求
_userName.value = "张三"
}
}
// Activity中观察
class MainActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.userName.observe(this) { name ->
textView.text = name
}
}
}
这里有个细节:observe(this) 传的是LifecycleOwner。LiveData只会在Activity处于STARTED或RESUMED状态时更新UI。如果Activity在后台,数据变化不会触发UI更新——避免了空指针和资源浪费。
避坑指南:我曾经在Fragment里用observe(viewLifecycleOwner)而不是observe(this)。为什么?因为Fragment的this可能和View的生命周期不同步。用viewLifecycleOwner才是安全的。
23.4 DataBinding数据绑定:告别findViewById
DataBinding让你在XML里直接绑定数据和事件。配合LiveData,可以实现“数据一变,UI自动变”。我个人觉得,DataBinding最大的价值是减少了样板代码——不用写一堆setText、setOnClickListener。
23.4.1 启用与基本绑定
// build.gradle (模块级)
android {
...
buildFeatures {
dataBinding true
}
}
// activity_main.xml
<layout>
<data>
<variable name="viewModel" type="com.example.MyViewModel"/>
</data>
<LinearLayout ...>
<TextView
android:text="@{viewModel.userName}" />
<Button
android:onClick="@{() -> viewModel.loadUser()}" />
</LinearLayout>
</layout>
// Activity中绑定
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewModel = viewModel
binding.lifecycleOwner = this // 让LiveData自动更新
注意:一定要设置binding.lifecycleOwner = this,否则LiveData变化不会自动反映到UI上。我刚开始就忘了这行,折腾了半天……
23.5 Jetpack Compose入门:声明式UI的未来
Compose是Google推出的声明式UI框架,用Kotlin写UI,不用XML。它和MVVM、LiveData配合得天衣无缝。你想想看,以前改一个UI状态,得先findViewById,再setText,再setVisibility……在Compose里,你只需要改变状态,UI自动重组。
23.5.1 一个简单的Compose页面
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val userName by viewModel.userName.observeAsState()
Button(onClick = { viewModel.loadUser() }) {
Text("点击加载用户")
}
Text("当前用户: $userName")
}
看到observeAsState()了吗?它把LiveData转成Compose的State,当LiveData变化时,Compose自动重组UI。不需要手动设置LifecycleOwner,Compose自己会处理。
核心变化:从“命令式”(告诉UI怎么做)到“声明式”(告诉UI是什么状态)。Compose只关心状态,不关心如何更新。
23.6 知识体系总览
下面这张SVG图,把本章的核心知识点和它们之间的关系画出来了。你可以看到:Hilt负责提供依赖,ViewModel管理状态,LiveData/StateFlow传递数据,DataBinding或Compose负责渲染。整个架构是单向数据流——数据从Model流向ViewModel,再流向View。
23.7 总结与个人心得
这一章内容不少,但核心就一句话:用Hilt管理依赖,用ViewModel管理状态,用LiveData或Compose观察变化。我在多个项目里实践过这套组合,最大的感受是——代码变得“好改”了。以前改一个需求,可能要动五六个文件,现在只需要改ViewModel里的逻辑,UI自动跟着变。
如果你刚开始接触,我建议你先从Hilt+ViewModel+LiveData+DataBinding入手,这是最稳妥的“官方推荐”组合。等熟悉了,再逐步迁移到Compose。Compose虽然香,但生态还在完善中,有些第三方库还没完全适配。
最后一个小技巧:在ViewModel里尽量用StateFlow代替LiveData,因为StateFlow支持协程,更容易与Compose配合。但如果你团队里有人不熟悉协程,LiveData仍然是安全的选择。
好了,这一章就到这里。记住,架构不是目的,目的是让代码更容易维护、更容易测试。工具只是手段,理解背后的思想才是关键。