A
第 27 章JAVA35 分钟

ViewModel + LiveData + Lifecycle

深入 ViewModel 作用域与生命周期、LiveData 概念与转换、MediatorLiveData 与 LifecycleOwner 观察机制,并以数据加载场景实战演示。

学习目标

  • 理解 ViewModel 的作用域与生命周期绑定规则
  • 掌握 LiveData 与 LifecycleOwner 的观察机制
  • 能够使用 Transformations.map / switchMap 与 MediatorLiveData 组合数据
  • 用 ViewModel + LiveData 完成一个数据加载示例

学习目标

  • 理解 ViewModel 的作用域与生命周期绑定规则;
  • 掌握 LiveData 与 LifecycleOwner 的观察机制;
  • 能够使用 Transformations.map / switchMap 与 MediatorLiveData 组合数据;
  • 用 ViewModel + LiveData 完成一个数据加载示例。

ViewModel + LiveData + Lifecycle

依赖配置

dependencies {
    implementation "androidx.appcompat:appcompat:1.7.0"
    implementation "androidx.core:core-ktx:1.13.1"
    // Lifecycle / ViewModel / LiveData
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.8.4"
    implementation "androidx.lifecycle:lifecycle-livedata:2.8.4"
    implementation "androidx.lifecycle:lifecycle-runtime:2.8.4"
    implementation "androidx.activity:activity:1.9.1"
    // Java 8+
    coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
}

ViewModel 作用域与生命周期

ViewModel 的设计目标

ViewModel 的核心目标是 在配置变更(如旋转屏幕)时保留 UI 数据,避免重新加载与状态丢失。它的生命周期并不绑定 Activity 的 onDestroy,而是绑定一个 ViewModelStoreOwner(Activity / Fragment / Navigation Graph):

  • 当 Activity 因配置变更销毁时,旧 Activity 的 ViewModelStore 会被保留并转移到新 Activity;
  • 当 Activity 真正销毁(用户按返回键或系统回收)时,ViewModelStore 才被清空,调用 ViewModel.onCleared()。
Activity 创建 ──> ViewModelStore 创建 ──> ViewModel 实例化
     │
     │ 配置变更(旋转)
     ↓
旧 Activity.onDestroy() ─> ViewModelStore 保留
     ↓
新 Activity.onCreate() ─> 复用同一 ViewModel
     │
     │ 用户真正退出
     ↓
Activity.onDestroy() ─> ViewModelStore.clear() ─> ViewModel.onCleared()

ViewModelStoreOwner 与获取 ViewModel

Activity、Fragment 都实现了 ViewModelStoreOwner,通过 ViewModelProvider 获取 ViewModel:

public class ProfileViewModel extends ViewModel {
    private final MutableLiveData<User> user = new MutableLiveData<>();
    public LiveData<User> getUser() { return user; }

    public void load(long id) {
        // 模拟异步加载
        user.setValue(new User(id, "User-" + id));
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        // 释放资源:取消网络请求、关闭数据库连接等
    }
}
public class ProfileActivity extends AppCompatActivity {
    private ProfileViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);

        // 同一个 owner 在配置变更前后拿到同一个 ViewModel 实例
        viewModel = new ViewModelProvider(this).get(ProfileViewModel.class);
        viewModel.getUser().observe(this, user -> {
            ((TextView) findViewById(R.id.tv_name)).setText(user.name);
        });

        if (savedInstanceState == null) {
            viewModel.load(1L); // 仅首次创建时加载
        }
    }
}

不要向 ViewModel 传递 View 引用

ViewModel 的生命周期可能比 Activity 长(配置变更场景),因此绝不能把 Activity / Fragment / View 作为构造参数传入 ViewModel,否则会持有已销毁的 Activity 引用,造成内存泄漏。

LiveData 概念

LiveData<T> 是一种 可观察的数据持有者,与普通观察者模式不同的是:

  • 生命周期感知:观察者必须传入 LifecycleOwner(Activity / Fragment),LiveData 只在 Owner 处于 STARTED 之后(STARTED / RESUMED)才回调 onChanged;
  • 自动取消订阅:Owner 销毁时自动断开观察,避免内存泄漏;
  • 粘性数据:新观察者订阅时会立即收到最新一次数据,便于配置变更后立即恢复 UI;
  • 主线程安全:setValue 必须在主线程调用,postValue 可在任意线程异步切回主线程。
