MENU

【译】MVVM 架构,ViewModel 和 LiveData(一)

November 23, 2020 • 开发

在 Google I/O 上,Google 推出了 Architecture Components,其中包含 LiveData 以及 ViewModel 来帮助使用 MVVM 模式来开发 Android 应用。

MVVM 简介

如果你之前有了解过 MVVM 的话完全可以跳过这一章节。

MVVM 是增强关注点分离原则(SoC)的架构模式之一。它可以将 UI 逻辑与业务(或者后端)逻辑分离开来。其主要目的在于「使得 UI 代码保持简单且无应用逻辑,以便于管理」,就像其他 MVC 模式一样。

MVVM 主要由以下三层组成:

  1. Model
    Model 层代表着数据以及应用业务逻辑。推荐的实现策略之一是通过 observable 暴露数据,以使其与 ViewModel 或 observer、consumer 完全解耦(后面会在 MVVM 示例应用中进行说明)。
  2. ViewModel
    ViewModel 层负责与 Model 层交互,同时为 View 层提供一个或多个可被观察的 observable。ViewModel 层可以额外为视图提供 Hook 方法将事件传递到 Model 层。ViewModel 层最重要的一个策略就是与 View 层解耦。举例来讲,ViewModel 不关心实际进行交互的视图。
  3. View
    最后,View 层用来观察(或订阅)一个 ViewModel 的 observable 获取数据以便相应地更新 UI 元素。

下图展示了 MVVM 组件以及基本交互。

LiveData

和上面说的一样,LiveData 是新引入的 Architecture Component 之一,LiveData 是可观察的数据持有者。这样应用中的组件就可以观察 LiveData 对象的修改而无需在它们之间创建显式和严格的依赖路径。从而使 LiveData 的生产者与消费者完全解耦。

除此之外,引入 LiveData 还有很多好处,LiveData 遵循应用组件(Activity,Fragment,Service)的生命周期状态,以此来管理对象的生命周期,确保 LiveData 不会导致泄漏。

根据 Google 文档所述,如果你已经在使用 Rx 或 Agera 之类的库,则可以继续使用它们而非 LiveData。但是在这种情况下,你需要按照 Android 组件的生命周期处理对象分配和回收。

由于 LiveData 遵循 Android 生命周期,因此这意味着除非 LiveData 的持有者(Activity 或 Fragment)处于活动状态(例如已触发 onStart() 且尚未触发 onStop() 时),否则它将不会触发其观察回调。除此之外,当持有者触发 onDestroy() 时,LiveData 还将自动移除 oberver。

LiveData 将在下面的 MVVM 示例应用中进行说明。

ViewModel

ViewModel 也是一个 Architecture Component 新进成员。Architecture components 提供了一个名为 ViewModel 的类来负责为 UI 及视图准备数据。

ViewModel 为你的 MVVM 的 ViewModel 层提供了一个很好的基类。通过继承 ViewModel(或其子类 AndroidViewModel)便可自动在配置变更时保存所持有的数据。这意味着在配置变更后,ViewModel 持有的数据可立即作用于接下来的 Activity 或 Fragment 实例。

下图展示了 ViewModel 的生命周期。

ViewModel 也将在下面的 MVVM 示例应用中进行说明。

示例应用

现在让我们开始最有意思的部分,让我们在一个实例应用内将它们融会贯通。实例应用主要包括两个页面,第一个页面展示一列 Google 的 GitHub 项目,它含有一些简短的信息,例如标题,编程语言,以及 watcher 数。

当应用的使用者点击任意列表项时,打开一个项目详情页面来展示该项目描述,编程语言,watcher 数,开放的 iusse,创建、更新信息以及克隆地址等信息。

示例应用交互图

下图显示了示例应用的 package 结构

下图展示了在搜索 Google GitHub 项目的应用场景下的示例交互图。

如图所示,每一层都通过观测后一层(Fragment(View)-> ViewModel -> Repository)的 LiveData,最终一旦获取到项目列表,就会将其绑定到 RecyclerView 的 adapter 并展示数据。

Repository 模块负责处理数据操作。明确这一点,Repository 模块需要为应用程序的其余部分提供干净的 API,并简化消费者 ViewModel 的工作。Repository 模块应该知道从何处获取数据以及在数据更新后要调用什么 API。可以将它们视为不同数据源( REST 服务,数据库,XML 文件等)之间的中介。

现在,让我们以检索 GitHub 项目场景为例,从下至上得解释这些层级。ModelViewModel 开始,最后到 View

示例应用 Model 层

让我们从业务逻辑层开始,我们有两个 model:

  1. Project,包含 GitHub 项目基本信息,如 id,名称,描述,创建时间等。
  2. User,包含 GitHub 项目所有者的用户信息。

为了使用 GitHub RESTful API,这里使用 Retrofit2 在 repository 包下创建接口:

interface GitHubService {
    String HTTPS_API_GITHUB_URL = "https://api.github.com/";

