Android 系统架构组件--ViewModel

可能有人觉得我写的 和官网不太一样 我这几篇关于架构组件的博客 是自己在学习的过程中的随笔 也可能有很多错误 只能说作为参考  后面我再使用过程中 会不断纠正自己之前的理解  

简介:

上一篇 关于LiveData 的学习 相信都差不多能看懂 我们再使用liveData的过程中 将其放在ViewModel中进行存储管理了 那么究竟什么是ViewModel  他有什么用处 好处呢 ? 我们根据文档 大概了解下 

官方:The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

大概意思就是 viewModel 是存储和管理UI数据的 ,所谓存储 简单来说就是UI销毁(横竖屏切换) 不会导致数据的丢失 
有人说onSaveInstanceState 也可以呢~ 你说的没错的呢~  onSaveInstanceState确实有用但是仅能保证少量可序列化的数据 如果是大量数据(比如 列表 bitmap等)的话 对不起 它做不到  
更重要的是 如果我们再界面中有异步调用 比如耗时的网络请求 我们UI再销毁重建的过程中 极有可能会造成内存泄漏  我们需要浪费大量资源去维护这种情况 针对这种情况 推出了ViewModel

ViewModel能够将数据控制 和UI控制分离。也就是当我们UIInactive的时候能够帮助我们存储数据 当active的时候能够提供给我们需要的数据  是不是很棒~  那我们继续 

实现ViewModel

情景: 我们需要从网络中获取用户列表(耗时操作) 然后对获取到的用户列表再activity中进行展示 

对于该场景 常用做法不说  这里 我们希望在ViewModel中完成数据的获取和存储 activity只负责展示即可 ok  let's try

我们之前已经写过一个模拟股票波动的 ViewModel的用法 我们这里再写个 获取用户列表 的 

public class CustomViewModel extends ViewModel{
    private UserManagerLiveData userLiveData;

    public LiveData<List<User>> getUserLiveData() {
        if (userLiveData == null) {
            userLiveData = new UserManagerLiveData();
        }
        return userLiveData;

    }
}


public class User {
    String name;
    String sex;

    public User(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}
public class UserManagerLiveData extends LiveData<List<User>> {

    private UserRequest userRequest;
    private UserRequestListener userRequestListener = userList -> postValue(userList);

    public UserManagerLiveData() {
        if (userRequest == null) {
            userRequest = new UserRequest();
        }
    }

    @Override
    protected void onActive() {
        super.onActive();
        userRequest.requestUserList(userRequestListener);
    }

    @Override
    protected void onInactive() {
        super.onInactive();
    }
}

interface UserRequestListener {
    void onSuccess(List<User> userList);
}
public class UserRequest {
    public void requestUserList(UserRequestListener userRequestListener) {
        List<User> userList = new ArrayList<>();
        for (int i=0;i<10;i++){
            User man = new User("name" + i, "man");
            userList.add(man);
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
//                模拟股市波动 1股每1s 1块钱 高兴
                for (int i = 0; i < 50; i++) {
                    SystemClock.sleep(1000);
                    if (userRequestListener != null) {
                        userRequestListener.onSuccess(userList);
                    }
                }
            }
        }).start();
    }
}
private void addViewModelLiveData() {
    CustomViewModel customViewModel = ViewModelProviders.of(this).get(CustomViewModel.class);
    customViewModel.getUserLiveData().observe(this, userList -> {
        for (User user : userList) {
            Log.d(TAG, user.toString());
        }
    });
}

直接上运行结果 不解释啦~

03-28 20:29:22.096 13534-13534/com.example.lifeapplication D/lifeCycle: onPause
03-28 20:29:22.159 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name0', sex='man'}
03-28 20:29:22.159 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name1', sex='man'}
03-28 20:29:22.159 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name2', sex='man'}
03-28 20:29:22.159 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name3', sex='man'}
03-28 20:29:22.160 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name4', sex='man'}
03-28 20:29:22.160 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name5', sex='man'}
03-28 20:29:22.160 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name6', sex='man'}
03-28 20:29:22.160 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name7', sex='man'}
03-28 20:29:22.160 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name8', sex='man'}
03-28 20:29:22.160 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name9', sex='man'}
03-28 20:29:22.531 13534-13534/com.example.lifeapplication D/lifeCycle: onStop
03-28 20:29:29.353 13534-13534/com.example.lifeapplication D/lifeCycle: onStart
03-28 20:29:29.354 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name0', sex='man'}
03-28 20:29:29.354 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name1', sex='man'}
03-28 20:29:29.354 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name2', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name3', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name4', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name5', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name6', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name7', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name8', sex='man'}
03-28 20:29:29.355 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name9', sex='man'}
03-28 20:29:29.356 13534-13534/com.example.lifeapplication D/lifeCycle: onResume
03-28 20:29:30.128 13534-13534/com.example.lifeapplication D/lifeCycle: User{name='name0', sex='man'}

