本章讲述 Activity 生命周期、状态以及状态切换时系统调用的方法。
- Nonexistent 表示 activity 不存在了,看不见了,它没有在内存里,或者已经被销毁了,也没有关联的视图供用户查看或与之交互。(发生在点击了后腿按钮)
- Stopped 表示 activity 在内存中具有实例,但其视图在屏幕上不可见。(发生在启动了另外的全屏 activity,或者点击了手机的主页按钮)
- Paused 表示 activity 在前台不能与用户交互但视图可见或部分可见。(比如说跳出一个对话框)
- Resumed 表示在内存中,完全可见且在前台的 activity。在任何给定时间,整个系统中只有一个活动可以处于 resumed 状态。 这意味着,如果一项活动进入 resumed 状态,则另一项 activity 可能会退出 resumed 状态。
Activity 类会提供许多回调,这些回调会让 Activity 知晓某个状态已经更改。
通常,通过覆盖 onCreate(Bundle) 方法,activity 可以预处理以下 UI 相关工作:
- 实例化组件并将它们放置在屏幕上(调用setContentView(int)方法);
- 引用已实例化的组件;
- 为组件设置监听器以处理用户交互;
- 访问外部模型数据。
日志跟踪理解 activity 生命周期
介绍的 android.util.Log 类打印日志,在上一章 MainActivity.kt 的上方加上日志 TAG 定义,然后,在 onCreate()、onStart()、onResume()、onPause()、onStop()、onDestroy() 生命周期回调方法中分别打印日志。
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
...
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart() called")
}
...
}
启动 app 日志:
点击 Home 键日志:
重新进入 app 日志:
旋转 app 日志:
退出 app 日志:
设备配置与 Activity 生命周期
旋转设备会改变设备配置(device configuration)。设备配置实际是指屏幕方向、屏幕像素密度、屏幕尺寸、键盘类型、语言等。
在运行时配置变更(runtime configuration change)发生时,可能会有更合适的资源来匹配新的设备配置。于是,Android销毁当前activity,为新配置寻找最佳资源,然后创建新实例使用这些资源。(在demo中,再创建了一个layout目录,并加了后缀-land,res/layout-land,于是结果是设备处于水平方向时,Android会找到并使用res/layout-land目录下的布局资源)
Android的配置修饰符列表及其代表的设备配置信息网址:http://developer.android.com/guide/topics/resources/providing-resources.html
UI 更新和多窗口模式
Android 7.0 之前,通常使用 onResume() 和 onPause() 来启动或者停止任何与 UI 相关的正在进行的更新(动画和刷新数据)。
Android 7.0 之后,有了多窗口模式,已经暂停的 activity 也是可见的状态,我们是希望已经暂停的 activitiy 也表现的像正常活动一样。
比如说看视频的时候,不过我们可以在将恢复播放和暂停的播放移至 onStart() 和 onStop() 中,这样就能满足需求了。
再探 activity 生命周期
protected void onSaveInstanceState(Bundle outState)【该方法通常在 onStop() 方法之前由系统调用,除非用户按后退键。(记住,按后退键就是告诉 Android,activity 用完了。随后,该 activity 就完全从内存中被抹掉,自然,也就没有必要为重建保存数据了。)】【 Bundle 是存储字符串键与限定类型值之间映射关系(键-值对)的一种结构】
所以,可通过覆盖 onSaveInstanceState(Bundle) 方法,将一些数据保存在 bundle 中,然后在 onCreate(Bundle) 方法中取回这些数据,解决旋转问题。
注意,在 Bundle 中存储和恢复的数据类型只能是基本类型(primitive type)以及可以实现 Serializable 或 Parcelable 接口的对象。在 Bundle 中保存定制类对象不是个好主意,因为你取回的对象可能已经没用了。比较好的做法是,通过其他方式保存定制类对象,而在Bundle中保存标识对象的基本类型数据。
深入学习:activity 内存清理现状
低内存状态下,Android直接从内存清除整个应用进程,连带应用的所有activity。目前,Android还做不到只销毁单个activity。
这里还介绍了使用Android手机中开发者设置,启用 Don’t keep activities
单击后退键后,系统总是会销毁当前的activity,相当于告诉系统“用户不再需要使用当前的activity”。
深入学习:日志记录的级别与方法
当然,打印日志也是有级别的,通常打错误日志才用 Log.e,默认是红色,打出来很显眼,可是平常一些信息什么的,最好不要打到这个级别了,很影响排除错误。
关于日志打印:https://www.jianshu.com/p/de79bbf35a5b
挑战练习:禁止一题多答
- 定义问题是否已经回答过问题的 boolean 类型的数组
private var mQuestionsAnswered: BooleanArray? = BooleanArray(questionBank.size)
- 写个方法专门用来设置答题按钮状态
private fun setBtnEnabled(enabled: Boolean) {
trueButton.isEnabled = enabled
falseButton.isEnabled = enabled
}
- 每一次检查问题答案的时候,立即将答题按钮状态置为 false,并将是否回答过问题的 boolean 数组当前位置的值设置为 true,因此在 checkAnswer 方法里面加上两句代码
private fun checkAnswer(userAnswer: Boolean) {
...
setBtnEnabled(false)
mQuestionsAnswered?.set(currentIndex, true)
}
- 每一次翻页都要更新当前问题是否回答过的按钮状态,所以updateQuestion() 方法中添加代码
private fun updateQuestion() {
...
setBtnEnabled(!mQuestionsAnswered?.get(currentIndex)!!)
}
- 为了解决旋转问题,所以是否回答过问题的数组也要保持下来,定义一个KEY,再在 onSaveInstanceState() 保存数组
private const val KEY_QUESTION_ANSWERED = "answered"
override fun onSaveInstanceState(savedInstanceState: Bundle) {
...
savedInstanceState.putBooleanArray(KEY_QUESTION_ANSWERED, mQuestionsAnswered)
}
- 最后当然也要在onCreate方法中得到刚刚保存的是否回答过问题的数组,解决旋转初始化值的问题
if (savedInstanceState != null) {
currentIndex = savedInstanceState.getInt(KEY_INDEX, 0)
mQuestionsAnswered = savedInstanceState.getBooleanArray(KEY_QUESTION_ANSWERED)
}
挑战练习:评分 (用户答完全部题后,显示一个toast消息,给出百分比形式的评分)
- 定义一个 Int 类型的数,记录回答正确答案的个数,初始化为 0
private var mTrueAnswerCount = 0
- 每次点击了回答问题的按钮,检测答案的时候,检查正确了,就将mTrueAnswerCount ++
private fun checkAnswer(userAnswer: Boolean) {
val correctAnswer = questionBank[currentIndex].answer
val messageResId = if (userAnswer == correctAnswer) {
mTrueAnswerCount++
R.string.correct_toast
} else {
R.string.incorrect_toast
}
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show()
setBtnEnabled(false)
mQuestionsAnswered?.set(currentIndex, true)
getScoreResult()
}
- 写个得到评分的方法,一直在想,什么时候会答完题,因为可以跳着答题的嘛,恰好答完所有就跳出提示,所以我的处理是在 checkAnswer()方法的最后,都会调用一下得到评分结果的方法,而在 getScoreResult() 方法里面判断一下当前是否答完了所有题,没有不作任何处理,答完了就做计算弹出当前评分的百分比
private fun getScoreResult() {
var isAllAnswered = true
for (i in questionBank.indices) {
if (!mQuestionsAnswered?.get(i)!!) {
isAllAnswered = false
return
}
}
if (isAllAnswered) {
Toast.makeText(
this,
"${mTrueAnswerCount * 100 / questionBank.size} %",
Toast.LENGTH_LONG
).show()
}
}
- 最后一个旋转问题,当然又是定义一个key,保存当前回答正确的问题数喽
private const val KEY_TRUE_ANSWER_COUNT = "true_answer_count"
mTrueAnswerCount = savedInstanceState.getInt(KEY_TRUE_ANSWER_COUNT)
savedInstanceState.putInt(KEY_TRUE_ANSWER_COUNT, mTrueAnswerCount)
OK!完毕!ヾ(◍°∇°◍)ノ゙