Android Jetpack之ViewModel详解

ViewModel类设计用于,以存在生命周期的方式来存储和管理UI相关的数据。ViewModel允许数据保存于配置发生变化的情况,例如屏幕旋转。ViewModel优点在于:共享数据、保存临时数据、视图解耦、利于UI更新。

Android框架管理UI控制器的生命周期,例如activities、fragments。框架可能会销毁或者重新创建UI控制器,为了响应用户动作事件,或者设备事件已经不受控制了。如果系统销毁或者重建,所有与UI相关的临时数据将会丢失。对于简单少量数据,可以调用onSaveInstanceState方法来存储,然后从onCreate方法读取bundle来恢复数据。但是,这个方法仅适合可序列化的少量数据,并不适合大量数据,比如用户列表或者bitmap位图。

另外一个问题是,UI控制器经常发起异步调用,这样会带来一定耗时。UI控制器需要管理这些调用,并且确保在它们销毁后清理系统,从而避免内存泄漏。同时,UI控制器需要负责从网络或者数据库加载数据。总而言之,UI控制器工作很多,处理逻辑也变得复杂起来。

有更简单更高效的方法,从UI控制器逻辑分离出视图数据关系。那就是使用ViewModel,导入依赖:

implementation 'android.arch.lifecycle:extensions:1.1.1'

结构组件为UI控制器提供ViewModel帮助类,负责为UI准备数据。ViewModel在配置发生变化时会自动保存,所以它们携带的数据可以立刻用于下一个activity或者fragment。如果我们需要在应用程序显示用户列表,确保ViewModel可以获取并持有用户列表。我们可以自定义ViewModel:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        //异步操作来获取数据
    }
}

我们可以这样从activity中访问list列表:

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // 更新 UI
        });
    }
}

一、ViewModel的生命周期

当获取ViewModel实例时,ViewModel对象的生命周期传递给ViewModelProvider。ViewModel一直保留于内存中,直到生命周期作用域永久消失。下图阐述一个activity经历旋转和销毁后,各种生命周期状态变化,同时也展示了关联activity的ViewModel生命周期:

二、fragment之间共享数据

activity的两个甚至多个fragment之间相互通信,这是很常见的事情。可以想象,master-detail fragment的案例,master fragment负责展示列表,detail fragment负责展示选中item的详情。因为两个fragment之间需要数据交互,所以需要定义一些接口,处理逻辑变得复杂起来。

这个通用痛点可以使用ViewModel对象来解决。这些fragments可以共享一个ViewModel,使用activity来处理它们之间的通信,如下面代码所示:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // 更新UI
        });
    }
}

需要注意的是,所有fragments都需要检查activity是否包含它们。那样,当某个fragment获取ViewModelProvider,它们会收到作用于当前activity的ShareViewModel实例。这个方法提供如下方便之处:

1、activity不需要做任何事情;

2、fragments不需要了解ShareViewModel之间的联系;

3、每个fragment有自己的生命周期,不影响其他fragment;

三、使用ViewModel替换加载器

例如CursorLoader之类的加载器,经常用于应用程序UI在数据库异步查询数据。我们可以使用ViewModel,更少的类,来替换加载器。ViewModel可以把UI控制器从数据加载操作中分离出来,这意味着我们在类与类之间使用更少的强引用。下图展示的是普通加载器加载过程:

ViewModel与Room、LiveData协同工作,可以替换Loader。当数据库数据发生变化时,Room会通知LiveData,接着,LiveData会更新UI数据,工作过程如下图所示:

发布了63 篇原创文章 · 获赞 179 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/85550678