Android Architecture Components(3) - ViewModel

上一篇文章中咱们介绍了Architecture Components中的LifeCycle,LifeCycleOwner及LifeCycleObserver,不知道你们掌握的怎么样?在学习编码的路上,仍是要多多实践才能够呢。 接下来咱们要介绍的是ViewModel。android


ViewModel简介数据库

ViewModel是用来存储和管理生命周期过程敏感的界面数据的一个类,用ViewModel存储的数据能够在应用设置项发生改变时保存下来例如当屏幕旋转。缓存

Android框架层管理UI控件的生命周期好比Activity和Fragment。框架层须要决定在面对用户交互时什么时候销毁或者重建UI控件,这一过程不是由开发者控制的。bash

若是系统销毁活着重建UI控件,那么用户的输入数据活着你已缓存的UI数据都会丢失,例如,在你的应用中有一个Activity展现着一个用户列表,当屏幕发生旋转时,Activity被重建,那么新建立的Activity须要再去请求一次用户列表数据。对于一些简单的数据,咱们可使用onSavedInstanceState()方法存储在Bundle中,而后在onCreate()函数中恢复,可是这种状况只适用于少许而且能够被序列化的数据,并不适用于其余数据,例如说一个用户列表或者不少图片。网络

另外一个问题是UI控件须要频繁的发起异步请求并等待返回结果。UI控件须要去管理这些异步请求并保证在它完成后被系统清理掉以免内存泄漏。这种管理须要大量的耐心和细心,而且在这些对象由于设置改变而重建的情形下,形成了一种资源浪费。app

Activity和Fragment这种UI空间只是去展现UI数据,响应用户交互或者处理系统交互,例如说请求权限,UI控件同时也须要负责从数据库或者网络上加载数据,咱们应该进行责任分摊,不要为UI控件添加过多的操做,这样会致使一个类去处理一个应用所要处理的工做,让咱们的测试工做变得更加艰难。框架

综合以上几点,Google推出了ViewModel,用于帮助UI控件准备数据,ViewModel会在设置发生变化时自动存储数据,他们所持有的数据能够马上被新建的Fragment或者Activity复用。异步

##ViewModel的使用 假设咱们须要在一个 应用页面内展现一个用户列表,那么咱们能够将数据请求操做托管给ViewModel,代码以下:async

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }
    private void loadUsers() {
        // Do an asyncronous operation to fetch users.
    }
}
复制代码

实现一个ViewModel只须要继承自ViewModel便可,随后咱们能够在Activity中访问数据,方式以下:ide

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}
复制代码

若是这个Activity被重建,那么他会接收到上一个Activity所建立的MyViewModel对象,当持有该ViewModel对象的Activity销毁时,系统会调用ViewModel.onCleared()方法释放资源。

注意ViewModel对象毫不能持有View,LifeCycle或者任何持有Activity 引用的对象

在设计理念上,ViewModel的生命周期独立于View或者LifeCycleOwner,这种设计也意味着你能够更简单的为ViewModel编写测试用例以覆盖ViewModel中的操做。ViewModel能够持有LifeCycleObservers好比说LiveData,若是ViewModel须要使用Application的引用,例如说去获取一个系统服务,此时能够继承自AndroidViewModel,该类有一个构造函数,能够接受Application的引用。

这里我再举一个简单的ViewModel的例子,以便你们更好的理解ViewModel。这个例子是界面上有一个Button和一个TextView,TextView用于记录Button 的点击次数,在屏幕旋转时保持TextView上的次数不变。

首先编写布局文件,代码以下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.code.archicomponentssmaples.MainActivity">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="Click Me"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
复制代码

这里我试用了约束布局,不懂得同窗能够自行百度,或者使用LinearLayout/RelativeLayout本身实现便可。

编写ViewModel类,用于持有Button的点击次数,以下:

public class ClickCounterViewModal extends ViewModel {
    private int count = 0;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
复制代码

随后在Activity中使用该ViewModel缓存Button点击次数,代码以下:

public class LifeOwnerActivity extends AppCompatActivity {

