主頁 > 移動端開發 > Android全面決議之Activity生命周期

Android全面決議之Activity生命周期

2020-12-15 10:01:22 移動端開發

前言

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

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

希望文章對你有幫助,

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

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

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/234812.html

標籤:Android

上一篇:IPA

下一篇:Android自定義View-使用BitmapShader實作圓形圖片

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more