3.1 什么是ViewModel
按照官方描述,ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类可在发生屏幕旋转等配置更改后让数据继续留存。
上面的描述怎么理解呢?这里通过上一章遗留的问题来说明。在上一章中使用Lifecycle实现了广告引导页的需求,即用户打开App进入引导页面,倒计5秒后进入主页面,正常操作下运行日志如图3-1所示。
图3-1 正常操作下的运行日志
如果在广告剩余2秒的时候将手机屏幕旋转,那么打印日志如图3-2所示。
图3-2 广告剩余2秒旋转屏幕日志
通过图3-2可以发现,屏幕旋转后,原有计时停止后又重新开始了。出现这个问题的原因是当屏幕旋转时Activity被销毁后又重新创建了。
注意
屏幕旋转时,生命周期的变化取决于configChanges属性,这里未配置config-Changes的属性,所以屏幕由竖屏切换为横屏时,会重新执行每个生命周期方法。读者可自行查阅configChanges的其他属性值。
这种结果用户肯定是不能接受的,因为旋转屏幕操作导致用户需要重复观看广告。这个问题一般如何处理呢?一种方式是通过修改configChanges属性使得App在旋转的时候不被销毁,但因为有其他业务逻辑限制,所以这里不考虑。另一种常用的方式是通过重写onSaveInstanceState方法在Activity被销毁的时候将当前计时的节点存储起来,重新开始计时的时候从上次计时的节点开始计时。
在使用这种方式处理之前,首先需要修改上一章中AdvertisingManage的代码计时器,将计时的起止时间修改为传参的形式,示例代码如下:
class AdvertisingManage(millisInFuture: Long = 5000) : LifecycleObserver { ... //定时器 private var countDownTimer: CountDownTimer? = object : CountDownTimer(millisInFuture, 1000) { ... }
这样修改后,每次计时都会将当前的计时记录下来,便于下一次使用。示例代码如下:
//计时的时长,默认值5秒 var millisInFuture: Long = 5000 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... val advertisingManage = AdvertisingManage(millisInFuture) ... advertisingManage.advertisingManageListener = object : AdvertisingManage.AdvertisingManageListener { override fun timing(second: Int) { tvAdvertisingTime.text = "广告剩余$second秒" millisInFuture = second.toLong() * 1000 } ..... }
接着在Activity中重写onSaveInstanceState方法,记录Activity销毁时计时的节点。在第二次创建AdvertisingManage实例的时候将还需要计时的时间传给Advertising-Manage类。示例代码如下:
//计时的时长,默认值5秒 var millisInFuture: Long = 5000 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... savedInstanceState?.let { millisInFuture = it.getLong(KEY_MILLISINFUTURE) } val advertisingManage = AdvertisingManage(millisInFuture) ... } ... override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(KEY_MILLISINFUTURE, millisInFuture) } companion object { //key计时的开始时间 const val KEY_MILLISINFUTURE = "keyMillsimfuture" }
修改上述代码之后,进行与前面相同的操作——广告剩余2秒时将手机屏幕旋转。打印日志如图3-3所示。
图3-3 广告剩余2秒时旋转屏幕的日志
通过打印的日志可以看到,在2秒时停止计时,屏幕旋转后从1s开始计时,符合预期效果。上面是通过onSaveInstanceState方法来解决屏幕旋转导致广告重新计时这一问题的。那么是否有更优雅的解决方案呢?