1. 程式人生 > 實用技巧 >Android全面解析之Activity生命週期

Android全面解析之Activity生命週期

前言

很高興遇見你~ 歡迎閱讀我的文章。

關於Activity生命週期的文章,網路上真的很多,有很多的部落格也都講得相當不錯,可見Activity的重要性是非常高的。事實上,我猜測每個android開發者接觸的第一個android元件都是Activity。我們從新建第一個Activity開始,運行了程式碼,看到模擬機上顯示了一個MainActivity標題和一行HolleWorld,從此開啟Android世界的大門。

本篇文章講解的重點是Activity的生命週期,在文章的最後也會涉及Activity的設計。不同於其他的部落格設計,文章採用系統化的講解,關於Activity生命週期的相關知識基本都會涉及到。

  • 文章第一部分講解關於Activity狀態的認知;
  • 第二部分全面講解activity生命週期回撥方法;
  • 第三部分是分析不同情景下的生命週期回撥順序:
  • 第四部分是原始碼分析;
  • 最後一部分是從更高的角度來思考activity以及生命週期。

那麼,我們開始吧。

生命狀態概述

Activity是一個很重要、很複雜的元件,他的啟動不像我們平時直接new一個物件就完事了,他需要經歷一系列的初始化。例如"剛建立狀態",“後臺狀態”,“可見狀態”等等。當我們在介面之間進行切換的時候,activity也會在多種狀態之間進行切換,例如可見或者不可見狀態、前臺或者後臺狀態。當Activity在不同的狀態之間切換時,會回撥不同的生命週期方法。我們可以重寫這一些方法,當進入不同的狀態的時候,執行對應的邏輯

