Jetpack架构组件(上):ViewModel与LiveData、DataBinding与ViewBinding、Lifecycle感知组件、Navigation组件

Jetpack这套东西,说实话刚出来的时候我还有点抵触。心想Google又搞新花样,学不动了。但真正在项目里用起来之后,嗯,真香。它解决了很多我们以前手写代码时反复踩的坑。今天咱们就把上半部分讲清楚,下半部分留到下一节。

核心思想:Jetpack不是让你写更少的代码,而是让你写更不容易出错的代码。它帮你把生命周期、数据绑定、页面导航这些脏活累活都干了。

1. ViewModel:让数据活过屏幕旋转

先问个问题:你的App在旋转屏幕时,数据丢了没?

Activity重建是Android的老大难。以前我用onSaveInstanceState存数据,又小又慢。后来用ViewModel,彻底解放了。

ViewModel的生命周期跟Activity/Fragment不一样。它会在Activity重建后依然存活。说白了,它活在Activity的“灵魂”里,而不是“肉体”里。

// 一个简单的ViewModel
class MyViewModel : ViewModel() {
    private val _count = MutableLiveData<Int>(0)
    val count: LiveData<Int> get() = _count

    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }
}

// 在Activity中使用
class MainActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel.count.observe(this) { value ->
            findViewById<TextView>(R.id.tvCount).text = value.toString()
        }

        findViewById<Button>(R.id.btnAdd).setOnClickListener {
            viewModel.increment()
        }
    }
}

我的习惯:ViewModel里不要持有Context引用,否则内存泄漏等着你。如果非要Context,用AndroidViewModel,它内部持有Application的引用。

2. LiveData:观察者模式的优雅实现

LiveData是个被观察的数据容器。它最大的特点是:感知生命周期。什么意思?就是Activity在前台时它才通知更新,后台时自动停止。你想想看,这省了多少手动取消注册的代码。

我以前在项目里遇到过一个问题:网络请求回来时Activity已经销毁了,结果更新UI直接崩溃。用了LiveData之后,再也没出过这种问题。

// 转换LiveData
val userIdLiveData: LiveData<String> = ...
val userNameLiveData: LiveData<String> = Transformations.map(userIdLiveData) { id ->
    fetchUserNameFromDatabase(id)
}

// 合并多个LiveData
val combinedLiveData = MediatorLiveData<String>().apply {
    addSource(source1) { value = it }
    addSource(source2) { value = it }
}

避坑指南:我曾经在Fragment里用observeForever,结果Fragment销毁了观察者还在,导致内存泄漏。记住:observeForever必须手动调用removeObserver。

3. DataBinding与ViewBinding:告别findViewById

这两个东西经常被搞混。我简单说:

  • ViewBinding:轻量级,只生成绑定类,没有表达式支持。适合大多数项目。
  • DataBinding:重量级,支持布局表达式、双向绑定。适合复杂UI场景。

我个人更推荐ViewBinding。为什么?因为它编译快,而且不会因为布局表达式写错导致编译失败。DataBinding的表达式调试起来太痛苦了。

// 启用ViewBinding(build.gradle)
android {
    buildFeatures {
        viewBinding = true
    }
}

// 使用ViewBinding
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.tvTitle.text = "Hello ViewBinding"
        binding.btnSubmit.setOnClickListener { ... }
    }
}

小技巧:如果某个布局文件不想生成绑定类,在根布局加个tools:viewBindingIgnore="true"就行。我经常在include布局里这么干。

4. Lifecycle感知组件:让代码自己知道什么时候该干活

Lifecycle组件是Jetpack的基石。ViewModel、LiveData都依赖它。它的核心就是两个东西:LifecycleOwner和LifecycleObserver。

你想想看,以前我们写定位、写传感器,都得在onStart里开启、onStop里关闭。一旦忘了,要么浪费电,要么崩溃。Lifecycle组件就是帮你自动做这件事。

// 自定义LifecycleObserver
class MyLocationObserver(private val context: Context) : DefaultLifecycleObserver {
    private lateinit var locationManager: LocationManager

    override fun onStart(owner: LifecycleOwner) {
        locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 0f, callback)
    }

    override fun onStop(owner: LifecycleOwner) {
        locationManager.removeUpdates(callback)
    }
}

// 在Activity中绑定
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(MyLocationObserver(this))
    }
}

核心价值:Lifecycle让你把生命周期逻辑从Activity/Fragment中抽离出来,放到独立的类里。代码更干净,测试更方便。

5. Navigation组件:单Activity架构的基石

Navigation组件是Google推荐的页面导航方案。它支持单Activity多Fragment架构,还自带Safe Args类型安全传参。

我记得第一次用Navigation时,最大的感受是:终于不用手写FragmentTransaction了。那个add、replace、addToBackStack的写法,我写了五年,真的写吐了。

<!-- nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.HomeFragment"
        android:label="首页">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.DetailFragment"
        android:label="详情">
        <argument
            android:name="itemId"
            app:argType="integer" />
    </fragment>
</navigation>
// 在Fragment中导航
findNavController().navigate(
    HomeFragmentDirections.actionHomeToDetail(itemId = 42)
)

避坑指南:我曾经在Fragment的onCreateView里调用findNavController,结果报错。因为此时Fragment还没跟NavController关联。正确的时机是在onViewCreated里调用。

知识体系总览

下面这张图把今天讲的五个组件串起来了。你看它们之间的关系:

Jetpack架构组件(上)知识体系 Lifecycle 感知组件 ViewModel 数据存活于重建 LiveData 生命周期感知观察 ViewBinding 轻量级视图绑定 DataBinding 表达式+双向绑定 Navigation 单Activity导航 所有组件都依赖 Lifecycle 进行生命周期感知,确保安全与性能

总结一下

今天讲的这五个组件,说白了就是Google帮你把Android开发中最容易出问题的几个点给规范了:

  • ViewModel:解决数据存活问题
  • LiveData:解决观察者生命周期问题
  • ViewBinding/DataBinding:解决视图绑定问题
  • Lifecycle:解决组件生命周期耦合问题
  • Navigation:解决页面导航混乱问题

这些组件单独用已经很强了,组合起来更是威力无穷。下一节我们会讲剩下的几个组件,包括Room、WorkManager、Paging等。到时候你会发现,Jetpack全家桶用起来是真的顺手。


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