新的一年,优先把欠账补齐,关于Jetpack下Lifecycle、ViewModel、LiveData组件库,以及ViewModel+LiveData项目实践,如今也只差ViewModel还没有记录了,接下来就开始吧…
Android Architecture Components 架构组件
关于ViewModel
的学习,以及ViewModel
近几年的一个发展情况,都可以结合 官方文档 与 三方blog进行补课 ~
其实 官方 对于ViewModel
的介绍并不多,或者可以说是很有限,不过也可以说是精简吧
ViewModel组件
的本质是将数据存储在ViewModelProvider
中,当我们需要数据时可及时从ViewModelProvider
中取出
官网解释
如果感觉这部分看的麻烦,可以看下面的归纳部分
Android 框架可以管理界面控制器(如 Activity 和 Fragment)的生命周期。Android 框架可能会决定销毁或重新创建界面控制器,以响应完全不受您控制的某些用户操作或设备事件。
如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity
中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState()
方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。
另一个问题是,界面控制器经常需要进行可能需要一些时间才能返回的异步调用。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。此项管理需要大量的维护工作,并且在为配置更改重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。
诸如 Activity 和 Fragment 之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。为界面控制器分配过多的责任可能会导致单个类尝试自己处理应用的所有工作,而不是将工作委托给其他类。以这种方式为界面控制器分配过多的责任也会大大增加测试的难度。
从界面控制器逻辑中分离出视图数据所有权的操作更容易且更高效。
简短归纳
支持感知组件生命周期
,主要用的还是 Lifecycle组件,这点从ViewModel的包引用和 LiveData组件 的源码中就可以知悉
数据与UI隔离
,通过ViewModel组件获取数据、加工数据,然后回调给UI层,明确职责,工作分离 (可参考 MVP、MVVM 框架结构)
数据持久化,支持较大量数据的保存与恢复
,不会因为组件(Activity、Fragment)的生命周期变化而导致数据丢失(屏幕旋转导致Activity重新创建而重置数据),可及时保存数据和恢复数据; 这里存在一个论点:在Android中我们可以通过onSaveInstanceState()
保存数据,之后在onRestoreInstanceState
或onCreate()
中取出保存的数据,这个特性会不会有点多余?论述:onSaveInstanceState
方式只适合保存少量数据
,不适合大量数据,而ViewModel组件
就解决了这个问题
Activity和Fragment共享数据源,提升系统性能
;比如Activity和Fragment需要同一份数据源,同时这份数据较大,导致网络请求返回结果时间较长,那么完全可以通过ViewModel组件
使Activity和Fragmeng
达到通信的目的,获取到最新的数据
Activity之间数据独立
,ViewModel是作用于当前Activity和依附其的Fragment,并不是作用于全局Activity(意味多个Activity绑定后无法同步共享数据)
数据与生命周期绑定
,ViewModel与注册的Activity的生命周期绑定,有着与Activity同步的生命周期,如下图.这样就算网络请求的数据的异步回来后Activity已经销毁也不会出现问题,因为ViewModel也会被销毁终止数据的回调.
ViewModel的生命周期长于Activity组件的生命周期,下方为官网供图
使用ViewModel
时,除去继承ViewModel
的类外;还会在对应的Activity、Fragment组件中通过ViewModelProvider获取到对应的ViewModel实例
针对不同的ViewModel版本,对应的AIP有所改变,比如在之前我看ViewModelProvider的构造方法时,发现重载的比较多,如下
使用方式
xxxViewModel model = new ViewModelProvider(this).get(xxxViewModel.class);
但是在2.1.0版本
的时候,ViewModelProvider
的构造方法就剩下俩个了,如下图所示
使用方式
xxxViewModel model = new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(xxxViewModel.class);
build.gradle
implementation "androidx.lifecycle:lifecycle-viewmodel:2.1.0"
一般官网的使用方式都是比较常规,死板的,在真实使用中我们都可以自行扩展一下
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() {
// Do an asynchronous operation to fetch users.
}
}
Activity
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
Fragment
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 ListFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
// Update the UI.
});
}
}
ViewModel注意事项
创建一个类继承于ViewModel
一般有一个get方法返回该ViewModel的实例
其中在类内声明可以统一出口,在外部声明的话就是灵活一点
TestViewModel
package com.example.viewmodel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
/**
* @author MrLiu
* @date 2022/1/4
* desc
*/
public class TestViewModel extends ViewModel {
private MutableLiveData<String> liveData;
public LiveData<String> getYourName() {
if (liveData == null) {
liveData = new MutableLiveData<String>();
}
return liveData;
}
/**
* 修改数据源
*/
public void setYourName(String name) {
liveData.postValue(name);
}
}
使用 - ViewModel注意事项
ViewModelProvider
获取ViewModel
实例,但是要留心提前知悉中提到的关于ViewModelProvider的注意事项
将ViewModel绑定观察者
,否则无法监听数据ViewModel
数据的场景取决于自己,修改方式上方ViewModel注意事项也有提到
MainActivity
package com.example.viewmodel;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView mBtn = findViewById(R.id.btn);
TextView mContent = findViewById(R.id.tv_content);
//创建ViewModel 被观察者
TestViewModel model = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(TestViewModel.class);
//绑定观察者,观察数据源的变化
model.getYourName().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
mContent.setText(s);
}
});
//修改ViewModel数据源的数据
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
model.setYourName("岁岁年年,年年岁岁,新的一年要争气!");
}
});
}
}
activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Content Display ~~~"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#12f2"
android:gravity="center"
android:text="Send Data"
android:textSize="18dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment数据共享
这里主要指的是同一个Activity下多个Fragment数据共享的场景,多个Fragment可以使用其 activity 范围共享 ViewModel 来处理此类通信
此方法具有以下优势:
疑问扩展:Activity和Fragment的通信方式有哪些?
示例代码主要借鉴自官网
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 ListFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
// Update the UI.
});
}
}
嗯… 你能看到这里的话,不知道你会不会有和我一样的疑问,为什么在Fragment中的ViewModel和Activity中的Model不同,这能用吗?
我看了好几篇blog都没有说,官网也没详细说明,因为我没有时间去详细尝试(最近比较忙…),所以我把我的想法说一下,主要有俩种方式
采用一个新的ViewModel,由ViewModel自带的机制进行赋值,将Activity的ViewModel数据重新赋值到Fragment的ViewModel中(我在ViewModel中没有看到这方面源码,所以觉得这种方式应该不太行)
Fragment中用的ViewModel与Actvity的ViewModel相同,如上文Activity的TextViewModel,我们在Fragment中也使用TextViewModel.class(我觉得这种方式概率大一点
)
在提前知悉
中有关于ViewModelProvider的构造方法,其中ViewModelProvider of(@NonNull Fragment fragment)
,ViewModelProvider of(@NonNull FragmentActivity activity)
之间主要区别是传入参数不同
,分别为 Fragment
或 FragmentActivity
;
然后,通过 ViewModel of 方法创建的 ViewModel 实例, 对于同一个 fragment 或者 fragmentActivity 实例,ViewModel 实例是相同的,因而我们可以利用该特点,在 Fragment 中创建 ViewModel 的时候,传入的是 Fragment 所依附的 Activity;因而他们的 ViewModel 实例是相同的,就可以做到共享数据
。
如下 TestActivity 和 TestFragment 中的 ViewModel 是同一个实例,从而实现数据共享
// TestActivity (TestFragment 依赖的 Activity)
mTestViewModel = ViewModelProviders.of(this, new TestViewModel.Factory(mkey)).get(TestViewModel.class);
MutableLiveData<String> nameEvent = mTestViewModel.getNameEvent();
nameEvent.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.i(TAG, "onChanged: s = " + s);
mTvName.setText(s);
}
});
// TestFragment 中
mViewModel = ViewModelProviders.of(mActivity).get(TestViewModel.class);
mViewModel.getNameEvent().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.d(TAG, "onChanged: s =" + s + " mViewModel.getKey() =" + mViewModel.getKey());
mTvName.setText(s);
boolean result = mViewModel == ((TestActivity) mListener).mTestViewModel;
Log.d(TAG, "onChanged: s result =" + result);
}
});