一、Lifecycle

什么是 Lifecycle?

LifecycleJetpack 架构组件中的核心类,用于统一管理 Android 组件(Activity、Fragment 等)的生命周期,并通过观察者模式向外部暴露生命周期的变化。

传统上,我们需要在 onCreateonStartonDestroy 等方法里手动写逻辑,很容易产生内存泄漏或重复代码。而 Lifecycle 的引入,让我们可以只关心“生命周期状态的变化”,而不用和具体回调方法绑定。

Lifecycle 本质上就是一个抽象类,提供了注册和移除观察者的能力:

public abstract class Lifecycle {
    // 添加观察者
    public abstract fun addObserver(observer: LifecycleObserver)
    
    // 删除观察者
    public abstract fun removeObserver(observer: LifecycleObserver)
}

ActivityFragment 等组件则实现了 LifecycleOwner 接口,要求提供一个 lifecycle 属性:

public interface LifecycleOwner {
    /**
     * 返回该组件的 Lifecycle
     */
    public val lifecycle: Lifecycle
}

这样一来,我们就能在任意地方对生命周期进行观察,而不必耦合到 Activity/Fragment 的具体方法中。

lifecycleScope

有了生命周期的监听机制,Google 官方进一步提供了协程作用域lifecycleScope。它是 LifecycleOwner 的扩展属性,可以直接在 ActivityFragment 中使用:

lifecycleScope.launch {
    // 在生命周期结束时自动取消
}

为什么会自动取消?因为在 LifecycleOwner.kt 中,Google 用 原子引用 + CAS 的方式,确保每个生命周期只持有一个唯一的 LifecycleCoroutineScope

// LifecycleOwner.kt
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

// Lifecycle.kt
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (internalScopeRef.compareAndSet(null, newScope)) {
                newScope.register() // 将协程作用域注册到 Lifecycle
                return newScope
            }
        }
    }

LifecycleCoroutineScopeImpllifecycleScope 的实际实现。它本身就是一个 LifecycleEventObserver,会随着生命周期的变化自动管理协程:

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    
    init {
        // 初始化时如果 Lifecycle 已经销毁,则立即取消协程
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            coroutineContext.cancel()
        }
    }
    
    fun register() {
        launch(Dispatchers.Main.immediate) {
            if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
                lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
            } else {
                coroutineContext.cancel()
            }
        }
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
            lifecycle.removeObserver(this)   // 移除观察者
            coroutineContext.cancel()        // 取消协程
        }
    }
}

总结:lifecycleScope 作用域会注册为观察者,当状态变为 DESTROYED 时自动取消协程,避免内存泄漏,开发者不再需要手动 cancel() 协程。


二、ViewModel

什么是 ViewModel

ViewModel 属于Android Jetpack 库的一部分,是一种业务逻辑或屏幕状态容器。它主要具有以下两种优势:

  • 数据持久保存:ViewModel 的生命周期通常比 Activity/Fragment 更长。在界面因为屏幕旋转等原因被销毁并重建时,ViewModel 不会随之销毁,因此非常适合保存 UI 状态数据,无需在页面重建时手动恢复。

  • 避免内存泄漏:ViewModel 基于 Lifecycle 机制管理,当宿主(Activity/Fragment)销毁后,ViewModel 会自动清理,避免了常见的内存泄漏问题。

ViewModel 的使用

使用 ViewModel 的方式很简单:只需继承 ViewModel 类即可。

import androidx.lifecycle.ViewModel

class CounterViewModel : ViewModel() {
    var count = 0 // ViewModel 变量(Activity 重建后不会丢失)

    fun increment() {
        count++
    }
}

在 Activity/Fragment 中并不是直接 new,而是通过 ViewModelProvider 获取实例:

class MainActivity : AppCompatActivity() {
    
    // 获取 ViewModel 实例
    private val viewModel: CounterViewModel by lazy {
        ViewModelProvider(this)[CounterViewModel::class.java]
    }

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

        val textView: TextView = findViewById(R.id.tv_count)
        val button: Button = findViewById(R.id.btn_increment)

        // 显示当前计数
        textView.text = viewModel.count.toString()

        // 按钮自增
        button.setOnClickListener {
            viewModel.increment()
            textView.text = viewModel.count.toString()
        }
    }
}

即使旋转屏幕,count 的值依旧会被保留,不会被重置。

通过 KTX 获取 ViewModel

Android KTX 扩展库提供了更简洁的用法,在 build.gradle 中引入:

implementation("androidx.activity:activity-ktx:1.11.0")

然后可以直接使用 by viewModels() 获取 ViewModel 实例,这样写更简洁,也更符合 Kotlin 的风格。

private val viewModel: CounterViewModel by viewModels()

ViewModel 的生命周期

ViewModel 的生命周期取决于它所在的作用域(ViewModelStoreOwner),通常是 Activity 或 Fragment。
它会一直存活,直到作用域销毁时才会回收。

在回收前,会触发 onCleared() 方法,适合做一些资源释放的操作。

viewModelScope

viewModelScopeViewModel 提供的一个扩展属性,代表一个与 ViewModel 生命周期绑定的协程作用域。

它的好处是:当 ViewModel 被销毁时,协程会自动取消,避免协程泄漏。借助 viewModelScope,我们可以在 ViewModel 中安全地执行异步任务(如网络请求、数据库操作),同时保证资源得到正确回收。

viewModelScope.launch {
    // ViewModel onCleared 时自动取消协程
}

三、LiveData

LiveData 搭配 ViewModel 是 MVVM 模式的最佳实践。

Flow

ViewBinding

DataBinding

什么是 DataBinding?

DataBinding 是谷歌官方发布的一个实现数据绑定的框架(实现数据与视图的双向绑定),DataBinding 可以帮助我们在 Android 中更好的实现 MVVM 模式。

DataBinding 的使用步骤

1. 启用 DataBinding

build.gradle.kts 中,配置启用 DataBinding:

android {
    // ...    
    dataBinding { 
        enable = true
    }
}

2. 修改布局文件为 DataBinding 布局

在 Android Studio 中,打开布局文件,选中最外层的布局标签,按下 ALT + 回车 键,选择 Convert to data binding layout,就可以自动转化为 DataBinding 布局。

3. 数据绑定

转换为 DataBinding 布局后,可以在布局文件中的 <data> 标签内定义变量 <variable>,设置好变量名和类型。然后就可以在布局中通过 "@{}" 的形式直接使用变量。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="account"
            type="top.tonydon.interview.mvvm.bean.Account" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".databinding.DemoActivity">

        <TextView
            android:id="@+id/tv_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{account.name + '|' + account.level}" />

        <Button
            android:layout_marginTop="50dp"
            android:id="@+id/btn_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="账号等级 +1" />
    </LinearLayout>
</layout>

在 Activity 中需要使用 DataBindingUtil 进行 setContentView(),它返回一个 ActivityDemoBinding 示例,通过它可以直接拿到布局中的 View、对变量赋值会自动更新 UI。

class DemoActivity : AppCompatActivity() {

    val account = Account("Tony", 100)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 通过 DataBindingUtil 设置 ContentView,返回一个 ActivityDemoBinding 实例(系统自动生成)
        val binding: ActivityDemoBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_demo)

        // 直接为 account 属性赋值
        binding.account = account

        // 告别繁琐的 findViewById,可以直接根据 id 获取 View 实例
        binding.btnAdd.setOnClickListener {
            account.level += 1
            binding.account = account   // 赋值 account,UI 会自动更新
        }
    }
}

WorkManager