Android Jetpack开发:原理解析与应用实战
上QQ阅读APP看书,第一时间看更新

3.1 什么是ViewModel

按照官方描述,ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类可在发生屏幕旋转等配置更改后让数据继续留存。

上面的描述怎么理解呢?这里通过上一章遗留的问题来说明。在上一章中使用Lifecycle实现了广告引导页的需求,即用户打开App进入引导页面,倒计5秒后进入主页面,正常操作下运行日志如图3-1所示。

034-1

图3-1 正常操作下的运行日志

如果在广告剩余2秒的时候将手机屏幕旋转,那么打印日志如图3-2所示。

034-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所示。

036-1

图3-3 广告剩余2秒时旋转屏幕的日志

通过打印的日志可以看到,在2秒时停止计时,屏幕旋转后从1s开始计时,符合预期效果。上面是通过onSaveInstanceState方法来解决屏幕旋转导致广告重新计时这一问题的。那么是否有更优雅的解决方案呢?