Jetpack(二)—— ViewModel

Jetpack(二)—— ViewModel,第1张

Jetpack(二)—— ViewModel ViewModel

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

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

另外,ViewModel还有一个非常重要的特性。我们都知道,当手机发生横竖屏旋转的时候, Activity会被重新创建,同时存放在Activity中的数据也会丢失。**而ViewModel的生命周期和Activity不同,**它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,**将与界面相关的变量存放在ViewModel当中,**这样即使旋转手机屏幕,界面上显示的数据也不会丢失。ViewModel的生命周期如图所示:

以下通过一个简单的计数示例来学习ViewModel的基本用法。

1 ViewModel的基本用法

由于Jetpack中的组件通常是以AndroidX库的形式发布的,因此一些常用的Jetpack组件会在创建Android项目时自动被包含进去。 不过如果想要使用ViewModel组件,还需要在app/build.gradle文件中添加如下依赖:

dependencies {
	....
	implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}

通常来讲,比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel,因此这里为MainActivity创建一个对应的MainViewModel类,并让它继承自ViewModel,代码如下所示:

class MainViewModel: ViewModel() {
}

根据前面所学,所有与界面相关的数据都应该放在ViewModel中。要实现 一个计数器的功能,就可以在ViewModel中加入一个counter变量用于计数,如下所示:

class MainViewModel : ViewModel() {
    var counter = 0
}

在界面上添加一个按钮,每点击一次按钮就让计数器加1,并且把最新的计数显示在界面上。修改activity_main.xml中的代码,如下所示:



    

    

布局文件非常简单,一个TextView用于显示当前的计数,一个Button用于对计数器加1。 接着我们开始实现计数器的逻辑,修改MainActivity中的代码,如下所示:

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
        infoText.text = viewModel.counter.toString()
    }
  
}

这里最需要注意的是,不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例, 具体语法规则如下:

ViewModelProvider().get(<定义的ViewModel>::class.java)

之所以要这么写,是因为ViewModel有其独立的生命周期,并且其生命周期要长于Activity。 如果在onCreate()方法中创建ViewModel的实例,那么每次onCreate()方法执行的时 候,ViewModel都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了。

除此之外的其他代码应该都是非常好理解的,refreshCounter()方法用来显示当前的计数,然后每次点击按钮的时候对计数器加1,并调用refreshCounter()方法刷新计数。

如果尝试通过侧边工具栏旋转一下模拟器的屏幕,就会发现Activity虽然被重新创建了,但是计数器的数据却没有丢失。

2 向ViewModel传递参数

上一节中创建的MainViewModel的构造函数中没有任何参数,但是有些情况下,确实需要通过构造函数来传递一些参数,由于所有ViewModel的实例都是通过ViewModelProvider来获取的,因此没有任何地方可以向ViewModel的构造函数中传递参数。这个问题需要借助ViewModelProvider.Factory就可以实现了。

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

实现这个功能需要在退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,并传递MainViewModel。因此,这里修改MainViewModel中的代码,如下所示:

class MainViewModel(countReserved: Int) : ViewModel() {
    var counter = countReserved
}

现在给MainViewModel的构造函数添加了一个countReserved参数,这个参数用于记录之前保存的计数值,并在初始化的时候赋值给counter变量。

接下来就是借助ViewModelProvider.Factory向MainViewModel的构造函数传递数据了。

新建一个MainViewModelFactory类,并让它实现ViewModelProvider.Factory接口, 代码如下所示:

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
    override fun  create(modelClass: Class): T {
        return MainViewModel(countReserved) as T
    }
}

可以看到,MainViewModelFactory的构造函数中也接收了一个countReserved参数。另外ViewModelProvider.Factory接口要求必须实现create()方法,因此这里在create()方法中创建了MainViewModel的实例,并将countReserved参数传了进去。 为什么这里就可以创建MainViewModel的实例了呢?因为create()方法的执行时机和Activity的生命周期无关,所以不会产生之前提到的问题。

另外,在界面上添加一个清零按钮,方便用户手动将计数器清零。修改activity_main.xml中的代码,如下所示:



    ...

    

最后修改MainActivity中的代码,如下所示:

class MainActivity : AppCompatActivity() {

  lateinit var viewModel: MainViewModel
  lateinit var sp: SharedPreferences

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    sp = getPreferences(Context.MODE_PRIVATE)
    val countReserved = sp.getInt("count_reserved", 0)

    viewModel = ViewModelProvider(
      this,
      MainViewModelFactory(countReserved)
    ).get(MainViewModel::class.java)


    clearBtn.setOnClickListener {
      viewModel.counter = 0
      refreshCounter()
    }
    
    ...

  }

  override fun onPause() {
    super.onPause()
    sp.edit {
      putInt("count_reserved", viewModel.counter)
    }
  }
  
  ...
}

在onCreate()方法中,首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值。接下来在ViewModelProvider中,额外传入了一个MainViewModelFactory参数,这里将读取到的计数值传给了 MainViewModelFactory的构造函数。注意,只有用这种写法才能将计数值最终传递给MainViewModel的构造函数。

剩下的代码就比较简单了,在Clear按钮的点击事件中对计数器进行清零,并且在onPause()方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数都不会丢失。

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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zaji/5659989.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存