public class TimerViewModel extends ViewModel() {
    private final MutableLiveData<Integer> seconds = new MutableLiveData<>(0);

    public LiveData<Integer> getSeconds() { return seconds; }

    public void tick() {
        Integer cur = seconds.getValue();
        seconds.setValue(cur == null ? 1 : cur + 1);
    }

    // 后台线程更新
    public void tickFromBackground() {
        new Thread(() -> {
            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
            seconds.postValue(seconds.getValue() == null ? 1 : seconds.getValue() + 1);
        }).start();
    }
}

LifecycleOwner 与观察者机制

LiveData.observe(LifecycleOwner, Observer) 内部会注册一个 LifecycleEventObserver,在 Owner 状态变为 DESTROYED 时自动移除观察者。你也可以直接使用 observeForever(Observer),但必须手动 removeObserver,否则会泄漏:

public class TimerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_timer);

        TimerViewModel vm = new ViewModelProvider(this).get(TimerViewModel.class);
        vm.getSeconds().observe(this, sec -> {
            ((TextView) findViewById(R.id.tv_timer)).setText(String.valueOf(sec));
        });

        findViewById(R.id.btn_tick).setOnClickListener(v -> vm.tick());
    }
}

LiveData 转换

Transformations.map

Transformations.map(source, mapFunction) 会在 source 变化时把值映射成新类型,返回新的 LiveData:

public class OrderViewModel extends ViewModel {
    private final MutableLiveData<Double> price = new MutableLiveData<>(0.0);
    public LiveData<Double> getPrice() { return price; }

    // 把价格映射为格式化字符串
    public LiveData<String> getFormatted() {
        return Transformations.map(price, p -> "¥" + String.format("%.2f", p));
    }

    public void setPrice(double p) { price.setValue(p); }
}

Transformations.switchMap

switchMap 用于根据源值切换到不同的下游 LiveData(典型场景:根据 ID 查询数据库 / 网络,返回另一个 LiveData):

public class UserRepo {
    public LiveData<User> loadUser(long id) {
        MutableLiveData<User> live = new MutableLiveData<>();
        new Thread(() -> {
            try { Thread.sleep(300); } catch (InterruptedException ignored) {}
            live.postValue(new User(id, "User-" + id));
        }).start();
        return live;
    }
}

public class UserViewModel extends ViewModel {
    private final MutableLiveData<Long> userId = new MutableLiveData<>();
    private final UserRepo repo = new UserRepo();

    public void setUserId(long id) { userId.setValue(id); }

    // 当 userId 变化时,切换到对应的 loadUser LiveData
    public LiveData<User> getUser() {
        return Transformations.switchMap(userId, repo::loadUser);
    }
}

switchMap 会自动取消上一个内层 LiveData 的订阅,避免并发请求交叉污染。

MediatorLiveData

MediatorLiveData<T> 用于 同时观察多个源 LiveData,把它们的变更汇总到一个出口。典型用法:合并网络数据 + 数据库数据、合并多个请求状态。

public class DashboardViewModel extends ViewModel {
    private final MediatorLiveData<String> summary = new MediatorLiveData<>();

    private final MutableLiveData<String> networkData = new MutableLiveData<>();
    private final MutableLiveData<String> dbData = new MutableLiveData<>();

    public DashboardViewModel() {
        summary.addSource(networkData, value -> summary.setValue("网络:" + value));
        summary.addSource(dbData, value -> summary.setValue("数据库:" + value));
    }

    public LiveData<String> getSummary() { return summary; }
    public void setNetworkData(String v) { networkData.setValue(v); }
    public void setDbData(String v) { dbData.setValue(v); }
}

只有当某个源发生实际数据变更时才会触发 MediatorLiveData 的 setValue,调用方订阅 summary 即可。

ViewModel + LiveData 实战:数据加载

下面整合上面所有概念,做一个完整的“加载用户列表”示例,涵盖加载状态、错误处理、配置变更保留:

