美文网首页
《Android编程权威指南》之Activity的生命周期篇

《Android编程权威指南》之Activity的生命周期篇

作者: 夜远曦白 | 来源:发表于2021-04-11 17:09 被阅读0次

    本章讲述 Activity 生命周期、状态以及状态切换时系统调用的方法。

    activity状态图解
    activity的状态( 某些场景下,暂停状态的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 键日志:

    home

    重新进入 app 日志:

    resume

    旋转 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生命周期

    深入学习:activity 内存清理现状

    低内存状态下,Android直接从内存清除整个应用进程,连带应用的所有activity。目前,Android还做不到只销毁单个activity。

    这里还介绍了使用Android手机中开发者设置,启用 Don’t keep activities

    单击后退键后,系统总是会销毁当前的activity,相当于告诉系统“用户不再需要使用当前的activity”。

    深入学习:日志记录的级别与方法

    当然,打印日志也是有级别的,通常打错误日志才用 Log.e,默认是红色,打出来很显眼,可是平常一些信息什么的,最好不要打到这个级别了,很影响排除错误。

    日志级别

    关于日志打印:https://www.jianshu.com/p/de79bbf35a5b

    挑战练习:禁止一题多答

    1. 定义问题是否已经回答过问题的 boolean 类型的数组
    private var mQuestionsAnswered: BooleanArray? = BooleanArray(questionBank.size)
    
    1. 写个方法专门用来设置答题按钮状态
    private fun setBtnEnabled(enabled: Boolean) {
            trueButton.isEnabled = enabled
            falseButton.isEnabled = enabled
        }
    
    1. 每一次检查问题答案的时候,立即将答题按钮状态置为 false,并将是否回答过问题的 boolean 数组当前位置的值设置为 true,因此在 checkAnswer 方法里面加上两句代码
    private fun checkAnswer(userAnswer: Boolean) {
            ...
           setBtnEnabled(false)
           mQuestionsAnswered?.set(currentIndex, true)
        }
    
    1. 每一次翻页都要更新当前问题是否回答过的按钮状态,所以updateQuestion() 方法中添加代码
    private fun updateQuestion() {
            ...
            setBtnEnabled(!mQuestionsAnswered?.get(currentIndex)!!)
        }
    
    1. 为了解决旋转问题,所以是否回答过问题的数组也要保持下来,定义一个KEY,再在 onSaveInstanceState() 保存数组
    private const val KEY_QUESTION_ANSWERED = "answered"
    
    override fun onSaveInstanceState(savedInstanceState: Bundle) {
            ...
            savedInstanceState.putBooleanArray(KEY_QUESTION_ANSWERED, mQuestionsAnswered)
    }
    
    1. 最后当然也要在onCreate方法中得到刚刚保存的是否回答过问题的数组,解决旋转初始化值的问题
    if (savedInstanceState != null) {
        currentIndex = savedInstanceState.getInt(KEY_INDEX, 0)
        mQuestionsAnswered = savedInstanceState.getBooleanArray(KEY_QUESTION_ANSWERED)
    }
    

    挑战练习:评分 (用户答完全部题后,显示一个toast消息,给出百分比形式的评分)

    1. 定义一个 Int 类型的数,记录回答正确答案的个数,初始化为 0
     private var mTrueAnswerCount = 0
    
    1. 每次点击了回答问题的按钮,检测答案的时候,检查正确了,就将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()
        }
    
    1. 写个得到评分的方法,一直在想,什么时候会答完题,因为可以跳着答题的嘛,恰好答完所有就跳出提示,所以我的处理是在 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()
            }
        }
    
    1. 最后一个旋转问题,当然又是定义一个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!完毕!ヾ(◍°∇°◍)ノ゙

    相关文章

      网友评论

          本文标题:《Android编程权威指南》之Activity的生命周期篇

          本文链接:https://www.haomeiwen.com/subject/yzfikltx.html