  private TextView mTextView;
  private Button mButton;
  private ClickCounterViewModal mClickCounterViewModal;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_life_owner);
    mClickCounterViewModal = ViewModelProviders.of(this).get(ClickCounterViewModal.class);
    initView();
    initData();
  }

  private void initView(){
    mTextView = (TextView)findViewById(R.id.owner_textView);
    mButton = findViewById(R.id.owner_Button);
  }

  private void initData(){
    displayClickCount();

  mButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      mClickCounterViewModal.setCount(mClickCounterViewModal.getCount()+1);
        displayClickCount();
      }
    });
  }

  private void displayClickCount(){
    mTextView.setText(mClickCounterViewModal.getCount()+"");
  }
}
复制代码

如上述代码ViewModel对象须要使用ViewModelProviders.of(Context)进行初始化。

另外多说一点ViewModel的提出只是为了处理UI相关数据的缓存问题,并不表明它能彻底替代onSavedInstanceState()的做用。

在这个例子中ViewModel的工做流程以下图:

这里写图片描述

##ViewModel生命周期

ViewModel对象的生命周期依赖于初始化时ViewModelProvider传入的LifeCycle对象,ViewModel对象会常驻在内存中直到与其对应的LifeCycle被销毁,对于Activity而言,就是当其被finish时,对于Fragment而言就是当它被detach的时候。 下图说明了一个Activity在发生屏幕旋转时自身的生命周期变化以及与其对应的ViewModel的生命周期。

这里写图片描述

一般状况下,在System调起Activity时,咱们在Activity的onCreate()函数内初始化ViewModel对象,随后系统可能屡次调用该Activity的onCreate()函数,例如说发生屡次屏幕旋转。这种清醒下ViewModel来源于第一次onCreate(),直到调用Activity.finish()时,该ViewModel对象才会被销毁并释放资源。

##使用ViewModel在Fragment间共享数据

在咱们平常变成生活中,Activity须要与其内部的一个或多个Fragment交互信息的需求比比皆是,假设又这样一种情形,咱们有一个Activity页面,左右各一个Fragment,左侧展现列表,右侧展现列表中某一项的详情。这种情形下Fragment中须要定义接口,持有Fragment的Activity须要同时绑定这两个Fragment,而且一个Fragment要处理另外一个Fragment没有建立或显示的问题。

这种痛点能够经过ViewModel解决,这两个Fragment能够公用一个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 ->
           // Update the UI.
        });
    }
}
复制代码

如上所示,两个Fragment只须要监听ViewModel中值的变化更新UI便可。 须要注意的是这两个Fragment在ViewModelProvider内传入了getActivity(), 此时该ViewModel对象的生命周期彻底依赖于持有两个Fragment的Activity。

这种模式有以下几点好处:

  • Activity不须要关注交互中的任何事;
  • Fragments彼此不须要知道对方的状态,一个Fragment消失了,另外一个仍然能够正常工做;
  • 每一个Fragment有其独立的生命周期,不会彼此影响,若是一个Fragment被另外一个Fragment替换了,UI仍然能正常显示;

##使用ViewModel代替Loaders

相似于CursorLoader的加载类常常被频繁的用于异步维护界面和数据库的数据一致,如今你也能够用ViewModel和一些其余的辅助类来实现这种功能了,使用ViewModel可使咱们的界面控制与数据加载解耦。 一种常见的使用CursorLoader监听数据库变化的结构图以下,当一个数据库值发生改变时,加载器会自动触发一个从新加载事件,完成后更新UI。

这里写图片描述
当ViewModel与Room以及LiveData结合使用时,就能够彻底替代加载器的功能,ViewModel保证数据在设置发生改变的过程当中不清空,当数据发生改变时,Room通知LiveData,随后使用新的数据更新UI。
这里写图片描述
当数据变得愈来愈复杂时,你或许会使用一个单独的类去加载数据,ViewModel的目的就在于保证数据在生命周期变化过程当中不被清空,关于LiveData,Room相关的更多详细内容,咱们会在下一篇推文中详细介绍,感谢你们阅读。更多详细内容欢迎你们关注个人公众号 [图片上传失败...(image-feea9f-1512483891026)]
相关文章
相关标签/搜索