public class UserListState {
    public final boolean loading;
    public final List<User> data;
    public final String error;
    public UserListState(boolean loading, List<User> data, String error) {
        this.loading = loading;
        this.data = data;
        this.error = error;
    }
    public static UserListState loading() {
        return new UserListState(true, Collections.emptyList(), null);
    }
    public static UserListState success(List<User> data) {
        return new UserListState(false, data, null);
    }
    public static UserListState error(String message) {
        return new UserListState(false, Collections.emptyList(), message);
    }
}
public class UserListViewModel extends ViewModel {
    private final MediatorLiveData<UserListState> state = new MediatorLiveData<>();
    private final UserRepo repo = new UserRepo();

    public LiveData<UserListState> getState() { return state; }

    public void refresh() {
        LiveData<List<User>> source = repo.fetchUsers();
        state.setValue(UserListState.loading());
        // 用 MediatorLiveData 把 repo 的结果映射到统一 state
        state.addSource(source, list -> {
            state.setValue(UserListState.success(list));
            state.removeSource(source); // 一次性,避免重复
        });
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        // 实际项目应取消网络请求 / RxJava Disposable
    }
}
public class UserListActivity extends AppCompatActivity {
    private UserListViewModel viewModel;
    private ProgressBar loadingPb;
    private TextView errorTv;
    private RecyclerView recyclerView;
    private UserAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);
        loadingPb = findViewById(R.id.pb_loading);
        errorTv = findViewById(R.id.tv_error);
        recyclerView = findViewById(R.id.rv_users);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new UserAdapter();
        recyclerView.setAdapter(adapter);

        viewModel = new ViewModelProvider(this).get(UserListViewModel.class);
        viewModel.getState().observe(this, s -> {
            loadingPb.setVisibility(s.loading ? View.VISIBLE : View.GONE);
            errorTv.setVisibility(s.error != null ? View.VISIBLE : View.GONE);
            if (s.error != null) {
                errorTv.setText(s.error);
            }
            if (s.data != null && !s.data.isEmpty()) {
                adapter.submit(s.data);
            }
        });

        if (savedInstanceState == null) {
            viewModel.refresh();
        }

        findViewById(R.id.btn_refresh).setOnClickListener(v -> viewModel.refresh());
    }
}

旋转屏幕后,UserListState 会被 ViewModel 保留,Activity 重新订阅时立即拿到上一次结果,无需重新发起网络请求。

常见坑与最佳实践

  1. 在 ViewModel 中持有 View / Activity 引用:会泄漏已销毁的 Activity。所有 UI 更新都通过 LiveData 抛出,由 View 订阅。
  2. 暴露 MutableLiveData 给外部:对外只暴露 LiveData<T>,避免外部随意 setValue 导致状态不可控。
  3. 在子线程调用 setValue:setValue 必须在主线程,子线程应使用 postValue,否则抛 IllegalStateException。
  4. switchMap 中返回新 LiveData 重复订阅:switchMap 内部自动管理订阅切换,不要在外部再手动 addSource,否则会泄漏。
  5. 粘性数据导致重复事件:粘性机制会在新观察者订阅时立即回放最后一次数据,注意区分“重复触发”和“恢复 UI”。
  6. 在 onCleared 中启动异步清理:onCleared 在主线程同步执行,长任务应提前在 ViewModel 内部管理(如 CompositeDisposable)。
  7. 用 observeForever 忘记 remove:尽量用 observe(LifecycleOwner, ...);非要 observeForever 时必须在合适的生命周期点 removeObserver。

章节小结

本章覆盖了 ViewModel 的作用域与生命周期绑定规则、LiveData 的生命周期感知观察机制、Transformations.map / switchMap 转换、MediatorLiveData 多源合并,并用一个数据加载场景串联起所有概念。核心要点是:ViewModel 不持有 View 引用、LiveData 只暴露不可变接口、配置变更时数据由 ViewModel 自动保留。

下一章预告

下一章将进入依赖注入领域,使用 Dagger 2 完整实战:核心注解 @Inject / @Provides / @Module / @Component、@Singleton 与自定义 Scope、@Qualifier 限定符、Component 依赖与 Subcomponent、AndroidInjector(dagger.android)以及测试中替换 Module 的技巧。