Jetpack学习之ViewModel

1 ViewModel的作用

从ViewModel的实现原理来看,ViewModel主要是用于解决一些界面上的“临时”、“瞬态”数据存储的问题,例如,界面上有一个EditText,用户在里面已经输入了一些内容,但是此时如果设备发生了诸如旋转屏幕等操作,此时如果不进行状态保存操作(也就是我们常说的onSaveInstanceState()),由于Activity的重新创建,这部分数据都会丢失。换句话说,这些数据,和Activity的生命周期“绑定”了,如果想要持久化这些数据,就需要进行手动管理。

因此,谷歌推出了ViewModel来解决以上问题。需要注意的是,Android已经为很多控件在以上情况发生时进行状态保存,例如,EditText就会在屏幕发生旋转时自动保存内容,并恢复它。主要实现也是借onSaveInstanceState()和onRestoreInstanceState()这两个方法来实现的,Activity在发生旋转或者其他异常销毁的情况下,会调用自身onSaveInstanceState()方法来进行状态保存,在这个方法的默认实现中,会遍历Activity中所有的View,并依次调用其onSaveInstanceState()方法进行状态保存。同理,Activity会在重新创建时调用onRestoreInstanceState()方法来进行状态恢复,在这个方法的默认实现中,会遍历Activity中所有的View,并调用其的onRestoreInstanceState()方法。View可以重写这两个方法来实现状态保存和恢复(对于一些自定义的View,我们也可以通过这种方式来实现保存和恢复状态的功能)。不过这些都和ViewModel有着本质的不同,onSaveInstanceState()和onRestoreInstanceState()方法本质是通过序列化,将“状态”保存到文件中,再从文件中恢复它们。而ViewModel是通过应用运行时的在内存中的实例对象来保存这些数据。从性能上,ViewModel应该是具有更好的性能和响应速度。而onSaveInstanceState()和onRestoreInstanceState()方法则更适合一些少量的,支持序列化的数据的保存,且其支持数据的持久化,我们可以在这两个方法中进行一些数据持久化的操作。

2 ViewModel实战

通过一个案例来演示一下ViewModel的效果,我编写了一个简单的程序,界面上存在一个显示数字的TextView和Button,点击Button,会给TextView中的数字加1。

2.1 不添加任何额外操作

在未使用onSaveInstanceState()和onRestoreInstanceState()方法进行状态保存恢复或未使用ViewModel的情况下,效果图如下图所示,TextView中的数字在发生屏幕旋转时会被重置。

 代码很简单,如下:

package com.android.viewmodeldemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button mBtnPlus;
    private TextView mNumCount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnPlus = findViewById(R.id.btn_plus);
        mNumCount = findViewById(R.id.num_count_textview);
        mBtnPlus.setOnClickListener(v -> {
            plusNumCount();
        });
    }

    private void plusNumCount() {
        mNumCount.setText(String.valueOf(Integer.valueOf(mNumCount.getText().toString()) + 1));
    }
}

2.2 借助ViewModel

通过将需要独立于Activity生命周期的数据包装到ViewModel中,我们实现了将这部分数据从Activity中“独立”出来,也就是说,这里说的“独立”并非是指Activity不再持有这部分数据,而是指数据不再随着Activity的生命周期而重置或者改变,避免了我们主动的去保存、管理它们的“状态”。借助ViewModel,效果图如下所示,TextView中的数字在发生屏幕旋转时不会被重置。

 代码如下,ViewModel类:

package com.android.viewmodeldemo;

import androidx.lifecycle.ViewModel;

public class NumCountViewModel extends ViewModel {
    private int mNumCount;

    public int getNumCount() {
        return mNumCount;
    }

    public void setNumCount(int numCount) {
        mNumCount = numCount;
    }
}

Activity中的逻辑:

package com.android.viewmodeldemo;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button mBtnPlus;
    private TextView mNumCount;
    private NumCountViewModel mNumCountViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnPlus = findViewById(R.id.btn_plus);
        mNumCount = findViewById(R.id.num_count_textview);

        // 1 创建ViewModel对象
        mNumCountViewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()))
                .get(NumCountViewModel.class);
        // 2 为TextView赋值
        mNumCount.setText(String.valueOf(mNumCountViewModel.getNumCount()));

        mBtnPlus.setOnClickListener(v -> {
            plusNumCount();
        });
    }

    private void plusNumCount() {
        mNumCountViewModel.setNumCount(mNumCountViewModel.getNumCount() + 1);
        mNumCount.setText(String.valueOf(mNumCountViewModel.getNumCount()));
    }
}

从具体代码中看,ViewModel类并无什么特殊的逻辑,主要是作为数据的包装类而存在。在ViewModel实现原理分析中,我们可以看到,ViewModel实际上还是被存储到ViewModelStoreOwner中,本样例中,Activity充当了这个Owner对象,进而间接的存储到ActivityThread类中。

2.3 借助onSaveInstanceState()和onRestoreInstanceState()

借助这两个方法,我们也可以实现在Activity异常销毁时保存我们数据状态,效果和ViewModel一样,如下图

 代码如下:

package com.android.viewmodeldemo;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private static final String KEY_NUM_COUNT = "key_num_count";

    private Button mBtnPlus;
    private TextView mNumCount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnPlus = findViewById(R.id.btn_plus);
        mNumCount = findViewById(R.id.num_count_textview);
        mBtnPlus.setOnClickListener(v -> {
            plusNumCount();
        });
    }

    private void plusNumCount() {
        mNumCount.setText(String.valueOf(Integer.valueOf(mNumCount.getText().toString()) + 1));
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(KEY_NUM_COUNT, mNumCount.getText().toString());
    }

    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            String savedNumCount = savedInstanceState.getString(KEY_NUM_COUNT);
            mNumCount.setText(savedNumCount);
        }
    }
}

从代码量上看,借助ViewModel和借助onSaveInstanceState()以及onRestoreInstanceState()差不多,甚至ViewModel的代码好像还要多一些,毕竟要单独创建一个ViewModel类,但是,这里是只有一个TextView的场景。如果有大量需要保持状态的数据,我们都可以放在ViewModel中,借助Generate功能为它们生成getter和setter方法即可,无需在onSaveInstanceState()以及onRestoreInstanceState()中一个一个的保存了。

并且前面也提到过,onSaveInstanceState()以及onRestoreInstanceState()主要是用于存储一些少量的(因为涉及到IO,所以如果是大量数据会影响到性能),支持序列化的数据,它可以将数据持久化到存储空间中。而ViewModel支持所有类型的数据,它本质是将数据保存到内存中,借助ActivityThread维持其状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值