天天看点

Android Jetpack -- ViewModel篇

  • Android Jetpack架构组件
    • Android Jetpack – ViewModel篇
    • Android Jetpack – LiveDate篇
    • Android Jetpack – DataBinding篇
    • Android Jetpack – Navigation篇
    • Android Jetpack – Lifecycles篇

ViewModel是一个负责为Fragment/Activity配置和管理数据的类,同时处理Fragment/Activity与Application其余部分的通信。

ViewModel始终与Scope(Fragment/Activity)相关联创建,同时一直保留当Scope存在时,例如Activity被finish。换句话说,ViewModel不会随着其拥有者因配置更改被销毁而销毁(旋转,横竖屏切换),新的拥有者会重新建立与ViewModel的关联。

ViewModel旨在获取和保存Activity或Fragment必要的信息,Activity或Fragment能够观察到ViewModel的数据改变,ViewModel通常通过LiveData或Android Data Binding暴露数据,也可以在你喜欢的框架中使用可观察构造。

ViewModel只负责管理UI数据,不应访问视图结构和持有Activity或Fragment的引用。

ViewModel的出现主要为了解决两个问题:

1.当Actvitiy销毁重建过程中的数据恢复问题,虽然原来可以使用onSaveInstanceState()来完成,但是只支持能被序列化的数据而且是小量数据,对于大量数据则显得有点无力。

2.UI控制器的工作繁忙,UI控制器主要用于处理显示,交互,其他的额外操作可以委托给其他类完成,将不应该分配给UI的任务分离出来是必要的,这也就是上面所说的

分离关注点

原则

下面则是示意图

Android Jetpack -- ViewModel篇

ViewModel实例

ViewModel在配置更改期间能自动保留其对象,以便它们所持有的数据可立即用于下一个

Activity

或片段

Fragment

下面通过一个具体的实例:

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;

    private TextView tv;
    private Button button1, button2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);

        tv = findViewById(R.id.textView);
        tv.setText(String.valueOf(myViewModel.num));
        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.num++;
                tv.setText(String.valueOf(myViewModel.num));
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.num += 2;
                tv.setText(String.valueOf(myViewModel.num));
            }
        });
    }
}
           

MyViewModel.java

public class MyViewModel extends ViewModel {

    public int num = 0;

}
           

布局如下:Welcome对应TextView,+1对象button1,+2对应button2

Android Jetpack -- ViewModel篇

加到7,当设备旋转

Android Jetpack -- ViewModel篇
Android Jetpack -- ViewModel篇

数据仍能得到保留,即使更改系统语言都可以,也就是说如果Activity被重建,将接收到第一个Activity创建的相同的MyViewModel实例。当所有者Activity完成后,会调用ViewModel对象的onCleared()方法,以便它能够清理资源。

同时这里也需要注意一个问题,当ViewModel持有外部引用的时候会阻止回收,所以

ViewModel绝不能引用View、Lifecycle(也是Jetpack组件之一)或任何可能包含对Activity上下文的引用的类。

回到最上面的那个图,图说明了

ViewModel

的作用域涉及到整个生命周期,当获取ViewModel时,ViewModel的生命周期限定为传入ViewModelProvider的对象的生命周期。也就是对于以下场景(引用官方示例)

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.
        });
    }
}
           

由于传入的是

Activity

,所以其作用域为整个

Activity

,不同的

Fragment

可以通过

ViewModelProviders

获取到同一个

ViewModel

,这样有以下的好处:

  • Activity无须参与Fragment之间的交互。Activity与Fragment无关
  • Fragment之间也无需互相建立联系,Fragment与Fragment无关
  • 每个Fragment都有自己的生命周期,即使被替换也不会有任何影响。

加强ViewModel,支持异常生命周期

我们知道,有些时候在Activity被意外杀死,如清理后台等会直接跳过onDestory()而是回调onSaveInstanceState()异常杀死下的生命周期,这个时候ViewModel也会被杀死,再次恢复的时候便会被重建,这样,原来的数据也就丢失了,因此我们需要改进一下ViewModel以支持异常退出情况下的重建。

首先很容易是想到通过

onSaveInstanceState()

来保存,然后通过

SaveInstanceState

来恢复,虽然也是一种可行的方法,但是好像全程跟ViewModel没什么关联,未免优点没有物尽其用,所以ViewModel也提供了类似SavedInstanceState的方法。

SavedStateHandle :用于保存状态的数据类型,是一个key-value的map,其实也就是类似Bundle。采用Hashmap来实现的。
           

具体使用:

public class ViewModelWithData extends ViewModel {

    private MutableLiveData<Integer> number;
    private SavedStateHandle handle;

    private static final String KEY_NUMBER = "number";

    public ViewModelWithData(SavedStateHandle handle) {
        this.handle = handle;
        number = new MutableLiveData<>();
        number.setValue(0);
    }

    public MutableLiveData<Integer> getNumber() {//每次都通过SavedStateHandle来获取相应的值
        if (!handle.contains(KEY_NUMBER)) {
            handle.set(KEY_NUMBER, 0);
        }
        return handle.getLiveData(KEY_NUMBER);
    }

    public void addNumber(int n) {
        getNumber().setValue(getNumber().getValue() + n);
    }
}
           
public class LiveDataActivity extends AppCompatActivity {

    private ViewModelWithData viewModelWithData;

    ActivityLiveDataBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_live_data);
		//需要将创建的SavedStateVMFactory传入
        viewModelWithData = ViewModelProviders.of(this, new SavedStateVMFactory(this)).get(ViewModelWithData.class);

        binding.setData(viewModelWithData);
        binding.setLifecycleOwner(this);
    }

}
           

好了,这样就完成了将VIewModel的生命周期与整个Activity的同步,包括异常情况下。

支持SharedPreference等使用到Application的相关

因为

SharedPreference

需要使用到

Application

来获取到,所以要想配合ViewModel还需要传入Application作为参数,当然,Jetpack已经为我们准备好了

AndroidViewModel:感知应用上下文的ViewModel,继承自ViewModel。

其实实现也很简单,传入Application就行了,需要时获取。

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}
           

然后剩下的便可以通过获取SharedPreference来存储数据,再配合LiveData,SaveStataHandler实现跨越整个App的生命周期同步。当然,数据的持久化存储不仅仅时这一种方式,配合任意方式都可。

使用ViewModel取代Loader

特别对于数据库这类数据,由于需要保持UI与数据库间的数据同步,常常导致在UI控制器(Activity等)进行数据加载,这有违

分离关注点

的原则。

使用

VIewModel

配合

Room

LiveData

能够方便的取代

CusorLoader

的操作,

VIewModel

保证数据的稳定性,

Room

通知数据更改,

LiveData

则自动使用更新的数据更新UI。

Android Jetpack -- ViewModel篇

以上便是ViewModel在整个Jetpack的作用和功能了,现在只是冰山一角,慢慢学习。