JetPack-(4)-ViewModel 视图与数据的桥梁

561 阅读6分钟

ViewModel简介

其实Android 平台上之所以会出现诸如MVP、MVVM之类的项目架构,就是因为在传统的开发模式下,Activity 的任务实在是太重 了,既要负责逻辑处理,又要控制UI展示,甚至还得处理网络回调,等等。在一个小型项目中 这样写或许没有什么问题,但是如果在大型项目中仍然使用这种写法的话,那么这个项目将会 变得非常臃肿并且难以维护,因为没有任何架构上的划分。

而ViewModel的一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样一定程度上可以减少Activity中的逻辑。

ViewModel可以帮助我们更好地将页面与数据从代码层面分离开来,依赖ViewModel的生命周期特性,我们不需要在关心屏幕旋转导致数据丢失的问题,需要注意的是,如果想在ViewModel中使用Context,建议使用ViewModel的子类AndroidViewModel。

1.视图与数据模型之间的桥梁ViewModel

ViewModel是视图View和数据模型Model之间的沟通桥梁 借助ViewModel, 视图 与 数据模型 实现了解耦,同时还能保证视图 与 数据模型之间 保持通信 这样Activity的代码量减少了,只需要维护视图View相关内容,增加了代码的可维护性。

1710332282898.jpg

  • 在ViewModel架构中,数据不由View直接进行管理,而是ViewModel进行管理
  • 当Activity屏幕旋转,销毁时,只会销毁Activity组件,不会将ViewModel以及数据模型Model销毁
  • Activity中的组件获取数据时,不直接从数据模型Model中获取,而是从ViewModel架构组件中获取
  • ViewModel作为不同的Activity或Fragment之间沟通的桥梁
1.1 ViewModel与onSaveInstanceState()方法
  • ViewModel: ViewModel能支持页面中所有的数据,但是需要注意的是,ViewModel不支持数据的持久化,当页面被彻底销毁时,ViewModel及持有的数据就不存在了
  • onSaveInstanceState()方法:只能保存少量的,能支持序列化的数据,但是onSaveInstanceState()方法可以持久化页面的数据。

2.ViewModel的生命周期

如图:

1710333270816.jpg

ViewModel的生命周期与Acitivity或Fragment的生命周期相互独立,ViewModel不受Activity组件销毁的影响。如果由于屏幕旋转原因导致的Activity销毁重建,与之绑定的ViewModel会在销毁时解绑,Activity重建时重新绑定。 ViewModel会在应用生命周期内存活,并且可以在Activity或Fragment之间共享数据

ViewModel的生命周期:一个ViewModel实例对象可以与多个Activity或Fragment绑定

  • 创建:在Activity的首次启动时 创建ViewModel实例对象,如果Activity多次启动,ViewModel只会创建一次。
  • 绑定:Activity与ViewModel关联时,开始绑定ViewModel,Activity组件中绑定ViewModel代码如下:
MainViewModel mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
  • 解绑:当Activity或者Fragment被销毁时,与之绑定的ViewModel会与UI组件解绑
  • 销毁:ViewModel关联的所有的Activity或Fragment全部销毁,则ViewModel实例对象也会被销毁

3.ViewModel使用

3.1 ViewModel使用注意事项

使用ViewModel时,不要将Context上下文对象传入ViewModel中,否则会导致内存泄漏。

如果要使用Context上下文对象,则ViewModel需要继承AndroidViewModel类,在其构造函数中获取Application对象

AndroidViewModel继承自ViewModel,并接收Application作为Context,因为Application会扩展Context:

public class TimerViewModel extends AndroidViewModel {
	 public TimerViewModel(@NonNull Application application) {
        super(application);
    }
}

3.2 使用

3.2.1 创建一个类继承ViewModel

注意:一般情况下,每一个Activity都应该对应一个ViewModel,例如MainActivity,对应的ViewModel应该是MainViewModel.

public class MyViewModel {
    public int count;

    public MyViewModel(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
3.2.2 在MainActivity中
public class MainActivity extends AppCompatActivity {

    private TextView textView;

    private MyViewModel myViewModel;

  
    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // 获取布局组件

        textView = findViewById(R.id.textView);

        Button btn_add = findViewById(R.id.btn_add);

 

          // 获取 ViewModel

          myViewModel new ViewModelProvider(this).get(MyViewModel.class)

        btn_add.setOnclickListener(new View.OnClickListener(){

            @Override

            public void onClick(View view) {

                myViewModel.setNumber(myViewModel.getNumber() + 1);

                textView.setText(String.valueOf(myViewModel.getNumber()));

            }

        })

        // 组件中显示 ViewModel 中的内容

        textView.setText(String.valueOf(myViewModel.getNumber()));

    }

}

运行效果: 在屏幕旋转后,Activity重建,也不会影响到数据运行,自增操作不会被打断

1710336960915.jpg

3.3 向ViewModel传递参数

如果我们确实 需要通过构造函数来传递一些参数,应该怎么办呢?由于所有ViewModel 的实例都是通过 ViewModelP rovider 来获取的,因此我们没有任何地方可以向ViewModel 的构造函数中传递参 数。只需要借助ViewModelProvider.Factory就可以实现了。

现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么 之前的计数就会被清零了。接下来我们就对这一功能进行升级,保证即使在退出程序后又重新 打开的情况下,数据仍然不会丢失。

首先新建一个MyViewModelFactory类,实现ViewModelProvider.Factory接口,

1710337194521.jpg 可以看到,MyViewModelFactory中接收了一个参数countReserved,另外ViewModelProvider.Factory接口要求我们必须实现 create() 方法,因此这里在 crate() 方法中我们创建了MyViewModel的实例,并将countReserved参数传了进去。为什么这里就可以创建MainViewModel的实例呢?因为 create() 方法的执行时机和Activity的生命周期无关。

然后我们在界面上添加一个清零按钮,

1710337412228.jpg

最后修改MainActivity中的代码:

public class MainActivity extends AppCompatActivity {
    private MyViewModel myViewModel;
    private SharedPreferences sp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.textview);
        Button btn_add = findViewById(R.id.btn_add);
        Button btn_clear = findViewById(R.id.btn_clear);

        sp = getPreferences(Context.MODE_PRIVATE);
        int count_reserved = sp.getInt("count_reserved", 0);
        Log.d("aaabbb", "onCreate: "+count_reserved);
        //获取ViewModel
        myViewModel = new ViewModelProvider(this, new MyViewModelFactory(count_reserved))
                .get(MyViewModel.class);

        btn_add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.setNumber(myViewModel.getNumber() + 1);
                textView.setText(String.valueOf(myViewModel.getNumber()));
            }
        });
        btn_clear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myViewModel.setNumber(0);
               textView.setText(String.valueOf(0));

            }
        });
        textView.setText(String.valueOf(myViewModel.getNumber()));
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("aaabbb", "onPause(): "+myViewModel.getNumber());
        sp.edit().putInt("count_reserved", myViewModel.getNumber()).apply();
    }
}

oncrate() 方法中,我们首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值。

接下来在ViewModelProvider中,额外传入了一个MyViewModelFactory参数,这里将读取到的计数值传给了MyViewModelFactory的构造函数,注意,这一步是非常重要的,只有用这种写法才能将计数值最终传递给MyViewModel的构造函数。

然后我们在clear的按钮中对计数器进行清零,并在 onPause() 方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数器都不会丢失。

现在重新运行程序,点击数次+1 按钮,然后退出程序并重新打开,你会发现,计数器 的值是不会丢失的