ViewModel

ViewModel 负责根据生命周期来存储和管理 UI 相关(Activity 或 Fragment)的数据。当配置发生变化时,如:屏幕方向旋转,它可以保证数据不被销毁。

你还在为开发中频繁切换环境打包而烦恼吗?快来试试 Environment Switcher 吧!使用它可以在app运行时一键切换环境,而且还支持其他贴心小功能,有了它妈妈再也不用担心频繁环境切换了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

为什么要用 ViewModel

Android Framework 层管理 UI 控制器(Activity/Fragment)的生命周期,它可以决定销毁或者重新创建一个 UI 控制器,以响应某些用户操作或完全无法控制的设备事件。如果系统销毁或重新创建 UI 控制器,那么存储在其中的任何与 UI 相关的临时数据都将丢失。例如,应用程序可能在一个 Activity 中包含一个用户列表,当由于配置更改导致 Activity 重新创建时,新的 Activity 必须重新获取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法在 onCreate() 中恢复数据,但这种方法只适用于少量的、可以序列化和反序列化的数据,不适用于像用户列表或位图这样的大量数据。

另一个问题是 UI 控制器经常需要异步调用,这可能需要一些时间来返回结果。UI 控制器需要管理这些调用,并确保系统在销毁后清除它们,以避免潜在的内存泄漏。这种管理需要大量的维护,并且在配置改变导致对象重新创建的情况下,会导致资源的浪费,因为对象可能不得不重新做它已经做过的事情。

UI控制器(如 Activity/ Fragment)主要用于显示 UI 数据、响应用户操作或处理操作系统通信(如权限请求)。如果界面控制器也负责从数据库或网络加载数据,就会导致这个类变的臃肿。将过度的责任分配给 UI 控制器可能导致一个单独的类试图处理所有应用程序的工作,而不是将工作委托给其他类,以这种方式将过度的责任分配给UI控制器也会使测试变得更加困难。

从UI控制器逻辑中分离出视图数据所有权,使开发更容易、更高效。

实现一个ViewModel

ViewModel 对象会在配置发生变化时自动保留,所以他们持有的数据对于新创建的 Activity 或 Fragment 实例是立即可用的。例如,如果需要在应用程序中显示一个用户列表,确保将获取和保存用户列表的职责分配给一个 ViewModel,而不是一个 Activity 或 Fragment,通过下面的示例代码说明:

public class UserListViewModel extends ViewModel {
    private MutableLiveData> users;

    public LiveData> getUsers() {
        if (users == null) {
            users = new MutableLiveData<>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        new Thread(){
            @Override
            public void run() {
                super.run();
                List list = new ArrayList<>(20);
                for (int i = 0; i < 20; i++) {
                    SystemClock.sleep(1000);
                    User user = new User(String.valueOf(i));
                    list.add(user);
                }
                users.postValue(list);
            }
        }.start();
    }
}

然后可以从一个 Activity 中访问该列表,如下:

public class UserListActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ......
        UserListViewModel userListViewModel = ViewModelProviders.of(this).get(UserListViewModel.class);
        userListViewModel.getUsers().observe(this, new Observer>() {
            @Override
            public void onChanged(@Nullable List users) {
                adapter.setUsers(users);
            }
        });
    }
}

如果 Activity 被重新创建,它会接收到被第一个 Activity 创建的相同的 UserListViewModel 实例。当宿主 Activity 结束运行时,Framework 层会调用 ViewModel 对象的 onCleared() 方法从而清理资源。

注意:ViewModel 唯一的责任是管理 UI 数据。一个 ViewModel 绝不能引用视图,有生命周期的,或持有 Activity 的 context 引用的类。

ViewModel 是比 View 或 LifecycleOwner 更具体的实例。这样的设计也意味着可以更容易的测试 ViewModel ,因为它不引用 View 和具有生命周期的对象。ViewModel 对象可以包含 LifecycleObserver 对象(如 LiveData)。然而ViewModel 对象禁止观察具有生命周期的可观察者的变化(如 LiveData)。如果ViewModel 需要 Application context,例如创建一个系统的服务,因为 Application 是 Context 的子类,所以可以继承 AndroidViewModel 类,并创建一个构造函数接收构造函数中的 Application。

ViewModel 的生命周期

ViewModel 对象通过 ViewModelProvider 获取,并且作用域在一个具有生命周期的类上。ViewModel 在其具有生命周期的类存活时,会一直保留在内存中:在一个 Activity 中,会保存到 finish 之前;而在一个 Fragment,会保存到 detached 之前。

下图展示了一个 Activity 经过旋转然后到结束运行的各种生命周期状态。该插图还在 Activity 的生命周期旁边显示了 ViewModel 的生命周期。虽然这个图描述的是 Activity 的状态,但是 Fragment 是和它类似的。