我们可以看出ViewModel其实是独立于生命周期持有者所存在的 这样我们可以更容易的写测试用例覆盖ViewModel中的操作

viewModel可以持有liveCycleObservers 比如liveData 具体如何持有 可以看前面两篇文章

如果ViewModel想使用Application的引用 比如去获取系统服务 那么此时可以继承AndroidViewModel 该类包含了一个构造函数能够拿到Application的引用

接下来我们看下ViewModel 的生命周期

官方:ViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting the ViewModel. The ViewModel remains in memory until the Lifecycle it's scoped to goes away permanently: in the case of an activity, when it finishes, while in the case of a fragment, when it's detached.

官方的意思大概是这样:ViewModel对象的生命周期是 通过ViewModelProvider获取的时候 被限定的,这个ViewModel对象一直被存储在内存中 知道activity finish 或者是fragment被detached的时候 被分离 。分离之后作用域永久消失

什么鬼  ??? 举个栗子  之前我们再activity中 通过ViewModelProviders 获取了CustomViewModel对象 那么此时改Viewmodel对象的生命周期已经获得了 和我们activity进行了捆绑 当我们activity finish的时候 ViweModel 才会被分离 差不多 我们继续 

我们看下 下面这个图片  这个图片表述了一个activity 经过了一次屏幕旋转之后 ViewModel对应情况 这样就可以验证我们上面说的了  activity oncreate 方法在旋转屏幕的时候被重复调用  但是finish 并没有被调用 所以ViewModel对象一直存在于内存中 换句话说 我们所持有的ViewModel对象只是我们第一次创建的对象 所以这样你就明白为什么 旋转屏幕 我们ViweModel仍然能持有数据了吧


好了 我们可以继续了  根据上面得出的结论 我们看下 ViewModel再两个Fragment之间传递数据 

应用场景 :

PS:关于Fragment之间传递数据 是特别常见的 比如我们常用的搜索界面(包含搜索历史) 和搜索结果页  

官方是这样说的  : 加入你有一个Fragment(用户选择一个item) 还有一个Fragment显示这个选择  的item 我们通常的做法是定义两个接口 然后再activity中绑定两个接口 

这样常见的需求 处理起来如此麻烦 ViewModel完全可以帮助我们简化这样的痛点

实现原理 就是我们上面讲解的生命周期 只要activity不finish  那么改ViewModel对象将一直存在于内存中  那么这样我们完全可以把数据交给它来处理  我这里就不在多写了  把官方例子直接摆出 有问题 再沟通吧

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 ->
           // Update the UI.
        });
    }
}

我们 发现 再获取ViewModel对象的时候 我们使用的是getActivity 所以每个Fragment所得到的都是捆绑在Activity上的ViewModel的单例  所以

 1.此时我们可以进行Fragment之间的通信了  而且这个通信 我们Activity不需要参与 

  2.Fragment之间互不影响 也就是Fragment finish不会影响其他Fragment的运行

 是不是方便了一些~ ok 继续 

使用ViewModel取代Loader

情景:我们经常会用CustomLoader再UI进程中 实现DataSource的异步刷新  那么根据上面我们介绍的 我们可以使用ViewModel将数据和界面相分离 实现类之间的解耦 

一些常见的loader的工作方式 如图所示

 


不懂英语的应该没有吧  翻译下 
大概流程 就是首先初始化/restart Loader-->LoadManager 发送命令到指定的Loader-> Loader异步获取当前数据 load complete->LoaderManager 通过callback 回调 通知UI界面刷新 -> UI界面刷新 
 知道Loader的工作原理 我们来看下 ViewModel是如何来替代的 

ViewModel replace Loader工作原理 


详细梳理下 ViewModel实现的逻辑  

如图所示ViewModel依赖于Room 和LiveData工作的  关于LiveData工作 大家看过之前文章的应该很熟悉  Room是什么鬼 ? 我们下篇文章会具体讲解 大概知道有这么一只鬼  之前我们已经说过很多次了 ViewModel捆绑生命周期持有者之后 能够确保数据能够在横竖屏切换 等等 设备配置改变的时候能让存活 从而保证数据能够存活 (只有当生命周期持有者finish 时才会分离  ) 又复习了一遍。Room的作用是通知LiveData数据发生变化  ,然后LiveData通过setValue/postValue 或者其他方式 通知UI变化  

关于 替代的好处 官方翻译过来是这样说的

随着您的数据变得越来越复杂,您可能会选择一个单独的类来加载数据。ViewModel的目的是封装UI控制器的数据,以便在配置更改时让数据存活 

还是为了 解耦和数据保活 好了  大概先到这 我们去了解下Room~


猜你喜欢

转载自blog.csdn.net/youth_never_go_away/article/details/79700899