Android 複習筆記目錄
嘮嘮任務棧,返回棧和生命週期
嘮嘮 Activity 的生命週期
上一篇文章嘮了嘮 任務棧,返回棧和啓動模式,今天來聊一聊同樣和 Activity 息息相關的 生命週期 。
關於 Activity 的生命週期,我相信大家倒着都可以說出來了。這裏放一張 android-lifecycle 裏的經典大圖,其中也包含了 Fragment 的生命週期圖。
由於這是一張 2014 年的圖,注意 onRestoreInstanceState()
和 onSaveInstanceState()
的調用時機會因爲 SDK 版本的不同稍有變化,文章後面也會提到。
目錄
-
每個生命週期做了什麼? -
onStart/onStop ?還是 onResume/onPause ? -
如何進行 UI 狀態的存儲與恢復? -
Activity 和應用進程的關係 -
在什麼時機觸發 LeakCanary 的檢測? -
被 SharedPreference 拖累的 Activity
每個生命週期做了什麼?
onCreate()
這是 Activity 的第一個生命週期方法,其中必須要做的操作就是 setContentView()
。
setContentView()
裏面大概做了這麼幾件事:
-
創建 DecorView,並設置 PhoneWindow -
解析 xml 佈局文件,生成 View 對象並塞到 DecorView 中
此時 DecorView 並沒有被繪製,Window 對象也沒有被顯示到屏幕,Activity 也是不可見的。
除此之外,開發者通常也會在 onCreate()
方法中做一些數據的初始化操作。
onCreate()
在一次完整的生命週期中只會回調一次,它也不是一個長駐狀態,完成工作只會就會進入 onStart()
。
onStart()
onStart()
也不是一個長駐狀態,官方文檔對於它的描述是這樣的:
The onStart() call makes the activity visible to the user, as the app prepares for the activity to enter the foreground and become interactive.
在 onStart()
方法中,Activity 對用戶可見,應用準備進入前臺和用戶交互。我對這句 Activity 對用戶可見 其實抱有很大的疑問。
不考慮特殊情況,正常啓動一個 Activity,onCreate -> onStart ,此時所謂的 “可見”,見到的是什麼?
這個問題在下面的 onResume
一節中會詳細說明,讀者可以先仔細揣摩一下。
onStart()
方法中可以做些什麼呢?通常會和 onStop()
搭配做一些資源申請和釋放的工作,例如相機的申請和釋放。
onResume
熟悉 UI 繪製流程的讀者肯定知道,onResume()
是真正進行 UI 繪製以及顯示的地方。其中的核心邏輯就是 WindowManager.addView()
方法,實際調用的是 WindowManagerGlobal.addView()
方法。它大致幹了這麼幾件事:
-
創建 ViewRootImpl 對象 -
調用 ViewRootImpl.setView() 方法
ViewRootImpl
的構造函數中做了這麼幾件事:
-
初始化跟 WMS 通信的 WIndowSession 對象,這是一個 Binder 對象 -
初始化 Choreographer 對象
ViewRootImpl.setView()
方法做了這麼幾件事:
-
調用 requestLayout() 方法,發起繪製 -
Binder 調用 WMS.addToDisplay() 方法,將 window 添加到屏幕
requestLayout()
方法中就會進行我們所熟知的 測量、佈局和繪製 流程,但並不是直接進行的,它依賴 vsync 信號。requestLayout()
只是通過前面已經初始化了的 Choreographer 對象進行註冊監聽,當下一個 vsync 信號來臨時,會回調 performTraversals()
方法,這其中就會真正的進行測量、佈局和繪製。
整個 UI 繪製流程的知識點很多,僅靠以上簡單一段文字肯定是無法完全概括的,感興趣的讀者可以自己去翻翻源碼。但是我們可以肯定是,onResume
是真正的用戶界面可見的時機。
再回到之前的問題,onStart
中可見的是什麼?我也無法回答這個問題,或者可能大家都曲解了官方文檔的意思,是否應該理解爲 “Activity 即將可見”。大家可以在留言區說說你的看法。
同樣,onResume()
通常也可以和 onPause()
搭配做一些資源申請和釋放的工作。那麼,既然 onStart/onStop
和 onResume/onPause
都可以,該如何選擇呢?同樣放到後面進行解答。
onPause
onPause()
是一個很短暫的過程,之後如果用戶返回了之前的 Activity,則會回調 onResume
。如果沒有,則會回調 onStop
。
給 onPause
一個精準的描述的話,應該是 非前臺,不可交互,但不一定不可見 。對於系統來說,無論是手機還是 PC ,同一個時間一定只有一個處於前臺,獲取焦點,且可與用戶交互的活動窗口,所以 非前臺,不可交互 很好理解。那 不一定不可見 如何理解呢?其實也很簡單,類似 PC 的多窗口,Android 系統也是有多窗口模式的。
最後,注意 onPause
中不建議進行重量級的耗時操作,因爲在 Activity 跳轉過程中,前一個 Activity 的 onPause()
是發生在後一個 Activity 的任何生命週期之前的。
onStop
完全的不可見狀態。但此時的 Activity 仍在內存中,只是沒有關聯到任何 Window 。如果後續有機會再次返回,則會回調 onRestart -> onStart
。
onDestroy
onStop
之後沒有被用戶撈回去,最後就得被銷燬。主動的調用 finish
或者系統配置改變也會可能導致銷燬。
注意在 onDestroy
中釋放所有不需要的資源,否則可能導致內存泄露。
到目前爲止,簡單介紹了各個生命週期的回調時機和應該處理的事情,同時也帶了一些疑問。下面來說說一些知識點。
onStart/onStop ?還是 onResume/onPause ?
在使用 EventBus 的時候,需要在相應的聲明週期中進行註冊和解註冊的操作,如下所示:
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
當然,在 onResume/onPause
中一般也是沒有問題的。
@Override
public void onResume() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onPause() {
super.onStop();
EventBus.getDefault().unregister(this);
}
但它們之間並不是完全沒有區別的。通常情況下,onPause
之後很快就會 onStop
,但是考慮到 Android 7.0 之後新增的 多窗口模式 的話,Activity 可能會停留在 onPause
一段時間。這種情況下,如果在 onStop
中進行資源釋放操作的話,可能並不能及時釋放。如果你的 Activity 持有的是相機等系統資源,會導致其他應用無法使用該資源,對用戶來說無疑是很不友好的。所以,在進行類似操作的時候要考慮一下應用場景。onResume/onPause
關注的是 Activity 是否可以交互,onStart/onStop
關注的是 Activity 是否可見。
最後得說下,上面的代碼中把生命週期處理和視圖控制器耦合在一起,並不是那麼優雅,可以通過 LifeCycle 組件進行解耦。拿我之前文章中的代碼說明一下:
class LocationUtil( ) : LifeCycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun startLocation( ){
......
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun stopLocation( ){
......
}
}
傳送門:
硬核講解 Jetpack 之 LifeCycle 使用篇
硬核講解 Jetpack 之 LifeCycle 源碼篇
如何進行 UI 狀態的存儲與恢復?
除了正常狀態下的數據持久化存儲,異常情況下的數據保存和恢復也是必要的。這裏的異常情況一般指系統配置變化,典型的橫豎屏切換,系統語言切換等。
異常情況下終止的 Activity,系統會調用 onSaveInstanceState()
方法來保存當前 Activity 的狀態。那麼哪些狀態默認會被保存呢?我們可以看一下 TextView
的 onSaveInstanceState()
方法。
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
......
if (freezesText || hasSelection) {
SavedState ss = new SavedState(superState);
if (freezesText) {
if (mText instanceof Spanned) {
final Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
// 保存文字
ss.text = sp;
} else {
ss.text = mText.toString();
}
}
...
return ss;
}
return superState;
}
可以看到文字是保存了的,這裏刪減了很多代碼,其實還保存了其他一些狀態。只要是實現了 onSaveInstanceState()
方法的 View,都會被保存下來。當 Activity 重新創建時,會通過 onCreate()
或者 onRestoreInstanceState()
方法恢復狀態。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
}
在 Kotlin 中重寫這兩個方法也可以看到,onCreate()
方法的參數是可空的,因爲會有正常啓動的情況。所以一般建議直接在 onRestoreInstanceState
方法中進行狀態恢復即可。
對於非 UI 狀態的其他數據,就得自己手動進行保存和恢復了。這裏直接拿官方文檔的示例代碼:
override fun onSaveInstanceState(outState: Bundle?) {
// Save the user's current game state
outState?.run {
putInt(STATE_SCORE, currentScore)
putInt(STATE_LEVEL, currentLevel)
}
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(outState)
}qubie
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState)
// Restore state members from saved instance
savedInstanceState?.run {
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
}
要注意的是,通過 onSaveInstanceState
的方式存儲數據,由於是發生在主線程,且存在序列化/反序列化的開銷,並不建議存儲大量數據。其實更好的做法是使用 ViewModel
,它可以在系統配置變化發生的 Activity 重建過程中來保存數據。
最後來說一下 onSaveInstanceState
的調用時機問題。在不同的 SDK 版本中,這個時機是不唯一的。
-
SDK 11 之前,在 onPause()
之前調用 -
SDK 28 之前,會在 onStop()
之前調用 -
SDK 28 之後,會在 onStop
之後調用
當然,這對我們來說並沒有什麼實質的區別。
Activity 和應用進程的關係
當系統內存不足時,會存在單個 Activity 直接被系統回收的情況嗎?
答案是否定的。
首先應用進程的生存時間並不是由自己直接控制的,而是由系統決定的。每一個 App 都至少對應着一個 Linux 進程。當系統內存不足無法滿足正在與用戶交互的進程的需求時,可能會回收一些資源。這些資源是一個一個進程,而不是進程裏的一個一個組件,不會存在單個 Activity 被系統回收的情況。
那麼,既然要回收進程,那麼肯定會給進程分個三六五等。按照官方文檔的描述,大致有這麼幾類,重要性依次降低。
Foreground Process :
-
有 Activity 處於前臺,正在和用戶交互 -
BroadcastReceiver 的 onReceive 方法正在執行 -
Service 正在運行代碼,包括 onCreate,onStart,onDestroy
Visible Process :
-
有 Activity 可見,但不在前臺 -
Service 正在運行前臺服務 -
持有一些用戶可以感知的特定服務,如動態壁紙,輸入法服務
Service Process :
有正在運行的 Service ,對用戶不可見,但正在進行一些用戶關心的工作,例如後臺下載等。當系統內存不足的時候,可能會被幹掉。
官方文章說運行超過 30 min 可能會被降級,但這個在不同的國產 ROM 肯定是有魔改的,具體應該以實際測試爲準。
Cached Process :
當系統內存不足時可以隨時自由終止的無用線程。但是爲了更高效的切換應用,系統一般不會把它們全部 kill 掉。
在什麼時機觸發 LeakCanary 的檢測?
之前羣裏的小夥伴 codelang 分享了一個有趣的知識點:
爲什麼 LeakCanary 要在 Activity onDestroy 之後調用 RefWatcher.watch() 開始監測內存泄露?
他給出的答案是:
從 ActivityThread 的角度看,Activity 就是一個對象,按照 GC Root,Activity 是被 ActivityThread 給引用着的。爲什麼要在 onDestroy 之後開始檢測,因爲這個時候 Activity 和 ActivityThread 的引用斷開了,在 ActivityThread.performDestroyActivity() 中從 mActivities 中移除了當前 Activity 對象。
的確,在 onDestroy()
之前,Activity 根本沒有斷開和 GC Roots 的引用,檢測個啥呢。
被 SharedPreference 拖累的 Activity
之前寫過一篇 細數 SharedPreference 的槽點 來吐槽 SP 。不合理的使用 SP 可能會導致卡頓,甚至 ANR 。
apply()
方法和 commit()
方法不同,它是通過異步任務來進行存儲操作的,通過 QueuedWork
類實現。
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
而在 onStop
中,需要等待這個異步任務的完成。看一下 ActivityThread.java
中 handleStopActivity()
方法:
@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
......
// 可能因等待寫入造成卡頓甚至 ANR
if (!r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
......
}
無論修改了什麼數據,這裏的存儲都是全量寫入,如果數據量過大,一定是會存在性能問題的。此外還要注意 SP 的舒適化過程也是全量讀取放到內存中,所以在數據量大的情況下,注意提前初始化。
最後
關於 Activity 的生命週期就說這麼多了,後面如果碰到相關的有意思的問題,再回來補充。
都看到這了,不妨關注我一下唄!
本文分享自微信公衆號 - 秉心說TM(gh_c6504b1af5ae)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。