ViewModel_第1张图片
viewmodel-lifecycle.png

从图中可以看出通常只需要在系统第一次调用 Activity 对象的 onCreate() 方法时获取一个 ViewModel。系统可能在一个 Activity 的运行过程中,会多次调用 onCreate() 方法(比如当设备屏幕旋转时)。我们第一次请求的 ViewModel 会一直存在,直到 Activity 结束并销毁。这意味着一个 ViewModel 将不会因为它的创建者的一个配置变化而被销毁(例如:屏幕旋转)。Activity 的新实例将与现有的 ViewModel 重新连接。

Fragment 之间共享数据

一个 Activity 中的两个或多个 Fragment 需要相互通信是很常见的。设想有两个 Fragment,其中有一个用来展示一个列表,用户可以从列表中选择一个条目,另一个 Fragment 显示所选项目的内容。这种情况绝不是微不足道的,因为这两个Fragment 都需要定义一些接口描述,而宿主 Activity 必须将两者结合起来。此外,这两个 Fragment 必须处理另一个 Fragment 尚未创建或可见时的场景。

现在,可以通过使用 ViewModel 对象处理这个难点。可以通过在这些 Fragment 的宿主 Activity 中共享一个 ViewModel,来处理这种通信,通过下面的示例代码说明:

public class SharedViewModel extends ViewModel {

    private final MutableLiveData selected = new MutableLiveData<>();

    private final MutableLiveData> liveData = new MutableLiveData<>();

    public void select(String string) {
        selected.setValue(string);
    }

    public LiveData getSelected() {
        return selected;
    }

    public LiveData> getLiveData() {
        List list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(new User(String.valueOf(i)));
        }
        liveData.setValue(list);
        select(list.get(0).getFirstName());
        return liveData;
    }
}

public class MasterFragment extends Fragment {

    private SharedViewModel model;
    private MyAdapter mAdapter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getLiveData().observe(this, new Observer>() {
            @Override
            public void onChanged(@Nullable List users) {
                mAdapter.setUsers(users);
            }
        });
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.master_fragment, container, false);
        RecyclerView recyclerView = root.findViewById(R.id.recycler);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        mAdapter = new MyAdapter();
        recyclerView.setAdapter(mAdapter);
        mAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(String string) {
                model.select(string);
            }
        });
        return root;
    }
}

public class DetailFragment extends Fragment {

    private TextView textView;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, new Observer() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.detail_fragment, container, false);
        textView = root.findViewById(R.id.text);
        return root;
    }
}

注意: 这两个 Fragment 使用 getActivity() 获取 ViewModelProvider。这样,两个 Fragment 得到宿主 Activity 中的同一个 SharedViewModel 实例。

这种方法的好处:

  • 这个 Activity 不需要做任何事情,也不需要知道任何关于这个通信的事情。
  • Fragment 之间不需要知道除了 SharedViewModel 之外的逻辑。如果其中一个 Fragment 消失了,不会影响另一个 Fragment 继续工作。
  • 每个 Fragment 都有自己的生命周期,不受另一个 Fragment 生命周期的影响。如果一个 Fragment 替换另一个 Fragment,UI 会继续工作,没有任何影响。

使用 ViewModel 替换数据加载器

像 CursorLoader 这样的加载类,经常被用来在一个应用程序的 UI 与数据库之间保持数据同步。现在可以使用 ViewModel 与其他几个类来取代这个加载类。使用 ViewModel 可以将 UI 控制器与数据加载操作分离,这意味着有更少的类之间的强引用。

使用加载器的一种通用方法是,一个应用程序可能使用 CursorLoader 观察数据库的内容。当数据库中的值发生变化时,加载器会自动触发重新加载数据并更新UI:

ViewModel_第2张图片
loader.png

使用 ViewModel、Room、LiveData 一起取代加载器。ViewModel 确保数据在设备配置变化时不被销毁。Room 会在数据库变化时通知 LiveData,反过来,Livedata 会在数据修改后更新UI。

ViewModel_第3张图片
viewmodel-replace-loader

这篇文章介绍了如何使用一个 ViewModel 与 LiveData 取代 AsyncTaskLoader。
https://medium.com/google-developers/lifecycle-aware-data-loading-with-android-architecture-components-f95484159de4

当数据变得更复杂时,可以选择单独的类来加载数据。ViewModel 的目的是封装UI 控制器的数据,保证数据在设备配置变化时不被销毁。

参考文章

https://developer.android.com/topic/libraries/architecture/viewmodel.html

你可能感兴趣的:(ViewModel)