探究Jetpack(一)之ViewModel和Lifecycles

目录

  • Jetpack简介
  • ViewModel
    • ViewModel的基本用法
    • 向ViewModel传递参数
  • Lifecycles
    • 为什么用Lifecycles
    • Lifecycles基本用法
    • Lifecycle主动获知当前的生命周期状态

Jetpack简介

Jetpack是一个开发组件工具集,它的主要目的是帮助我们编写出更加简洁的代码,并简化开发过程。
Jetpack中的组件有一个特点,它们大部分不依赖于任何Android系统版本,这意味着这些组件通常是定义在AndroidX库当中的,并且拥有非常好的向下兼容性

Android studio新建一个JetpackTest工程,然后开始了解Jetpack

ViewModel

ViewModel应该可以算是Jetpack中最重要的组件之一了,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑
ViewModel还有一个非常重要的特性。当手机发生横竖屏旋转的时候,Activity会被重新创建,同时存放在Activity中的数据也会丢失。而ViewModel的生命周期和Activity不同,它可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失

ViewModel的基本用法

比较好的编程规范是给每一个Activity和Fragment都创建一个对应的ViewModel,先为MainActivity创建一个对应的MainViewModel类,并让它继承自ViewMode
根据前面所了解的知识,所有与界面相关的数据都应该放在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(<你的Activity或Fragment实例>).get(<你的ViewModel>::class.java)

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

重新运行一下程序,点击界面上的“Plus One”按钮,计数器就会开始增长了
探究Jetpack(一)之ViewModel和Lifecycles_第1张图片

向ViewModel传递参数

由于所有ViewModel的实例都是通过ViewModelProvider来获取的,因此没有任何地方可以向ViewModel的构造函数中传递参数
这就需要借助ViewModelProvider.Factory就可以实现了
现在的计数器虽然在屏幕旋转的时候不会丢失数据,但是如果退出程序之后再重新打开,那么之前的计数就会被清零了。接下来就对这一功能进行升级,保证即使在退出程序后又重新打开的情况下,数据仍然不会丢失
修改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 <T : ViewModel> create(modelClass: Class<T>): T {
	 	return MainViewModel(countReserved) as T
	 }
}

MainViewModelFactory的构造函数中也接收了一个countReserved参数
另外ViewModelProvider.Factory接口要求必须实现create()方法,因此这里在create()方法中创建了MainViewModel的实例,并将countReserved参数传了进去

修改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()
		 }
		 refreshCounter()
	 }
	 
	 override fun onPause() {
		 super.onPause()
		 sp.edit {
		 	putInt("count_reserved", viewModel.counter)
		 }
	 }
	 ...
}

在onCreate()方法中,首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值
接下来在ViewModelProvider中,额外传入了一个MainViewModelFactory参数,这里将读取到的计数值传给了MainViewModelFactory的构造函数。注意,这一步是非常重要的,只有用这种写法才能将
计数值最终传递给MainViewModel的构造函数
剩下的代码就比较简单了,在“Clear”按钮的点击事件中对计数器进行清零,并且在onPause()方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数都不会丢失

重新运行程序,,点击数次“Plus One”按钮,然后退出程序并重新打开,可以看到,计数器的值是不会丢失的,只有点击“Clear”按钮,计数器的值才会被清零

探究Jetpack(一)之ViewModel和Lifecycles_第2张图片

Lifecycles

为什么用Lifecycles

在编写Android应用程序的时候,可能会经常遇到需要感知Activity生命周期的情况
比如说,某个界面中发起了一条网络请求,但是当请求得到响应的时候,界面或许已经关闭了,这个时候就不应该继续对响应的结果进行处理
因此,需要能够时刻感知到Activity的生命周期,以便在适当的时候进行相应的逻辑控制
这种需求是广泛存在的,同时也衍生出了一系列的解决方案,比如通过在Activity中嵌入一个隐藏的Fragment来进行感知,或者通过手写监听器的方式来进行感知,等等
这些实现方式虽然是可以正常工作的,但是不够优雅,需要在Activity中编写太多额外的逻辑
而Lifecycles组件就是为了解决这个问题而出现的,它可以让任何一个类都能轻松感知到Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理

Lifecycles基本用法

新建一个MyObserver类,并让它实现LifecycleObserver接口,代码如下所示:

class MyObserver : LifecycleObserver {

	 @OnLifecycleEvent(Lifecycle.Event.ON_START)
	 fun activityStart() {
	 	Log.d("MyObserver", "activityStart")
	 }
	 
	 @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
	 fun activityStop() {
	 	Log.d("MyObserver", "activityStop")
	 }
}

在义activityStart()和activityStop()的方法上使用了@OnLifecycleEvent注解,并传入了一种生命周期事件
生命周期事件的类型一共有7种:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP和ON_DESTROY分别匹配Activity中相应的生命周期回调;另外还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期回调
因此,上述代码中的activityStart()和activityStop()方法就应该分别在Activity的onStart()和onStop()触发的时候执行

但是代码写到这里还是无法正常工作的,因为当Activity的生命周期发生变化的时候并没有人去通知MyObserver,这个时候就得借助LifecycleOwner这个好帮手了,它可以使用如下的语法结构让MyObserver得到通知,修改MainActivity.xml文件:

class MainActivity : AppCompatActivity() {
	 ...
	 override fun onCreate(savedInstanceState: Bundle?) {
		 super.onCreate(savedInstanceState)
		 setContentView(R.layout.activity_main)
		 ...
		 lifecycle.addObserver(MyObserver())
	 }
	 ...
}

首先调用LifecycleOwner的getLifecycle()方法,得到一个Lifecycle对象,然后调用它的addObserver()方法来观察LifecycleOwner的生命周期,再把MyObserver的实例传进去就可以了

现在重新运行一下程序, activityStart这条日志就会打印出来了。如果你再按下Home键或者Back键的话,activityStop这条日志也会打印出来
在这里插入图片描述

Lifecycle主动获知当前的生命周期状态

只需要在MyObserver的构造函数中将Lifecycle对象传进来即可,如下所示:

class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver {
 	...
}

有了Lifecycle对象之后,我们就可以在任何地方调用lifecycle.currentState来主动获知当前的生命周期状态
lifecycle.currentState返回的生命周期状态是一个枚举类型,一共有INITIALIZED、DESTROYED、CREATED、STARTED、RESUMED这5种状态类型
它们与Activity的生命周期回调所对应的关系如图所示
探究Jetpack(一)之ViewModel和Lifecycles_第3张图片
也就是说,当获取的生命周期状态是CREATED的时候,说明onCreate()方法已经执行了,但是onStart()方法还没有执行。当获取的生命周期状态是STARTED的时候,说明onStart()方法已经执行了,但是onResume()方法还没有执行,以此类推

你可能感兴趣的:(Android,android,android,studio)