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

希望文章對你有幫助。

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

另外歡迎光臨筆者的個人博客:傳送門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM