Android系統編程入門系列之加載界面Activity
- 2021 年 7 月 2 日
- 筆記
- Android_Home
上回說到應用初始化加載及其生命周期,在Android系統調用Applicaiton.onCreate()
之後,繼續創建並加載清單文件中註冊的首個界面即主Activity
,也可稱之為入口界面。主Activity
的確定規則在Android系統編程入門系列之清單文件有介紹,本文主要介紹Android系統創建Activity
之後的生命周期流程。
在清單文件中所註冊的界面均為自定義Activity
,其父類往上追溯,必須繼承自android.content.Activity。
生命周期
Activity
作為四大組件之首,主要負責與系統使用者的可視化交互響應。只有深刻掌握Activity
的生命周期及相關概念,才能在開發設計時遊刃有餘。注意,這裡的生命周期介紹,與官方生命周期定義有所區別,本文中的範圍更加寬泛。
Android組件的生命周期,均是由Android系統主線程調用,如果在調用的生命周期方法內出現耗時操作,將會導致後續的生命周期方法無法被及時調用,反應到交互界面上就是應用程序卡頓甚至操作無響應。為了防止這種情況的發生,Android系統定義當應用程序超時無響應時間超過一定時長(界面Activity
默認5秒),會觸發應用無響應錯誤ANR,同時界面彈出提示對話框,以供用戶選擇退出停止響應或繼續等待。所以在生命周期方法內不允許耗時操作。
(調用構造方法)啟動實例化
界面Activity
的啟動需要通過android.content.Intent意圖來操作,意圖的創建分顯示意圖和隱示意圖兩種類型。顧名思義,顯示意圖要指定Activity
的具體包名和類名,而隱示意圖只需要指定Activity
在清單文件中註冊時所嵌入的action和category標籤信息。創建的意圖作為參數才能啟動界面Activity
。顯示意圖常用於當前應用內的界面Activity
之間啟動,而隱示意圖多用於不同應用間的界面啟動。
回想下,在Android系統編程入門系列之清單文件文章中說到,主Activity
的註冊時,必須在其標籤內部嵌入<intent-filter></intent-filter>
標籤,並在該標籤內固定且唯一<action>
和<category>
標籤的內容。這種寫法的原因,就在於Android系統是創建的隱示意圖啟動應用內的主Activity
。舉一反三,留個問題思考下,如果一個應用的清單文件中有多個上述主Activity
的固定<intent-filter></intent-filter>
標籤內容,那Android系統在啟動這個應用後會怎麼調用這些Activity
呢?(答案將在相應視頻教程中揭曉)
在Android系統通過清單文件找到意圖指定的界面Activity
之後,會實例化該界面Activity
對象,並將其放入當前應用程序的指定任務棧中。任務棧,正如其名,是採用先進後出的模式管理當前應用程序啟動的所有界面Activity
對象的數據結構。
當界面Activity
啟動時放入任務棧中,退出界面Activity
時則從任務棧中取出其對象並銷毀。然而如果一個應用程序只對應一個任務棧,在有些頻繁啟動退出單個界面的場景中,就會頻繁創建銷毀該界面實例,浪費了很多cpu耗時。為了優化界面的實例化過程,Android系統允許一個應用程序使用多個任務棧。這也就引出了兩個問題,一是如何從多個任務棧中指定具體一個作為界面啟動管理的任務棧?二是界面在啟動時放入不同任務棧的規則是什麼?
對於第一個問題,在清單文件中靜態註冊界面Activity
時,可以在<activity></activity>
標籤中對屬性名taskAffinity賦值,該屬性值默認為當前應用包名,從而指定當前界面Activity
所屬的任務棧。而<application></application>
標籤中也可以使用該屬性,表示當前應用程序內的所有界面啟動時都使用該屬性值對應的任務棧管理。同時在界面Activity
中,可以使用getTaskId()
來獲取當前界面Activity
所屬任務棧對應的id值。
對於第二個問題,可以根據界面Activity
的啟動模式來確定放入任務棧的規則。啟動模式可以在清單文件中靜態註冊<activity></activity>
標籤時,指定其屬性launchMode賦值;也可以在創建意圖Intent
操作之後,通過調用其setFlags(int flags)
方法動態設置。如果動態設置方式會優先覆蓋靜態設置方式,如果不設置啟動模式,則使用默認模式啟動。啟動模式主要有四種,其具體方式和對應的屬性值可參考下表。
啟動模式 | 靜態設置 | 動態設置 | 功能含義 |
---|---|---|---|
標準模式(默認模式) | standard | – | 啟動時會在內存空間中實例化對象,並將該對象放入指定任務棧中 |
棧頂單例模式 | singleTop | Intent.FLAG_ACTIVITY_SINGLE_TOP | 啟動時先檢查指定任務棧頂部界面對象, 如果與該界面信息一致,則使用任務棧頂層的對象; 否則實例化對象並放入指定任務棧頂部 |
棧內單例模式 | singleTask | Intent.FLAG_ACTIVITY_CLEAR_TOP | 啟動時檢查指定任務棧內的所有界面對象, 如果存在與該界面信息一致的對象,則將其頂部界面對象移出任務棧並銷毀,最終保留一致對象在任務棧頂部; 否則實例化對象並放入指定任務棧頂部 |
全局單例模式 | singleInstance | Intent.FLAG_ACTIVITY_NEW_TASK | 啟動時檢查當前應用程序使用的所有任務棧內的所有界面對象, 如果存在與該界面信息一致的對象,則將其所在任務棧頂部的界面對象移出任務棧並銷毀,最終保留一致對象在任務棧頂部; 否則實例化對象並放入指定任務棧頂部 |
在界面`Activity`啟動實例化階段,只是創建實例,並未加載Android系統相關環境,故該階段一般不需要重寫構造方法。
(調用attachBaseContext(Context base)
)加載運行環境
與Application
一樣,Activity
也繼承自android.content.ContentWrapper
,使用時綁定上下文環境,尤為注意的是,在AndroidSDK定義的所有android.content.ContentWrapper
子類中,只有android.content.Activity
類中重寫了attachBaseContext(Context base)
方法。故界面Activity
實例化之後,系統會最終調用android.content.Activity.attachBaseContext(Context base)
方法,在該方法中以完成將當前界面與上下文環境綁定的任務。
同樣的,在界面Activity
加載運行環境階段,也不推薦重寫該方法,界面內所有的操作都應在該方法之後完成。
(調用onCreate()
)界面創建
在界面加載運行環境之後,Android系統對界面的創建工作就完成了。之後系統回調onCreate()
方法,官方稱之為已創建狀態。此時當前Activity
還處在後台未展示給用戶,因此可以重寫該方法,以完成界面內相關變量的初始化操作。
(調用onStart()
)界面展示
在界面創建之後,或界面重新展示之後,系統會回調onStart()
方法,將當前Activity
繪製給用戶可見,官方稱之為已開始狀態。如果重寫該方法,可以完成展示給用戶的界面初始化操作。
(調用onResume()
)界面運行
當界面展示之後,系統會回調onResume()
方法,使得界面可以與用戶進行交互,官方稱之為已恢復狀態。此時系統開始響應該界面內的用戶交互,這也表明當前界面Activity
已經處於運行了。
系統調用該生命周期方法後會一直處於界面運行中,直到用戶的一些操作改變該界面的狀態。這些用戶操作包括但不限於:
- 在該界面
Activity
啟動另一個界面Activity
。 - 點擊back返回按鍵,退出該界面
Activity
,返回上一個界面Activity
。 - 點擊home主頁按鍵,該應用
Application
切換到後台。 - 點擊menu菜單按鍵,該應用的當前界面
Activity
在最近任務欄展示。 - 點擊分屏按鍵,該應用
Application
作為多窗口模式之一顯示。 - 點擊息屏按鍵,該應用
Application
在後台休眠。 - 點擊電源按鍵,強制關機,該應用
Application
被殺死。
界面暫停(調用onPause()
)
當界面運行狀態被改變後,系統都會首先調用onPause()
方法,此時系統已停止當前界面Activity
與用戶的交互響應操作,官方稱之為已暫停狀態。可以重寫該方法,在其中做釋放一些不需要但卻很耗電的系統資源,以防止無效持有消耗電量。但是此時該界面仍然對用戶可見,所以不建議在該方法內執行耗時的釋放操作,以免給用戶帶來卡頓的假象。
在該狀態之後,Android系統根據之前的用戶操作類型判斷後續生命周期流程。
操作1、2、3、6、7可能會繼續令當前界面Activity
停止展示。
而操作4、5將可能在用戶重新點擊該界面返回,此時將重新回到界面運行的生命周期中。
界面停止展示(調用onStop()
)
當系統令當前界面Activiy
停止展示時,會調用onStop()
方法,將當前界面對用戶不可見,官方稱之為已停止狀態。重寫該方法時,可以釋放界面資源和不需要的CPU耗時資源,以供其他地方及時獲得。
在該狀態之後,Android系統繼續根據之前的用戶操作類型判斷後續的生命周期流程。
操作1會將新啟動的界面Activity
放入當前Activity
所在任務棧中,等待新啟動的界面返回時,令當前界面重新展示。
操作2則是將當前界面從任務棧中取出並銷毀。
操作3則可能在手機內存不足時,將當前應用的所有界面包括當前界面從任務棧中取出並銷毀。
操作6會將當前應用休眠,等設備重新亮屏後,令當前界面重新展示;也可能有的設備在息屏休眠後保持低消耗電源模式時,當前應用超過一定時間或內存佔用的休眠期後,被系統殺死,即當前應用的所有界面包括當前界面從任務棧匯總取出並銷毀。
操作7則會將當前應用的所有界面全部銷毀,不會再調用當前界面的任何生命周期。
界面重新展示(調用onRestart()
)
當界面由用戶不可見轉為可見時,如果之前已經界面創建了,則會調用onRestart()
方法,以表明當前界面將會重新展示。重寫該方法可以處理對用戶重新可見後的操作。
該方法調用之後,系統將會繼續調用界面展示和運行,以重新運行界面與用戶的交互操作。
界面銷毀(調用onDestroy()
)
當界面Activity
被移除任務棧後,如果應用程序還處在Android系統的控制下,系統將在調用onDestroy()
方法之後銷毀界面,官方稱之為已銷毀狀態。重寫此方法可以釋放所有不需要的資源,以防止發生內存泄漏OOM的問題。
上述內容是針對單個界面Activity
的加載流程,那麼其中涉及到的兩個界面Activity
的相互交流方式是怎樣的,以及界面運行之後如何處理與用戶的交互操作?詳情請關注後續文章。