    @GET("users/{user}/repos")
    Call<List<Project>> getProjectList(@Path("user") String user);

    @GET("/repos/{user}/{reponame}")
    Call<Project> getProjectDetails(@Path("user") String user, @Path("reponame") String projectName);
}

为了方便 ViewModel 的工作,我们创建了一个 ProjectRepository 类来与 GitHub 服务交互,最终它会为 ViewModel 提供一个 LiveData 对象。以后该类还将用于安排服务的调用,下面代码展示了 getProjectList() 的 API 实现。

public class ProjectRepository {
    private GitHubService gitHubService;

    //…

    public LiveData<List<Project>> getProjectList(String userId) {
        final MutableLiveData<List<Project>> data = new MutableLiveData<>();

        gitHubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
            @Override
            public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
                data.setValue(response.body());
            }

            // Error handling will be explained in the next article …
        });

        return data;
    }
    
    // …
}

ProjectRepository 是 ViewModel 的数据提供者,其 getProjectList() 方法将响应简单包装成一个 LiveData 对象。

出于简化文章的目的,这里省略了异常处理,它将在下一篇文章中进行说明。

示例应用 ViewModel 层

为了使用 getProjectList() API,我们创建了一个 ViewModel 类(该类将调用 Repository API,并将结果适当转换)。下面代码展示了 ProjectListViewModel 类的实现。

public class ProjectListViewModel extends AndroidViewModel {
    private final LiveData<List<Project>> projectListObservable;

    public ProjectListViewModel(Application application) {
        super(application);

        // If any transformation is needed, this can be simply done by Transformations class ...
        projectListObservable = ProjectRepository.getInstance().getProjectList("Google");
    }

    /**
     * Expose the LiveData Projects query so the UI can observe it.
     */
    public LiveData<List<Project>> getProjectListObservable() {
        return projectListObservable;
    }
}

如上所示,我们的 ProjectListViewModel 类继承自 AndroidViewModel,在构造方法里,它调用了 getProjectList(“Google”) 方法来查询 Google 在 GitHub 上的项目。

在实际情况下,通常需要在给观测的视图传递数据前对数据进行转换。因此你可以像下面这篇文章一样使用 Transformation 类简化工作。

示例应用 View 层

最后让我们简单看下本应用的 View 层,我们主要有一个 叫做 MainActivity 的 Activity 来处理代表应用两个不同页面的 Fragment 间的导航。

  1. ProjectListFragment,用来展示 Google 的 GitHub 项目列表。
  2. ProjectFragment,用来展示选中的 GitHub 项目的详情。

由于 Activity 和 Fragment 被视为 Lifecycle 的所有者,Activity 需要继承 LifecycleActivity 而 Fragment 需要继承 LifecycleFragment。然而,请务必牢记,在将 Lifecycle 与 support 库集成前,LifecycleActivityLifecycleFragment 类都是临时实现:详情看这里

现在,我们继续查看 ProjectListFragment,以下代码显示了最重要的集成部分。

public class ProjectListFragment extends LifecycleFragment {
    private ProjectAdapter projectAdapter;
    
    //…

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        final ProjectListViewModel viewModel =
                ViewModelProviders.of(this).get(ProjectListViewModel.class);

        observeViewModel(viewModel);
    }

    private void observeViewModel(ProjectListViewModel viewModel) {
        // Update the list when the data changes
        viewModel.getProjectListObservable().observe(this, new Observer<List<Project>>() {
            @Override
            public void onChanged(@Nullable List<Project> projects) {
                if (projects != null) {
                    //…
                    projectAdapter.setProjectList(projects);
                }
            }
        });
    }

    //…
}

如上所示,ProjectListFragment 获取 ProjectListViewModel,然后监听其 getProjectListObservable() 方法,以便在准备好时获取 Github 项目的列表。最后,一旦获取到项目列表,则将其传递到 projectAdapter(RecyclerView 的适配器),以便在 RecyclerView 组件中显示项目列表。

这是该项目一个端到端方案的解释,您可以在 GitHub 上找到完整的项目

MVVM 实现的重要指导原则

一些实现 MVVM 的重要指导原则:

  1. 如示例所示,ViewModel 不会也不得直接引用 View,因为这样做会导致 ViewModel 的生命周期超过 View 的生命周期,并且可能发生内存泄漏。
  2. 建议 Model 和 ViewModel 使用 LiveData 对外暴露其数据,因为 LiveData 遵循应用程序组件(Activity,Fragment,Service)的生命周期状态,以此来管理对象的生命周期,这可以确保 LiveData 对象不会导致泄漏问题。

下一篇文章

文章还没有结束,因为我们还有一些问题需要解决,例如:

  1. 依赖注入
  2. 异常处理
  3. 缓存
  4. 在 Demo 中添加更多功能,以了解 Room 如何提升 SQLite Data 操作。
  5. 单元测试
  6. 其它

MVVM 的下一系列文章将对此进行说明,请继续关注。

原文链接