Day 28--Complex lifecycle situations

在范例Dessert App中利用timer来观察更复杂的生命周期

Timer

专案中的class DessertTimer有一个startTime()与stopTimer()

class DessertTimer {

    // The number of seconds counted since the timer started
    var secondsCount = 0

    private var handler = Handler()
    private lateinit var runnable: Runnable
    
    fun startTimer() {...}
    fun stopTimer() {...}
}

藉由Runnable与Handler以後台程序执行startTime()时
执行时会log每秒印出 Timer is at : $secondsCount

在MainActivity宣告一个变数

private lateinit var dessertTimer : DessertTimer

并在onCreate()中实例化

dessertTimer = DessertTimer()

Start and stop the timer

Activity生命周期

可以看出在activity显现前,就会呼叫onStart(),而activity不可见之後,会呼叫onStop()
在这二个地方加入startTimer()与stopTimer()

override fun onStart() {
        super.onStart()
        dessertTimer.startTimer()
        Timber.i("onStart Called")
    }

override fun onStop(){
        super.onStop()
        dessertTimer.stopTimer()
        Timber.i("onStop Called")
    }
  • 执行app後可以看到onStart()被呼叫以後,Timer就开始执行

  • 按分享键时,会使activity进入onPause(),但并未onStop()
    所以Timer()仍会继续

  • 按Home键到桌面,此时app进入onStop(),再使用最近任务将app叫回
    又会进入onStart(),Timer也继续执行

  • 若使用Back键退出,或完整的结束,app会进入onDestory()
    再次执行app,Timer()将会重新计数

  • 若将onStop()中的stopTimer()注解掉,且未完整结束/退出app
    则app即使未显示於萤幕,Timer()也会持续在後台执行与使用系统资源
    这是一种memory leak

  • 若dessertTimer.startTimer()置於onCreate()执行
    则app从後台回到前台时,Timer()不会执行,因爲onCreate()在 app被onDestroy()只会执行一次

  • 本段的重点爲:若在生命周期中设置了某些可能会一直使用资源的程序
    同时也要注意什麽时候该设置结束的程序

lifecycle library

当一个app中有许多类似Timer()这样的程序须要注意何时结束时
可以使用lifecycle library
如上一段的例子,通常是activity在各个生命周期发生时,设置程序该做什麽,如startTimer(),stopTimer()

但使用lifecycle library则反过来,让Timer()自我监控当生命周期改变时,该做什麽

lifecycle library主要有三个部分

  • Lifecycle owners:(持有生命周期的组件)的拥有者,如activity,fragment,实作LifecycleOwner interface
  • Lifecycle class:持有Lifecycle owners的状态,并在生命周期发生变化时触发事件
  • Lifecycle observers:观察生命周期的变化并执行对应动作,实作LifecycleObserver interface

啓用lifecycle library

Lifecycle observer

在此范例app中,DessertTimer就是Lifecycle observers
因此,让DessertTimer实作LifecycleObserver interface
得以观察activity的生命周期而自动进行对应动作

class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {
...
}

改写的class DessertTimer包含二件事情

  • 建构子lifecycle实例Lifecycle物件,爲Timer要观察的对象
  • 实作LifecycleObserver interface

再用lifecycle呼叫addObserver()将DessertTimer这个class加到观察者,并初始化

实例化DessertTimer的时候,Lifecycle owner(MainActivity)将传入本身的lifecycle
所以这个动作就连结了MainActivity与DessertTimer

init {
   lifecycle.addObserver(this)
}

接着在对应生命周期发生时,所要执行的方法前加上annotation
@OnLifecycleEvent
并将对应的生命周期事件作爲参数传入
如图,属於activity的生命周期皆可以被观察

因此我们要在activity的生命周期

  • 在onStart()时执行startTimer()
  • 在onStop()时执行时投票TImer()
    就加上annotation
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {...}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer() {...}

Lifecycle owner , Lifecycle class

MainActivity就是本app的Lifecycle owner
而MainActivity继承自上层的FragmentActivity已实作了LifecycleOwner

再来是将MainActivity自身的lifecycle状态(Lifecycle class)

传给DessertTimer的建构子

dessertTimer = DessertTimer(this.lifecycle) //this = 当前的MainActivity

至此就完成了DessertTimer对MainActivity的观察
当MainActivity的生命周期改变时
DessertTimer即会依照设置的annotation,执行对应的方法

所以原先MainActivity onStart(),onStop()中执行的startTimer()与stopTimer()都不需要了

执行app,检查timer print log的动作应皆相同

app shutdown

app不在当前画面,刚进入後台时,还不会呼叫onDestroy()
等待使用者随时可快速的返回app

但android管理所有资源,可能因爲app久未被使用或资源不够
而将已进入後台的app完全关闭

以下用adb模拟android关闭app的情况(android api版本须爲28以上)

Android Debug Bridge (adb)

开啓Terminal,输入adb,正常要看到Android Debug Bridge version X.XX.X
出现如图这样表示没找到adb执行程序

参考官方解决方式:Windows

找自己的Android SDK Location爲 C:\Users\user\AppData\Local\Android\Sdk\

但实际上adb.exe在platform-tools资料夹内

所以路径是
C:\Users\user\AppData\Local\Android\Sdk\platform-tools

再依照第二步,将SDK路径加入系统变数中

进阶系统设定->环境变数->系统变数

执行adb,看到版本号了

执行app并累积一些金额

按Home,将app收到後台

开啓Terminal,输入adb shell am kill com.example.android.dessertclicker
此指令功能爲将後台的app强制结束

再将app调回前台显示,原本从後台回到前台
预期app的内容应该是与进後台前相同

但被强制结束的app,可以回到前台之後,内容都被清空了
app的生命周期也从onCreate()开始执行,timer从0开始

android系统会自动保存已设置ID之view的状态
尽量於app恢复执行时,显示之前的状态
但有些程序中的变数,android系统不认得
因此若须要保存的话,可用onSaveInstanceState()包装起来


<<:  远端系列 - 1:什麽是本地数据库(local repository)、远端数据库(remote repository)?

>>:  Day30

Day 6:监控系统的设计

昨天稍微谈到了一些有关警报的设计,然而,警报的发出与否,应是建立在我们观测到的一些系统的行为,例如说...

Trouble with Distributed Systems (4-2) - System Model & Summary

续 Day 12 今天的特别理论和抽象,所以懒得看就跳过吧! 系统模型和现实 (System Mo...

【从实作学习ASP.NET Core】Day08 | 後台 | 新增商品类别

今天来处理昨天建立的 Category 商品类别模型 假如说商店有机会扩大经营的话,那商品类别势必会...

【Day14】变数的地盘—作用域(scoop)与提升(Hoisting)

作用域(scoop)简单来说,就是变数的地盘,在地盘内,变数都有作用,出了地盘,变数就undefi...

Leetcode: 630. Course Schedule III | 含C++笔记

思路: 我一开始看到这题,感觉他像可以用Greedy解法解的问题,然後又想他是III,所以他也可以用...