《Android編程權威指南》之Activity的生命周期篇

夜遠曦白 2021-08-15 18:16:47 阅读数:802

本文一共[544]字,预计阅读时长:1分钟~
android 指南 activity 生命周期 生命

這是我參與8月更文挑戰的第6天,活動詳情查看:8月更文挑戰

本章講述 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的配置修飾符列錶及其代錶的設備配置信息網址:developer.android.com/guide/topic…

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,默認是紅色,打出來很顯眼,可是平常一些信息什麼的,最好不要打到這個級別了,很影響排除錯誤。

日志級別

關於日志打印:www.jianshu.com/p/de79bbf35…

挑戰練習:禁止一題多答

  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!完畢!ヾ(◍°∇°◍)ノ゙

版权声明:本文为[夜遠曦白]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815181419021a.html