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 funcreate(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按钮,然后退出程序并重新打开,会发现,计数器的值是不会丢失的。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)