Android——Jetpack之Viewmodel的学习(java实现)

简介

在页面(Activity/Fragment)很简单的情况下,通常我们会将UI交互,数据获取与处理等相关业务逻辑,全部写在页面中,但是在页面复杂的情况下,这样做是不合适的,它不符合“单一责任”原则。页面只应该负责接收用户的交互,以及将数据展示到屏幕上,相关数据应该单独存放和处理。
为此,Android为我们提供了ViewModel类,专门用于存放应用程序页面所需的数据。它将页面所需的数据从页面中剥离出来,页面只需要处理用户交互,以及负责展示数据的工作。
ViewModel只是用来管理UI的数据的,千万不要让它持有View、Activity或者Fragment的引用(小心内存泄露)

作用

  1. 帮助activity分担一部分的工作,专门用于存放与界面相关数据的。在一定程度上减少activity的逻辑(即代码)
  2. 同时会维护自己独立的生命周期
    如当系统配置发生变更(如切换语言等)、横竖屏切换等,可能会导致 Activity 销毁重建,假设要被销毁是 Activity A,需要被重新创建的是 Activity B,虽然他们都属于同一类型,但是是两个不同的实例对象。因此在 Activity 销毁重建的过程中,就涉及 A 在销毁时,其内部维护的数据要过渡到重建的 B 中,这就依赖于 ViewModel。

    ViewModel这个名字可以这样理解:它是介于View(视图)和Model(模型数据)之间的这样一个东西,它起到了桥梁的作用,使得视图和数据既能够分离开,也能够保持通信。

创建的常用的方式

由于 ViewModel 的生命周期是由系统维护的,因此不能直接在代码中通过 new 的方式创建。

  1. 直接基于ViewModelProvider获取
myViewmodel = new ViewModelProvider(this).get(MyViewmodel.class);
  1. 通过实现 ViewModelFactory 接口创建
  • 首先需要重新构造一个类,让这个类实现ViewModelProvider.Factory接口
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);
    }
}
  • 其次在MainActivity里获取实例
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'

基本用法——横竖屏时保留数据

  1. activity_main布局文件
<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>
  1. 创建一个类 MyViewmodel去继承ViewModel,所有与界面相关的数据都应该放在ViewModel中。
public class MyViewmodel extends ViewModel {
    
    
    //a用来计数
    protected int a=0;
    public MyViewmodel(int a) {
    
    
         this.a = a;
    }
}
  1. MainActivity
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虽然在屏幕旋转时不会丢失数据,但是如果退出程序之后在打开,就会被清零了。接下来我们对这一功能进行升级,保证程序退出后又重新打开后数据仍然不会丢失。

  1. 修改activity_main.xml
    添加一个清零按钮方便用户将计算器清零。
<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>
  1. 修改MyViewmodel类
public class MyViewmodel extends ViewModel {
    
    
    protected int a;

    public MyViewmodel(int countReserved){
    
    
        a=countReserved;
    }
}
  1. 新建一个VmFactory类,并让它实现ViewModelProvider.Factory接口
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);
    }
}
  1. 修改MainActivity
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()方法中对当前的计数进行保存,这样就可以保证不管程序退出还是进入后台,计数都不会消失。

猜你喜欢

转载自blog.csdn.net/The_onion/article/details/128046031