在页面(Activity/Fragment)很简单的情况下,通常我们会将UI交互,数据获取与处理等相关业务逻辑,全部写在页面中,但是在页面复杂的情况下,这样做是不合适的,它不符合“单一责任”原则。页面只应该负责接收用户的交互,以及将数据展示到屏幕上,相关数据应该单独存放和处理。
为此,Android为我们提供了ViewModel类,专门用于存放应用程序页面所需的数据。它将页面所需的数据从页面中剥离出来,页面只需要处理用户交互,以及负责展示数据的工作。
ViewModel只是用来管理UI的数据的,千万不要让它持有View、Activity或者Fragment的引用(小心内存泄露)
ViewModel这个名字可以这样理解:它是介于View(视图)和Model(模型数据)之间的这样一个东西,它起到了桥梁的作用,使得视图和数据既能够分离开,也能够保持通信。
由于 ViewModel 的生命周期是由系统维护的,因此不能直接在代码中通过 new 的方式创建。
myViewmodel = new ViewModelProvider(this).get(MyViewmodel.class);
public class VmFactory implements ViewModelProvider.Factory {
private int a;
public VmFactory(int a) {
this.a = a;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T)new MyViewmodel(a);
}
}
myViewmodel = new ViewModelProvider(this, new VmFactory(0)).get(MyViewmodel.class);
其实,上述两种方式最终都是基于 ViewModelProvider.Factory 来生成 ViewModel 实例,只不过第一种方式如果不传 Factory,内部会使用默认的 Factory。
ViewModel 目前只有一个生命周期方法 onCleared(),是在 ViewModel 实例对象被清除的时候回调,也就是与之相关的Activity都被销毁时,该方法会被系统调用,我们可以在这个方法里面执行一些资源释放的操作,以免内存泄漏。
注意:既然ViewModel的销毁是由系统来判断和执行的,那么系统是如何判断的呢?是根据Context引用。因此,我们在使用ViewModel的时候,千万不能从外面传入Activity,Fragment或者View之类的含有Context引用的东西,否则系统会认为该ViewModel还在使用中,从而无法被系统销毁回收,导致内存泄漏的发生。
添加依赖
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32dp" />
<Button
android:id="@+id/plusOneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="plus one" />
</LinearLayout>
public class MyViewmodel extends ViewModel {
//a用来计数
protected int a=0;
public MyViewmodel(int a) {
this.a = a;
}
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
MyViewmodel myViewmodel;
TextView textView;
Button plusonebtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//不能直接创建ViewModel的实例,不然每次活动oncreate()都会创建一个实例,无法保留其中数据
//不需传参,使用无参构造函数
myViewmodel = new ViewModelProvider(this).get(MyViewmodel.class);
plusonebtn = findViewById(R.id.plusOneBtn);
textView = findViewById(R.id.infoText);
plusonebtn.setOnClickListener(this);
textView.setText(String.valueOf(myViewmodel.a));
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.plusOneBtn:
myViewmodel.a++;
textView.setText(myViewmodel.a + "");//+空字符串是为了转字符串
break;
default:
break;
}
}
}
上面demo虽然在屏幕旋转时不会丢失数据,但是如果退出程序之后在打开,就会被清零了。接下来我们对这一功能进行升级,保证程序退出后又重新打开后数据仍然不会丢失。
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
......
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/clearbtn"
android:layout_gravity="center_vertical"
android:text="Clear"
/>
</LinearLayout>
public class MyViewmodel extends ViewModel {
protected int a;
public MyViewmodel(int countReserved){
a=countReserved;
}
}
public class VmFactory implements ViewModelProvider.Factory {
private int a;
public VmFactory(int a) {
this.a = a;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T)new MyViewmodel(a);
}
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
MyViewmodel myViewmodel;
TextView textView;
Button plusonebtn;
Button clearbtn;
//用来存放数据
SharedPreferences preferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
preferences= PreferenceManager.getDefaultSharedPreferences(this);
int savedCounter=preferences.getInt("count_reserved",0);
//若需要传参数使用这个同时实现ViewModelProvider.Factory
myViewmodel = new ViewModelProvider(this, new VmFactory(savedCounter)).get(MyViewmodel.class);
plusonebtn = findViewById(R.id.plusOneBtn);
clearbtn=findViewById(R.id.clearbtn);
textView = findViewById(R.id.infoText);
plusonebtn.setOnClickListener(this);
clearbtn.setOnClickListener(this);
textView.setText(String.valueOf(myViewmodel.a));
}
//重写onPause():对当前数据进行保存,这样可以保证不管程序退出还是进入后台,计数都不会消失
@Override
protected void onPause() {
super.onPause();
SharedPreferences.Editor editor=preferences.edit();
editor.putInt("count_reserved",myViewmodel.a);
editor.apply();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.plusOneBtn:
myViewmodel.a++;
textView.setText(myViewmodel.a + "");//+空字符串是为了转为字符串
break;
case R.id.clearbtn:
myViewmodel.a=0;
textView.setText(myViewmodel.a+"");
break;
default:
break;
}
}
在onCreate()方法中,我们首先获取了SharedPreference的实例,然后读取之前把保存的数据,没有读到的话,就使用0作为默认值。接下来在ViewModelProvider中,额外传入了一个MainViewModelFactory参数,这里将读取到的计数值传给了MainViewModelFactory的构造函数。
接下来的代码,就是我们在“Clear”按钮的点击事件中对计算器进行清零,并且在==onPause()方法中对当前的计数进行保存,这样就可以保证不管程序退出还是进入后台,计数都不会消失。