在ActivityLifecycleItem`抽象類中定義了9種狀態。這個抽象類有很多的子類,是AMS管理Activity生命週期的事務類。(其實就像一個聖旨,AMS丟給應用程式,那麼應用程式就必須執行這個聖旨)Activity主要使用其中6個(這裡的6個是筆者在原始碼中明確看到呼叫setState來設定狀態,其他的三種並未看到呼叫setState方法來設定狀態,所以這裡主要講這6種),如下:

// Activity剛被建立時
public static final int ON_CREATE = 1;
// 執行完轉到前臺的最後準備工作
public static final int ON_START = 2;
// 執行完即將與使用者互動的最後準備工作
// 此時該activity位於前臺
public static final int ON_RESUME = 3;
// 使用者離開,activity進入後臺
public static final int ON_PAUSE = 4;
// activity不可見
public static final int ON_STOP = 5;
// 執行完被銷燬前最後的準備工作
public static final int ON_DESTROY = 6;

狀態之間的跳轉不是隨意的,例如不能從ON_CREATE直接跳轉到ON_PAUSE狀態,狀態之間的跳轉收到AMS的管理。當Activity在這些狀態之間切換的時候,就會回撥對應的生命週期。這裡的狀態看著很不好理解,筆者畫了個圖幫助理解一下:

這裡按照「可互動」「可見」「可存在」三個維度來區分Activity的生命狀態。可互動則為是否可以與使用者操作;可見則為是否顯示在螢幕上;可存在,則為該activity是否被系統殺死或者呼叫了finish方法。箭頭的上方為進入對應狀態會呼叫的方法。這裡就先不展開講每個狀態之間的切換,主要是讓讀者可以更好地理解activity的狀態與狀態切換。

注意,這裡使用的三個維度並不是非常嚴謹的,是結合總體的顯示規則來進行區分的。

在谷歌的官方文件中對於onStart方法是這樣描述的:onStart() 呼叫使 Activity 對使用者可見,因為應用會為 Activity 進入前臺並支援互動做準備。這也符合我們上面的維度的區分。而當activity進入ON_PAUSE狀態的時候,Activity是可能依舊可見的,但是不可互動。如操作另一個應用的懸浮視窗的時候,當前應用的activity會進入ON_PAUSE狀態。

但是!在activity啟動的流程中,直到onResume方法被呼叫,介面依舊是不可見的。這點在後面的原始碼分析再詳細解釋。所以這裡的狀態區分維度,僅僅只是總體上的一種區分,可以這麼認為,但細節上並不是非常嚴謹的。需要讀者注意一下。

生命週期的一個重要作用就是讓activity在不同狀態之間切換的時候,可以執行對應的邏輯。舉個栗子。我們在介面A使用了相機資源,當我們切換到下個介面B的時候,那麼介面A就必須釋放相機資源,這樣才不會導致介面B無法使用相機;而當我們切回介面A的時候,又希望介面A繼續保持擁有相機資源的狀態;那麼我們就需要在介面不可見的時候釋放相機資源,而在介面恢復的時候再次獲取相機資源。每個Activity一般情況下可以認為是一個介面或者說,一個螢幕。當我們在介面之間進行導航切換的時候,其實就是在切換Activity。當介面在不同狀態之間進行切換的時候,也就是Activity狀態的切換,就會回撥activity相關的方法。例如當介面不可見的時候會回撥onStop方法,恢復的時候會回撥onReStart方法等。

在合適的生命週期做合適的工作會讓app變得更加有魯棒性。避免當用戶跳轉別的app的時候發生崩潰、記憶體洩露、當用戶切回來的時候失去進度、當用戶旋轉螢幕的時候失去進度或者崩潰等等。這些都需要我們對生命週期有一定的認知,才能在具體的場景下做出最正確的選擇。

這一部分概述並沒有展開講生命週期,而是需要重點理解狀態與狀態之間的切換,生命週期的回撥就發生在不同的狀態之間的切換。我們學習生命週期的一個重要目的就是能夠在對應的業務場景下做合適的工作,例如資源的申請、釋放、儲存、恢復,讓app更加具有魯棒性。

重要生命週期解析

關於Activity重要的生命週期回撥方法谷歌官方有了一張非常重要的流程圖,可以說是人人皆知。我在這張圖上加上了一些常用的回撥方法。這些方法嚴格上並不算Activity的生命週期,因為並沒有涉及到狀態的切換,但卻在Activity的整個生命歷程中發揮了非常大的作用,也是很重要的回撥方法。方法很多,我們先看圖,再一個個地解釋。看具體方法解釋的時候一定要結合下面這張圖以及上一部分概述的圖一起理解。

主要生命週期

首先我們先看到最重要的七個生命週期,這七個生命週期是嚴格意義上的生命週期,他符合狀態切換這個關鍵定義。這部分內容建議結合概述部分的圖一起理解。(onRestart並不涉及狀態變換,但因為執行完他之後會馬上執行onStart,所以也放在一起講)

  • onCreate:當Activity建立例項完成,並呼叫attach方法賦值PhoneWindow、ContextImpl等屬性之後,呼叫此方法。該方法在整個Activity生命週期內只會呼叫一次。呼叫該方法後Activity進入ON_CREATE狀態。

    該方法是我們使用最頻繁的一個回撥方法。

    我們需要在這個方法中初始化基礎元件和檢視。如viewmodel,textview。同時必須在該方法中呼叫setContentView來給activity設定佈局。

    這個方法接收一個引數,該引數保留之前狀態的資料。如果是第一次啟動,則該引數為空。該引數來自onSaveInstanceState儲存的資料。只有當activity暫時銷燬並且預期一定會被重新建立的時候才會被回撥,如螢幕旋轉、後臺應用被銷燬等

  • onStart:當Activity準備進入前臺時會呼叫此方法。呼叫後Activity會進入ON_START狀態。

    要注意理解這裡的前臺的意思。雖然谷歌文件中表示呼叫該方法之後activity可見,如下圖:

    但是我們前面講到,前臺並不意味著Activity可見,只是表示activity處於活躍狀態。這也是谷歌文件裡讓我比較迷惑的地方之一。(事實上谷歌文件有挺多地方寫的缺乏嚴謹,可能考慮到易懂性,就犧牲了一點嚴謹性吧)。

    前臺activity一般只有一個,所以這也意味著其他的activity都進入後臺了。這裡的前後臺需要結合activity返回棧來理解,後續筆者再寫一篇關於返回棧的。

    這個方法一般用於從別的activity切回來本activity的時候呼叫。

  • onResume:當Activity準備與使用者互動的時候呼叫。呼叫之後Activity進入ON_RESUME狀態。

    注意,這個方法一直被認為是activity一定可見,且準備好與使用者互動的狀態。但事實並不一直是這樣。如果在onReume方法中彈出popupWindow你會收穫一個異常:token is null,表示介面尚沒有被新增到螢幕上。

    但是,這種情況只出現在第一次啟動activity的時候。當activity啟動後decorview就已經擁有token了,再次在onReume方法中彈出popupWindow就不會出現問題了。

    因此,在onResume呼叫的時候activity是否可見要區分是否是第一次建立activity

    onStart方法是後臺與前臺的區分,而這個方法是是否可互動的區分。使用場景最多是在當彈出別的activity的視窗時,原activity就會進入ON_PAUSE狀態,但是仍然可見;當再次回到原activity的時候,就會回撥onResume方法了。

  • onPause:當前activity視窗失去焦點的時候,會呼叫此方法。呼叫後activity進入ON_PAUSE狀態,並進入後臺。

    這個方法一般在另一個activity要進入前臺前被呼叫。只有當前activity進入後臺,其他的activity才能進入前臺。所以,該方法不能做重量級的操作,不然則會引用介面切換卡頓

    一般的使用場景為介面進入後臺時的輕量級資源釋放。

    最好理解這個狀態就是彈出另一個activity的視窗的時候。因為前臺activity只能有一個,所以當前可互動的activity變成另一個activity後,原activity就必須呼叫onPause方法進入ON_PAUSE狀態;但是!!仍然是可見的,只是無法進行互動。這裡也可以更好地體會前臺可互動與可見性的區別。

  • onStop:當activity不可見的時候進行呼叫。呼叫後activity進入ON_STOP狀態。

    這裡的不可見是嚴謹意義上的不可見。

    當activity不可互動時會回撥onPause方法並進入ON_PAUSE狀態。但如果進入的是另一個全屏的activity而不是小視窗,那麼當新的activity介面顯示出來的時候,原Activity才會進入ON_STOP狀態,並回調onStop方法。同時,activity第一建立的時候,介面是在onResume方法之後才顯示出來,所以onStop方法會在新activity的onResume方法回撥之後再被回撥。

    注意,被啟動的activity並不會等待onStop執行完畢之後再顯示。因而如果onStop方法裡做一些比較耗時的操作也不會導致被啟動的activity啟動延遲。

    onStop方法的目的就是做資源釋放操作。因為是在另一個activity顯示之後再被回撥,所以這裡可以做一些相對重量級的資源釋放操作,如中斷網路請求、斷開資料庫連線、釋放相機資源等。

    如果一個應用的全部activity都處於ON_STOP狀態,那麼這個應用是很有可能被系統殺死的。而如果一個ON_STOP狀態的activity被系統回收的話,系統會保留該activity中view的相關資訊到bundle中,下一次恢復的時候,可以在onCreate或者onRestoreInstanceState中進行恢復。

  • onRestart :當從另一個activity切回到該activity的時候會呼叫。呼叫該方法後會立即呼叫onStart方法,之後activity進入ON_START狀態。

    這個方法一般在activity從ON_STOP狀態被重新啟動的時候會呼叫。執行該方法後會立即執行onStart方法,然後Activity進入ON_START狀態,進入前臺。

  • onDestroy:當activity被系統殺死或者呼叫finish方法之後,會回撥該方法。呼叫該方法之後activity進入ON_DESTROY狀態。

    這個方法是activity在被銷燬前回調的最後一個方法。我們需要在這個方法中釋放所有的資源,防止造成記憶體洩漏問題。

    回撥該方法後的activity就等待被系統回收了。如果再次開啟該activity需要從onCreate開始執行,重新建立activity。

那到這裡七個最關鍵的生命週期方法就講完了。需要讀者注意的是,在概述一圖中,我們使用了三個維度來進行區分不同型別的狀態,但是很明顯,同一型別的狀態並不是等價的。如ON_START狀態表示activity進入前臺,而ON_PAUSE狀態卻表示activity進入後臺。這可能也是為什麼谷歌要區分出on_start和on_pause兩個狀態,他們代表並不是一致的狀態。

這七個生命週期回撥方法是最重要的七個生命週期回撥方法,需要讀者好好理解每個回撥方法設計到的activity的狀態轉換。而理解了狀態轉換後,也就可以寫出更加強壯的程式碼了。

其他生命週期回撥方法

  • onActivityResult

這個方法也很常見,他需要結合startActivityForResult一起使用。

使用的場景是:啟動一個activity,並期望在該activity結束的時候返回資料。

當啟動的activity結束的時候,返回原activity,原activity就會回撥onActivityResult方法了。該方法執行在其他所有的生命週期方法前。關於onActivityResult如何使用這裡就不展開了,我們主要介紹生命週期。

  • onSaveInstanceState/onRestoreInstanceState

這兩個方法,主要用於在Activity被意外殺死的情況下進行介面資料儲存與恢復。什麼叫意外殺死呢?

如果你主動點選返回鍵、呼叫finish方法、從多工列表清除後臺應用等等,這些操作表示使用者想要完整得退出activity,那麼就沒有必要保留介面資料了,所以也不會呼叫這兩個方法。而當應用被系統意外殺死,或者系統配置更改導致的activity銷燬,這個時候當用戶返回activity時,期望介面的資料還在,則會通過回撥onSaveInstanceState方法來儲存介面資料,而在activity重新建立並執行的時候呼叫onRestoreInstanceState方法來恢復資料。事實上,onRestoreInstanceState方法的引數和onCreate方法的引數是一致的,只是他們兩個方法回撥的時機不同。因此,判斷是否執行的關鍵因素就是使用者是否期望返回該activity時介面資料仍然存在

這裡需要注意幾個點:

  1. 不同android版本下,onSaveInstanceState方法的呼叫時機是不同的。目前筆者的原始碼是api30,在官方註釋中可以看到這麼一句話:

    /*If called, this method will occur after {@link #onStop} for applications
     * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
     * For applications targeting earlier platform versions this method will occur
     * before {@link #onStop} and there are no guarantees about whether it will
     * occur before or after {@link #onPause}.
     */
    

    翻譯過來意思就是,在api28及以上版本onSaveInstanceState是在onStop之後呼叫的,但是在低版本中,他是在onStop之前被呼叫的,且與onPause之間的順序是不確定的。

  2. 當activity進入後臺的時候,onSaveInstanceState方法則會被呼叫,而不是異常情況下才會呼叫onSaveInstanceState方法,因為並不確定在後臺時,activity是否會被系統殺死,所以以最保險的方法,先儲存資料。當確實是因為異常情況被殺死時,返回activity使用者期望介面需要恢復資料,才會呼叫onRestoreInstanceState來恢復資料。但是,activity直接按返回鍵或者呼叫finish方法直接結束Activity的時候,是不會回撥onSaveInstanceState方法,因為非常明確下一次返回該activity使用者期望的是一個乾淨介面的新activity。

  3. onSaveInstanceState不能做重量級的資料儲存。onSaveInstanceState儲存資料的原理是把資料序列化到磁碟中,如果儲存的資料過於龐大,會導致介面卡頓,掉幀等情況出現。

  4. 正常情況下,每個view都會重寫這兩個方法,當activity的這兩個方法被呼叫的時候,會向上委託window去呼叫頂層viewGroup的這兩個方法;而viewGroup會遞迴呼叫子view的onSaveInstanceState/onRestoreInstanceState方法,這樣所有的view狀態就都被恢復了。

關於介面資料恢復這裡也不展開細講了,有興趣的讀者可以自行深入研究。

  • onPostCreate

這個方法其實和onPostResume是一樣的,同樣的還有onContextChange方法。這三個方法都是不常用的,這裡也點出其中一個來統一講一下。

onPostCreate方法發生在onRestoreInstanceState之後,onResume之前,他代表著介面資料已經完全恢復,就差顯示出來與使用者互動了。在onStart方法被呼叫時這些操作尚未完成。

onPostResume是在Resume方法被完全執行之後的回撥。

onContentChange是在setContentView之後的回撥。

這些方法都不常用,僅做了解。如果真的遇到了具體的業務需求,也可以拿出來用一下。

  • onNewIntent

這個方法涉及到的場景也是重複啟動,但是與onRestart方法被呼叫的場景是不同的。

我們知道activity是有多種啟動模式的,其中singleInstance、singleTop、singleTask都保證了在一定情況下的單例狀態。如singleTop,如果我們啟動一個正處於棧頂且啟動模式為singleTop的activity,那麼他並不會在建立一個activity例項,而是會回撥該activity的onNewIntent方法。該方法接收一個intent引數,該引數就是新的啟動Intent例項。

其他的情況如singleTask、singleInstance,當遇到這種強制單例情況時,都會回撥onNewIntent方法。關於啟動模式這裡也不展開,後續筆者可能會再出一期文章講啟動模式。

場景生命週期流程

這一部分主要是講解在一些場景下,生命週期方法的回撥順序。對於當個Activity而言,上述流程圖已經展示了各種情況下的生命週期回撥順序了。但是,當啟動另一個activity的時候,到底是onStop先執行,還是被啟動的onStart先執行呢?這些就變得比較難以確定。

驗證生命週期回撥順序最好的方法就是寫demo,通過日誌列印,可以很明顯地觀察到生命週期的回撥順序。當然,檢視原始碼也是一個不錯的方法,但是需要對系統原始碼有一定的認識,我們還是選擇簡單粗暴的方法。

正常啟動與結束

onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy

這種情況的生命週期比較好理解,就是常規的啟動與結束,也不會涉及到第二個activity。最後看日誌列印:

Activity切換

Activity1:onPause
Activity2:onCreate -> onStart -> onResume
Activity1:onStop

當切換到另一個activity的時候,本activity會先呼叫onPause方法,進入後臺;被啟動的activity依次呼叫三個回撥方法後準備與使用者互動;這時原activity再呼叫onStop方法變得不可見,最後被啟動的activity才會顯示出來。

理解這個生命週期順序只需要記住兩個點:前後臺、是否可見。onPause呼叫之後,activity會進入後臺。而前臺互動的activity只能有一個,所以原activity必須先進入後臺後,目標activity才能啟動並進入前臺。onStop呼叫之後activity變得不可見,因而只有在目標activity即將要與使用者互動的時候,需要進行顯示了,原Activity才會呼叫onStop方法進入不可見狀態。

當從Activity2回退到Activity1的時候,流程也是類似的,只是Activity1會在其他生命週期之前執行一次onRestart,跟前面的流程是類似的。讀者可以看一下下面的日誌列印,這裡就不再贅述了。

下面看一下切換到另一個activity的生命週期日誌列印:

這裡我們看到最後回調了onSaveInstanceState方法,前面我們講到了,當activity進入後臺的時候,會呼叫該方法來儲存資料。因為並不知道在後臺時activity是否會被系統殺死。下面再看一下從activity2返回的時候,生命週期的日誌列印:

螢幕旋轉

running -> onPause -> onStop -> onSaveInstanceState -> onDestroy

onCreate -> onStart -> onRestoreInstanceState -> onResume

當因資源配置改變時,activity會銷燬重建,最常見的就是螢幕旋轉。這個時候屬於異常情況的Activity生命結束。因而,在銷燬的時候,會呼叫onSaveInstanceState來儲存資料,在重新建立新的activity的時候,會呼叫onRestoreInstanceState來恢復資料。

看一下日誌列印:

後臺應用被系統殺死

onDestroy

onCreate -> onStart -> onRestoreInstanceState -> onResume

這個流程跟上面的資源配置更改是很像的,只是每個activity不可見的時候,會回撥onSaveInstanceState提前儲存資料,那麼在被後臺殺死的時候,就不需要再次儲存資料了。

具有返回值的啟動

onActivityResult -> onRestart -> onResume

這裡主要針對使用startActivityForResult方法啟動另一個activity,當該activity銷燬並返回時,原activity的onActivityResult方法的執行時機。大部分流程和activity切換是一樣的。但在返回原Activity時,onActivityResult方法會在其他所有的生命週期方法執行前被執行。看一下日誌列印:

重複啟動

onPause -> onNewIntent -> onResume

這個流程是比較容易在學習生命週期的時候被忽略的。前面已經有講到了關於onNewIntent的相關介紹,這裡就不再贅述。主要是記得如果當前activity正處於棧頂,那麼會先回調onPause之後再回調onNewIntent。關於啟動模式以及返回棧的設計這裡就不展開講了,記住生命週期就可以了。看一下日誌列印:

從原始碼看生命週期

到這裡關於生命週期的一些應用知識就已經講得差不多了,這一部分是深入原始碼,去探究生命週期在原始碼中是如何實現的。這樣對生命週期會有更加深刻的理解,同時可以更加了解android系統的原始碼設計。

由於生命週期方法很多,筆者不可能一一講解,這樣篇幅就太大了且沒有意義。這一部分的內容一共分為兩個部分:第一部分是概述一下ActivityThread中關於每個生命週期的呼叫方法,這樣大家就懂得如何去尋找對應的原始碼來研究;第二部分是拿onResume這個方法來舉例講解,同時解釋為什麼在第一次啟動時,當onResume被呼叫時介面依然不可見。

從ActivityThread看生命週期

我們都知道,Activity的啟動是受AMS調配的,那具體的調配方式是如何的呢?

通過Handler機制一文我們知道,android的程式執行是使用handler機制來實現訊息驅動型的。AMS想要控制Activity的生命週期,就必須不斷地向主執行緒傳送message;而程式想要執行AMS的命令,就必須handle這些message執行邏輯,兩端配合,才能達到這種效率。

打個比方,領導要吩咐下屬去工作,他肯定不會把工作的具體流程都給下屬,而只是會發個命令,如:給明天的演講做個ppt,給我預約個下星期的飛機等等。那麼下屬,就必須根據這些命令來執行指定的邏輯。所以,在android程式,肯定有一系列的邏輯,來分別執行來自AMS的“命令”。這就是ActivityThread中的一系列handlexxx方法。給個我在vs code中的搜尋圖感受一下:

當然,應用程式不止收到AMS的管理,同樣的還有WMS、PMS等等系統服務。系統服務是執行在系統服務程序的,當系統服務需要控制應用程式的時候,會通過Binder跨程序通訊把訊息傳送給應用程式。應用程式的Binder執行緒會通過handler把訊息傳送給主執行緒去執行。因而,從這裡也可以看出,當應用程式剛被建立的時候,必須初始化的有主執行緒、binder執行緒、主執行緒handler、以及提前編寫了命令的執行邏輯的類ActivityThread。光說不畫假解釋,畫個圖感受一下:

回到我們的生命週期主題。關於生命週期命令的執行方法主要有:

handleLaunchActivity;
handleStartActivity;
handleResumeActivity;
handlePauseActivity;
handleStopActivity;
handleDestroyActivity;

具體的方法當然不止這麼多,只是列出一些比較常用的。這些方法都在ActivityThread中。ActivityThread每個應用程式有且只有一個,他是系統服務“命令”的執行者。

瞭解了AMS如何調配之後,那麼他們的執行順序如何確定呢?AMS是先發送handleStartActivity命令呢,還是先發送handleResumeActivity?這裡就需要我們對Activity的啟動流程有一定的認知,感興趣讀者可以點選Activity啟動流程前往學習,這裡就不展開了。

最後再延伸一下,那,ActivityThread可不可以自己決定執行邏輯,而不理會AMS的命令呢?答案肯定是no。你想啊,如果在公司裡,你沒有老闆的同意 ,你能動用公司的資源嗎?回到Android系統也是一樣的,沒有AMS的授權,應用程式是無法得到系統資源的,所以AMS就保證了每一個程式都必須符合一定的規範。關於這方面的設計,讀者感興趣可以閱讀context機制這篇文章瞭解一下,重新認識一下context。

好了,扯得有點遠,我們回到主題。下面呢就不展開去跟蹤整個流程了,而是定位到具體的handle方法去看看具體執行了什麼邏輯,對原始碼流程感性去的讀者可以自行研究,限於篇幅這裡就不展開了。下面主要介紹handleResumeActivity方法。

解析onRusume原始碼

根據我們前面的學習,handleResumeActivity肯定是在handleLaunchActivityhandleStartActivity之後被執行的。我們直接來看原始碼:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    ...
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...;
    if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
        ...
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    }
    ...
}

程式碼我截取了兩個非常重要的部分。performResumeActivity最終會執行onResume方法;activity.makeVisible();是真正讓介面顯示在螢幕個上的方法,我們看一下makeVisible():

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

如果尚未新增到螢幕上,那麼會呼叫windowManager的addView方法來新增,之後,activity介面才真正顯示在螢幕上。迴應之前的問題:為什麼在onResume彈出popupWindow會拋異常而彈出dialog卻不會?原因就是這個時候activity的介面尚未新增到螢幕上,而popupWindow需要依附於父介面,這個時候彈出就會丟擲token is null異常了。而Dialog屬於應用層級視窗,不需要依附於任何視窗,所以dialog在onCreate方法中彈出都是沒有問題的。為了驗證我們的判斷,我在生命週期中列印decorView的windowToken,當decorView被新增到螢幕上後,就會被賦值token了,看日誌列印:

可以看到,直到onPostResume方法執行,介面依舊沒有顯示在螢幕上。而直到onWindowFocusChange被執行時,介面才是真正顯示在螢幕上了。

好了,讓我們再回到一開始的原始碼,深入performResumeActivity方法中看看,在哪裡執行了onResume方法:

public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
        String reason) {
    ...
    try {
        ...
        if (r.pendingIntents != null) {
            // 判斷是否需要執行newIntent方法
            deliverNewIntents(r, r.pendingIntents);
            r.pendingIntents = null;
        }
        if (r.pendingResults != null) {
            // 判斷是否需要執行onActivityResult方法
            deliverResults(r, r.pendingResults, reason);
            r.pendingResults = null;
        }
        // 回撥onResume方法
        r.activity.performResume(r.startsNotResumed, reason);

        r.state = null;
        r.persistentState = null;
        // 設定狀態
        r.setState(ON_RESUME);

        reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
    } 
    ...
}

這個方法的重點就是,先判斷是否是需要執行onNewIntent或者onActivityResult的場景,如果沒有則執行呼叫performResume方法,我們深入performResume看一下:

final void performResume(boolean followedByPause, String reason) {
    dispatchActivityPreResumed();
    performRestart(true /* start */, reason);
	...
    mInstrumentation.callActivityOnResume(this);
    ...
    onPostResume();
   	...
}

public void callActivityOnResume(Activity activity) {
    activity.mResumed = true;
    activity.onResume();
    ...
}

同樣只看重點。首先會呼叫performRestart方法,這個方法內部會判斷是否需要執行onRestart方法和onStart方法,如果是從別的activity返回這裡是肯定要執行的。然後使用Instrumentation來回調Activity的onResume方法。當onResume回撥完成後,會再呼叫onPostResume()方法。

那麼到這裡關於handleResumeActivity的方法就講完了,為什麼在onResume甚至onPostResume方法被回撥的時候介面尚未顯示,也有了更加深刻的認識。具體的程式碼邏輯非常多,而關於生命週期的程式碼我只挑了重點來講,其他的原始碼,感興趣的讀者可以自行去查閱原始碼。筆者這裡更多的是充當一個拋磚引玉的效果。要從原始碼中學習到知識,就必須自己手動去閱讀原始碼,跟著文章看完事實上收穫是不大的。

從系統設計看Activity與其生命週期

在筆者認為,每一個知識,都是在具體的場景下為了解決具體的問題,通過權衡各種條件設計出來的。在學習了每一個知識之後,筆者總是喜歡反過來,思考一下這一塊知識的底層設計思想是什麼,他是需要解決什麼問題,權衡了什麼條件。通過不斷思考來從一個更高的角度來看待每一個知識點。

要理解生命週期的設計,首先需要理解Activity本身。想一下,如果沒有Activity,那麼我們該如何編寫程式?有沒有忽然反應到,沒有了activity,我們的程式竟無處下手?因為這涉及到Activity的一個最大的作用:Activity 類是 Android 應用的關鍵元件,而 Activity 的啟動和組合方式則是該平臺應用模型的基本組成部分

相信很多讀者都寫過c語言、java或者其他語言的課程設計,我們的程式入口就是main函式。從main函式開始,根據使用者的輸入來進入不同的功能模組,如更改資訊模組、查閱模組等等。以功能模組為基本組成部分的應用模型是我們最初的程式設計模型。而android程式,我們會說這個程式有幾個介面。我們更關注的是介面之間的跳轉,而不是功能模組之間的跳轉。我們在設計程式的時候,我們會說這個介面有什麼功能,那個介面有什麼功能,多個介面之間如何協調。對於使用者來說,他們感知的也是一個個獨立的介面。當我們通過通訊軟體呼叫郵箱app的傳送郵件介面時,我們喜歡看到的只是傳送郵件的介面,而不需要看到收件箱、登入註冊等介面。以功能模組為應用模型的設計從一個主功能模組入口,然後通過使用者的輸入去呼叫不同的功能模組。與其類似,android程式也有一個主介面,通過這個主介面,接受使用者的操作去呼叫其他的介面。組成android程式的,是一個個的介面,而每一個介面,對應一個Activity。因此,Activity是android平臺應用模型的基本組成成分

功能模組的應用模型從main方法進入主功能模組,而android程式從ActivityThread的main方法開始,接收AMS的排程啟動“LaunchActivity”,也就是我們在AndroidManifest中配置為main的activity,當應用啟動的時候,就會首先開啟這個activity。那麼第一個介面被開啟,其他的介面就根據使用者的操作來依次跳轉了。

那如何做到每個介面之間彼此解耦、各自的顯示不發生混亂、介面之間的跳轉有條不紊等等?這些工作,官方都幫我們做好了。Activity就是在這個設計思想下開發出來的。當我們在Activity上開發的時候,就已經沿用了他的這種設計思想。當我們開發一個app的時候,最開始要考慮的,是介面如何設計。設計好介面之後,就是考慮如何開發每個介面了。那我們如何自定義好每一個介面?如何根據我們的需求去設計每個介面的功能?Activity並沒有main方法,我們的程式碼該寫在哪裡被執行?答案就是:生命週期回撥方法

到這裡,你應該可以理解為什麼啟動activity並不是一句new就可以解決的吧?Activity承擔的責任非常多,需要初始化的邏輯也非常多。當Activity被啟動,他會根據自身的啟動情況,來回調不同的生命週期方法。其中,承擔初始化整個介面已經各個功能元件的初始化任務的就是onCreate方法。他有點類似於我們功能模組的入口函式,在這裡我們通過setContentView來設計我們介面的佈局,通過setOnClickListenner來給每個view設定監聽等等。在MVVM設計模式中,還需要初始化viewModel、繫結資料等等。這是生命週期的第一個非常重要的意義所在。

而當介面的顯示、退出,我們需要為之申請或者釋放資源。如我上文舉的相機例子,我在微信的掃一掃申請了相機許可權,如果進入後臺的時候沒有釋放資源,那麼開啟系統相機就無法使用了,資源被佔領了。因此,生命週期的另一個重要的作用就是:做好資源的申請與釋放,避免記憶體洩露

其他生命週期的作用,如介面資料恢復、介面切換邏輯處理等等就不再贅述了,前面已經都有涉及到。

這一部分的重點就是理解android應用程式是以Activity為基本組成部分的應用模型這個點。當介面的啟動以及不同介面之間進行切換的時候,也就可以更加感知生命週期的作用了。

最後

關於Activity生命週期的內容,在一篇文章講完整是不可能的。當對他研究地越深,涉及到內容就會越多。每個知識點就是像是瓜藤架上的一個瓜,如果單純摘瓜,那就是一個瓜;如果抓著藤蔓往外拔,那麼整個架子都會被扯出來。這篇文章也當是拋磚引玉,在講生命週期相關的知識講完之後,提供給讀者一個思考的思路。

希望文章對你有幫助。

全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發。
筆者才疏學淺,有任何想法歡迎評論區交流指正。
如需轉載請評論區或私信交流。

另外歡迎光臨筆者的個人部落格:傳送門