嘮嘮 Activity 的生命週期

Android 複習筆記目錄

  1. 嘮嘮任務棧,返回棧和生命週期

  2. 嘮嘮 Activity 的生命週期

上一篇文章嘮了嘮 任務棧,返回棧和啓動模式,今天來聊一聊同樣和 Activity 息息相關的 生命週期

關於 Activity 的生命週期,我相信大家倒着都可以說出來了。這裏放一張 android-lifecycle 裏的經典大圖,其中也包含了 Fragment 的生命週期圖。

由於這是一張 2014 年的圖,注意 onRestoreInstanceState()onSaveInstanceState() 的調用時機會因爲 SDK 版本的不同稍有變化,文章後面也會提到。

目錄

  • 每個生命週期做了什麼?
  • onStart/onStop ?還是 onResume/onPause ?
  • 如何進行 UI 狀態的存儲與恢復?
  • Activity 和應用進程的關係
  • 在什麼時機觸發 LeakCanary 的檢測?
  • 被 SharedPreference 拖累的 Activity

每個生命週期做了什麼?

onCreate()

這是 Activity 的第一個生命週期方法,其中必須要做的操作就是 setContentView()

setContentView() 裏面大概做了這麼幾件事:

  1. 創建 DecorView,並設置 PhoneWindow
  2. 解析 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() 方法。它大致幹了這麼幾件事:

  1. 創建 ViewRootImpl 對象
  2. 調用 ViewRootImpl.setView() 方法

ViewRootImpl 的構造函數中做了這麼幾件事:

  1. 初始化跟 WMS 通信的 WIndowSession 對象,這是一個 Binder 對象
  2. 初始化 Choreographer 對象

ViewRootImpl.setView() 方法做了這麼幾件事:

  1. 調用 requestLayout() 方法,發起繪製
  2. Binder 調用 WMS.addToDisplay() 方法,將 window 添加到屏幕

requestLayout() 方法中就會進行我們所熟知的 測量、佈局和繪製 流程,但並不是直接進行的,它依賴 vsync 信號。requestLayout() 只是通過前面已經初始化了的 Choreographer 對象進行註冊監聽,當下一個 vsync 信號來臨時,會回調 performTraversals() 方法,這其中就會真正的進行測量、佈局和繪製。

整個 UI 繪製流程的知識點很多,僅靠以上簡單一段文字肯定是無法完全概括的,感興趣的讀者可以自己去翻翻源碼。但是我們可以肯定是,onResume 是真正的用戶界面可見的時機。

再回到之前的問題,onStart 中可見的是什麼?我也無法回答這個問題,或者可能大家都曲解了官方文檔的意思,是否應該理解爲 “Activity 即將可見”。大家可以在留言區說說你的看法。

同樣,onResume() 通常也可以和 onPause() 搭配做一些資源申請和釋放的工作。那麼,既然 onStart/onStoponResume/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 的狀態。那麼哪些狀態默認會被保存呢?我們可以看一下 TextViewonSaveInstanceState() 方法。

@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.javahandleStopActivity() 方法:

@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源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章