第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的模板代码。

对比项Dagger2Hilt
配置复杂度高(需手动定义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。

依赖注入与架构知识体系 Hilt / Dagger2 依赖注入容器 ViewModel 持有状态与业务逻辑 LiveData / StateFlow 可观察数据流 DataBinding XML数据绑定 Activity / Fragment Jetpack Compose 声明式UI 单向数据流:Hilt提供依赖 → ViewModel管理状态 → LiveData/DataBinding/Compose渲染UI

23.7 总结与个人心得

这一章内容不少,但核心就一句话:用Hilt管理依赖,用ViewModel管理状态,用LiveData或Compose观察变化。我在多个项目里实践过这套组合,最大的感受是——代码变得“好改”了。以前改一个需求,可能要动五六个文件,现在只需要改ViewModel里的逻辑,UI自动跟着变。

如果你刚开始接触,我建议你先从Hilt+ViewModel+LiveData+DataBinding入手,这是最稳妥的“官方推荐”组合。等熟悉了,再逐步迁移到Compose。Compose虽然香,但生态还在完善中,有些第三方库还没完全适配。

最后一个小技巧:在ViewModel里尽量用StateFlow代替LiveData,因为StateFlow支持协程,更容易与Compose配合。但如果你团队里有人不熟悉协程,LiveData仍然是安全的选择。

好了,这一章就到这里。记住,架构不是目的,目的是让代码更容易维护、更容易测试。工具只是手段,理解背后的思想才是关键。

公众号:蓝海资料掘金营,微信deep3321