安卓現代化開發系列——從生命周期到Lifecycle

由于安卓已經誕生快二十載,其最初的開發思想與現代的開發思想已經大相徑庭,特別是Jetpack庫誕生之后,項目中存在著新老思想混雜的情況,讓許多的新手老手都措手不及,項目大步向屎山邁進。為了解決這個問題,開發者必須弄懂新舊兩種開發模式,這就是《安卓現代化開發系列》誕生的意義,本系列并不會包含隱晦難懂的代碼,一切的文字都是以理解本質為主,起到一個拋鉆引玉的作用。

生命周期的前世今生

1.1、前世——初識篇

天地初開,一切皆為混沌的時代,安卓宇宙中誕生了名為Activity(活動)的組件,Activity 是Android應用中最關鍵的組件,一個Activity 通常對應的是App的一個頁面,當手機使用者在不同的頁面之間導航的時候,新的Activity 會誕生,同時也會在特定的時候銷毀。一個頁面的誕生之初到它銷毀的這段時間,名為「生命周期」。

理解并掌握生命周期是每一個Android修煉者的必修功力,因為生命周期的每一個階段均代表Activity 處于不同的狀態之中,一旦錯誤處理生命周期周期,修煉者輕則內傷殘疾(手機耗電過多,丟失信息),重則走火入魔(程序崩潰)。

關于生命周期,江湖中一直流傳著一張「Activity生命周期總覽圖」,但個中奧秘,卻鮮為人知,因此少有人能夠修煉到最高境界:

由圖可見,Activity的生命周期中,提供了6種回調:onCreate()onStart()onResume()onPause()onStop()onDestroy() ,需要特別注意的是,這僅僅是一種回調,與我們通常的認識不同都是,生命周期的某個階段是指一個時間段 ,而回調或者說事件只是一個瞬間,換句話來說,onCreate并不是指生命周期中存在一個階段名為onCreate,而是Activity 觸發了onCreate事件,即將進入已創建階段。

然而可惜的是,在遠古Android的設計中,Android的創世神并沒有為開發者提供具體的生命周期階段的概念,僅僅是提供了進入某個生命周期階段的回調,因此上述提到的“已創建”這個狀態在原生安卓的概念中并不存在。然而在后人的努力中,生命周期階段這一概念最終得到確定與落實,不過這都是后話了。

1.2、前世——詳解篇

1.2.1、onCreate()、onDestroy()

  • onCreate()Activity 生命周期的起點,首次被系統創建時觸發,整個生命周期只會觸發一次。此回調通常用于執行頁面View的設置,例如setContentView()
  • onDestroy()Activity生命周期的終點,在Activity被銷毀前觸發,此回調的有兩種情況被調用:
  1. 用戶手動關閉Activity(按返回鍵)或者系統主動關閉Activity(一般是App進程因內存不足被銷毀,導致Activity也被銷毀)。
  2. 配置變更(設備旋轉、語言切換等)。

簡單來說,onCreate()是Activity被創建的時刻,onDestroy()是Activity即將被銷毀的時刻。

一個Activity進入onDestroy() 之后,理應被GC回收,但是如果此時它仍然被引用(例如被某些網絡請求的回調中被引用),那么此Activity就會導致內存泄漏 ,這也是所有Android開發者需要關注其生命周期的原因。

1.2.2、onStart()、onStop()

  • onStart() :當Activity在onCreate()之后不久就會觸發此回調,說明了Activity 此刻進入了“已開始”的狀態,但是此刻的Activity仍然未獲取焦點

很多Android開發者一直搞不懂Activity為什么會存在一個可見但是沒有獲取焦點的狀態,會存在這種疑惑的原因是因為Android通常作為一種移動設備的系統而存在,而移動設備由于其特殊性,通常也只會同屏存在一個頁面,因此可見但是沒有獲取焦點這種狀態幾乎只存在一瞬間(它馬上就會遮住之前正在交互的頁面),然而我們以電腦系統的角度來看,電腦系統的桌面上基本都是多窗口并存的,然而即使存在了多窗口,用戶能交互的也僅僅只有獲取焦點的那個窗口。

因此,可見但是沒有獲取焦點的窗口,就像是電腦上那些打開著、但被用戶正在交互的窗口擋住的那些窗口,假如電腦桌面上存在著一個QQ窗口,然而用戶正在編寫一個Word文檔,那么被Word擋住的那個QQ窗口,就是可見但未獲取焦點的窗口。

  • onStop() :當一個Activity可見但是沒有獲取到焦點的狀態變為完全不可見的狀態時就會觸發此回調,按照上文類比,這種情況通常可以理解為:電腦桌面上的一個被遮擋的窗口被最小化了

1.2.3、onResume()、onPause()

  • onResume() :當Activity可見但是沒有獲取焦點的狀態變成可見同時獲取焦點的狀態時,觸發此回調,同樣按照電腦系統的角度來理解,這種情況通常可以理解為:電腦桌面上的一個被遮擋的窗口此刻被用戶交互了
  • onPause() :當Activity從“可見同時獲取焦點”的狀態變成可見但是沒有獲取焦點的狀態時,觸發此回調,同樣同樣按照電腦系統的角度來理解,這種情況通常可以理解為:電腦桌面上的一個正在被用戶交互的窗口,由于用戶操作了其他窗口,導致當前的窗口被遮擋了,也因此失去了焦點

1.3、前世——總結篇

我們從電腦系統的窗口去理解Activity的生命周期:

  1. 啟動一個程序的時候,程序就會在電腦桌面上創建一個窗口,創建的那一瞬間(通常會很快,可能不需要1秒)就相當于Activity的onCreate()。
  2. 創建完成后,窗口就可以被用戶所看見了,被用戶看到的那一瞬間就相當于Activity的onStart()。
  3. 通常來說,一個新啟動的程序會自動獲得焦點并可被用戶交互,因此onStart()之后,窗口會被置頂到頂層,這一瞬間就相當于Activity的onResume()。
  4. 當用戶選擇其他窗口時,之前交互的窗口并不會消失,而是會失去焦點并被用戶最新交互的窗口所遮擋,這一瞬間就相當于Activity的onPause()。
  5. 當用戶最小化窗口時,窗口就會進入后臺(并不是銷毀)而且并不能被用戶所看見,這一瞬間就相當于進入了Activity的onStop()。
  6. 當用戶關閉程序亦或者電腦內存不足時,程序被銷毀,窗口同時也被銷毀了,這一瞬間就相當于進入了Activity的onDestroy()。

一個窗口當然可以失去焦點后重新獲取焦點,因此onPause()和onResume()可能在生命周期中多次被執行,同理窗口也可以最小化之后重新最大化,onStart()和onStop()也可能在生命周期中多次被執行。只不過對于移動設備來說,幾乎不存在頁面失去焦點后又重新獲得焦點的情況,因為移動設備的頁面絕大多數情況都是一個頁面可被用戶交互,被擋住的頁面完全不可見,即等價于電腦系統中只存在一個最大化的頁面,所以移動設備的Activity的生命周期通常只會在onStart()和onStop()兩者之間流轉(當然,仍然會遵循onStart()->onResumt()->onPause()->onStop()的順序)。

而一個窗口只能被創建和銷毀一次,因此在Activity的生命周期中,onCreate()和onDestroy()只會被調用一次。

上文中提到,原生的Android生命周期設計中,只提供了進入某個生命周期狀態的回調,并沒有提供具體的狀態的定義,例如onCreate() 與onStart()之間的狀態叫什么呢,官方的文檔提到了這個叫“已創建”的狀態,然而這只存在于文本性的文檔中,這在代碼中并不存在,只能作為一種“共識”的定義。這也為開發者之間溝通生命周期帶來了極大的困擾。

2.1、今生——初識篇

經歷漫長的混沌時代之后,Jetpack攜帶著「Lifecycle」正式進入到了Android的世界中,「Lifecycle」為千千萬萬的Android修煉者帶來了福音,因為它比起傳統的基于回調的方式來感知生命周期的方式有以下的優點:

  1. 提出了「生命周期狀態」的概念,彌補了安卓傳統的生命周期只有事件沒有狀態的缺陷
  2. 將生命周期管理從頁面(如Activity和Fragment)脫離,將生命周期監聽的職責轉移到組件中,降低頁面與組件的耦合度。

為了讓讀者更加清晰使用「Lifecycle」與不使用它之間的區別,這里使用兩個代碼案例來對比:

  • 首先,定義一個常見的基于回調的監聽類,每秒鐘會對外廣播一次字符串。

  • Activity中的onCreate()階段初始化監聽,然后在onStart()中開啟監聽,在onStop()中關閉監聽,這樣的好處是當頁面不可見的時候不會浪費手機性能。

以上便是傳統安卓開發中最直接也是最常見的一種根據生命周期來實現監聽的方式,讓我們分析一下這種方式的缺點:

  1. 真實業務開發中,同一頁面中往往存在大量的生命周期監聽需求,Activity等生命周期組件會同時管理大量的組件,讓代碼難以維護。
  2. 代碼缺乏一致性,需要監聽生命周期的組件存在許多模板代碼。試想一下,一個需要在onStart()啟動,在onStop()關閉的、同時在項目中大量存在的組件,某天需要它在onResume()做一些操作,那將會導致災難,因為需要每一處使用它的代碼中增加onResume()的修改,一旦遺漏這個修改將會導致不可預知的bug。
  3. 無法獲取實時的生命周期狀態。假設在onStart()的階段,需要執行一個網絡請求或者其他耗時操作之后再調用listener.start()的場景下,無法保證此刻頁面仍然處于可見的狀態,開發者也無法獲取「當前所處狀態」來避免不可見的時候仍然調用listener.start()(這個缺陷上文已經提到,原生安卓生命周期只提供了生命周期事件而沒有生命周期狀態)。

讓我們看一看使用了「Lifecycle」庫之后的生命周期是如何實現監聽的:

我們讓需要監聽Activity生命周期的MyListener組件實現DefaultLifecycleObserver接口,然后重寫onStart()、onStop()方法,然后直接在Activity中獲取lifecycle然后調用其addObserver()即可。

我們會發現,「生命周期管理」的責任從Activity轉移到了組件中,Activity本身只負責對外廣播自身的生命周期,這樣極大減少了Activity的維護負擔。

2.2、今生——詳解篇

2.2.1、Lifecycle(opens new window)

Lifecycle包含兩個定義,一個指的是Jetpack庫中的Lifecycle組件庫,一個指的是Lifecycle組件庫中的一個核心類,后文中如果沒有特指情況下,文章中描述的默認為類

上文中提到,安卓原生中只有描述生命周期的事件,缺乏一種描述當前生命周期所處的狀態,但是「Lifecycle」庫中補全了狀態,下圖中闡述了事件與狀態的關系:

根據「Lifecycle」庫的定義,一個生命周期狀態的起點是「Initialized」,終點是「Destroyed」,當發生生命周期事件時,生命周期狀態就會發生移動,包括狀態提升狀態下降

我們把狀態從Initialized到Resumed當做一個從小到大的狀態,如果狀態值變小了,則稱為狀態下降,反之則為狀態提升

初步的定義有了,讓我們把視角聚焦于Lifecycle類的源碼:

可以看到,Lifecycle類的設計基本遵循生命周期事件與狀態圖例,一個Lifecycle只有2個核心功能:

  1. 緩存當前的生命周期狀態(currentState)。
  2. 添加與移除生命周期觀察者。

上述代碼中,對EventState的部分代碼進行了省略,下面展開講解:

首先是Event類,Event類對應的是生命周期事件,也就是原生安卓生命周期的事件,即onCreate()、onPause()等。

該類提供了一個targetState的屬性,指的是發生了該事件之后,生命周期狀態發生改變的狀態目標。

例如發生了ON_CREATE事件,這是狀態從「Initialized」向「Created」轉移的瞬間,那么targetState自然就是「State.CREATED」了;同理發生ON_STOP事件時,是狀態從「Started」向「Created」轉移的瞬間,targetState也是「State.CREATED」。

此處不必死記硬背,只需要配合狀態與事件圖理解其意義即可。

該類還提供了四個方法,downFrom()、downTo()、upFrom()、upTo(),這些都是當狀態發生提升或者降級的時候,方便獲取對應的事件的便捷方法,以downFrom()舉例:

downFrom(state:State)的含義是獲取會導致state發生狀態下降的事件,假如State.Created,發生什么事件會導致狀態從State.Created下降呢,我們回去查看狀態與事件圖,發現是發生了ON_DESTROY事件,那么該方法就會返回ON_DESTROY。

此處不必死記硬背,只需要配合狀態與事件圖理解其意義即可。

看完了Event,我們把視角轉向State

State的代碼非常簡單甚至不用一絲的省略,除了枚舉值外僅有一個方法:isAtLeast(state:State) ,此方法的含義是用于判斷當前的狀態是否大于或等于目標值的狀態。

如何理解呢?還記得上文提到的嗎,狀態是有大小的:

我們把狀態從Initialized到Resumed當做一個從小到大的狀態,如果狀態值變小了,則稱為狀態下降,反之則為狀態提升

因此對于生命周期的狀態而言,Created是比Initialized的,isAtLeast(state:State)的含義就是判斷生命周期是否比某個預期值“走的更遠”了,如果一個行為可以在組件創建后被執行,那么換句話說,只要生命周期的狀態大于或者等于Created即可。

上文中提到,原生的生命周期回調無法實時獲取生命周期所處的狀態,一旦在生命周期回調方法中執行一些耗時操作,就無法耗時操作結束后,仍處于安全的生命周期區間,例如下面的代碼:

我們嘗試在onStart()中執行一段耗時操作再開啟監聽,但是執行耗時操作期間無法Activity是否已經處于onStop()了,此刻我們就可以使用isAtLeast(state:State)來判斷耗時操作結束后的生命周期狀態:

可見,「Lifecycle」庫確實解決了生命周期只有事件沒有狀態的問題,開發者可以輕易獲取當前的生命周期所處的階段。

2.2.2、LifecycleOwner(opens new window)

首先,我們看看它的源碼:

非常的簡單,只是給實現者對外提供一個獲取Lifecycle的入口,為什么要這樣設計呢?還記得Lifecycle嗎,它并不是一個接口而是一個抽象類,在Jvm中是單繼承的,因此不太可能會讓帶有生命周期的組件直接繼承Lifecycle抽象類。

因此在實際使用中,帶有生命周期的組件和Lifecycle是包含的關系,即下圖的情況:

為什么谷歌的開發人員要如此奇怪呢,讓Lifecycle變成接口,讓Activity實現接口不一樣能讓組件訪問到Lifecycle嗎?先別急,Lifecycle的具體實現我們還沒看,等到那一節將會解答這個疑問。

總結:LifecycleOwner只是一個簡單的對外提供訪問Lifecycle的接口。

2.2.3、LifecycleObserver(opens new window)

此處就不放代碼了,因為這是一個空接口,作用是將其實現者變成一個生命周期的觀察者

其本身不起作用,業務中我們通常使用其子接口,例如DefaultLifecycleObserverLifecycleEventObserver等,可以回去查看2.1節的MyListener實現了DefaultLifecycleObserver之后是如何感知Activity的生命周期的。

2.2.4、LifecycleRegistry(opens new window)

此類是「Lifecycle」庫的核心類,也是Lifecycle抽象類的直接實現,它的作用是管理生命周期事件的派發,但是其做了非常多的優化,例如解決了產生事件時,迭代觀察者過程中可能會新增或者移除觀察者,用ArrayList遍歷會崩的問題、新加入的觀察者如何派發事件的問題,移除觀察者如何更新狀態的問題等等。

這些谷歌的開發人員都幫我們解決了,只需要按下圖簡單配置一下即可使用:

可見,我們只需要按照上文提到的結構,在Activity中實例化一個LifecycleRegistry,然后在合適的生命周期回調中派發響應的事件,所有監聽當前Activity生命周期的組件就可以獲取到當前Activity的生命周期了。

需要注意的是:上述代碼僅僅是為了為你展示Lifecycle是如何實現生命周期事件派發的,實際使用中,并不需要為Activity手動派發事件,ComponentActivityAppcompatActivity實際上已經配置好了派發邏輯,開發中直接獲取Lifecycle即可。

下面即將深入LifecycleRegistry的源碼層面探究一下它的原理,但是需要注意的是,本文章的目的并不是讓讀者100%搞懂源碼中每一行代碼的運行邏輯,因為這違背了本系列文章的初衷——讓讀者能夠在對庫有足夠充足的了解下開發,同時筆者也沒有100%搞懂源碼每一行的邏輯。

如果讀者非常有鉆研精神,可以看一下這個博主的文章,他對LifecycleRegistry的源碼做了非常詳細的講解:

下面我們看看LifecycleRegistry的代碼脈絡:

筆者省去了絕大部分和業務無關的代碼,只保留了最核心最精華的代碼,其實被移除掉的代碼都是為了解決前文中提到的“遍歷過程中增刪列表”、”新加入的觀察者如何派發事件“等細枝末節的問題,與本文主題關系不大。

可以看到,LifecycleRegistry本質上就是一個強化版的觀察者模式的設計,添加觀察者(observer)、遍歷派發事件的模式。

還記得上文提到的一個小問題嗎,為什么LifecycleOwner不直接設計成接口而是以成員變量的方式掛載在對應的生命周期組件里面呢?通過LifecycleRegistry的源碼我們可以看到,LifecycleOwner被以弱引用的方式存放著的,也就是說處理生命周期事件派發的LifecycleRegistry并不會直接引用LifecycleOwner,可以認為是谷歌的開發人員是為了防止產生內存泄漏而故意設計的。

2.2.5、小總結

我們已經依次瀏覽了「Lifecycle」庫中的四個最核心的組件,他們的關系如果你已經搞混了,筆者再次通過一段極簡的代碼的方式來強化讀者對他們的理解:

關于四個核心組件的總結:

  1. Lifecycle描述的是存放和管理生命周期的容器
  2. LifecycleRegistryLifecycle的實現類
  3. LifecycleObserver是觀察生命周期變化的監聽器
  4. LifecycleOwner是對外提供Lifecycle的提供者。

3、谷歌眼中的Lifecycle

3.1、ComponentActivity

此類是谷歌官方基于Activity開發的子類,其集成了許多Jetpack庫的核心功能,其中就包括了「Lifecycle」庫,該類因此也實現了LifecycleOwner接口,開發者常用的AppcompatActvity也是該類的子類。

但是細讀源碼會發現,該類并沒有像筆者之前展示的源碼那樣,直接調用LifecycleRegistry在特定的Activity生命周期回調中派發事件,那么該類是如何實現生命周期事件的派發的呢?下面介紹「Lifecycle」庫中的另外一個關鍵類:ReportFragment

3.1.1、ReportFragment與LifecycleCallbacks

ComponentActivity的onCreate()中,有一段ReportFragment.injectIfNeededIn(this)的代碼,這個就是實現了生命周期事件派發的核心類。

接下來讓我們走進ReportFragment的源碼,正如前文所述,文章并不會闡述每一行代碼的原理,而是抓住主要的脈絡,隱藏了和主脈絡無關的代碼,但是剩余的代碼量仍然挺多,讀者不必對大量的代碼感到恐慌,因為文章會逐一解釋:

可見,ReportFragment做的事非常簡單,就是在其生命周期的各個階段上報生命周期事件,因為Fragment的生命周期和Activity在絕大部分是保持一致的(特殊的如onCreate()除外,不過也有onActivityCreated()onActivityPostCreated()等可以感知Activity生命周期的函數),谷歌的開發人員于是就利用ReportFragment作為監聽Activity生命周期的工具,你可以看到這個Fragment是沒有UI的,這也間接證明了它的任務并不是展示一個UI而僅僅是為了監聽生命周期。

讓我們回到injectIfNeedIn() ,可以清楚的看到這里做了一個版本判斷,如果大于api版本大于29,則使用LifecycleCallbacks做一個注冊的邏輯,這是怎么回事呢?

在我提到ReportFragment是作為一個生命周期監聽者而不是一個展示UI的模塊的時候,你也許就已經隱隱約約聞到一種非常奇怪的味道。由于安卓源碼設計的缺陷(只對外提供了回調方法而沒有提供回調監聽注冊),開發者對待這一問題必須考慮向下兼容,因此他們選擇了源碼中已經存在的、可以監聽Activity的生命周期的Fragment,但是在api 29之后,Activity原生自帶了生命周期的回調監聽注冊,因此一旦檢測到api大于或者等于29,ReportFragment的作用就形同虛設了,因為廣播生命周期的事件的任務已經轉移給Activity自帶的生命周期回調來實現了。

你也許還會擔心,現在有ReportFragmentActivity自帶的生命周期回調兩種方式了,會不會導致一個事件被廣播兩次呢?其實不用擔心,廣播的時候已經做了排除了,只有api小于29的情況下,ReportFragment才會生效。

3.2、Fragment

Fragment本身的生命周期和Activity沒有很大的差異,依然是內置LifecycleRegistry然后在合適的生命周期回調中廣播生命周期事件的一套,但是值得注意的是:

FragmentFragmentManager管理時,例如執行replace()事務中,邏輯上當前的Fragment只是被另外一個同類所替換了,它并沒有真的被銷毀(因為待會還有重新回來的機會),因此該Fragment并不會執行onDestroy(),然而由于內存上的考量,不可見的FragmentView理應被回收,因此View會被銷毀。

換句話說,Fragment不可見之后,它的狀態會保存起來,但是其View會被銷毀,待會再次可見的時候,會根據其狀態再一次執行onCreateView()

上述機制導致了一個問題:Fragment的生命周期和其對應的View的生命周期在實質上是不對等的,然而實際開發中感知生命周期大多數是為了與UI進行互動,這也導致了開發者單純監聽Fragment的生命周期已經不能夠滿足開發上的需求了。

下面這張來源于谷歌官方開發者文檔的圖片很好的詮釋了Fragment和它的View的生命周期關系:

假如一個Fragment正在棧頂,他會處于Resumed的階段,但是被replace之后,它會進入Created階段,此刻View被銷毀,View會進入Destroyed階段,但是Fragment重回棧頂的時候,Fragment會從Created再次回歸到Resume,而View會從Destroyed重回Resumed狀態。

換句話說,在Fragment的生命周期中,它的View可能會反復的從Destroyed到Resumed之間移動(即不斷地銷毀與創建)

谷歌為了緩解這個問題,給FragmentView單獨添加了一套生命周期,我們可以通過代碼看到端倪:

可以看到,在Fragment執行performCreateView()的時候,會初始化ViewLifecycle,兩者的生命周期事件是單獨通知的。

  • 如果開發者想訪問Fragment的生命周期,在Fragment中訪問lifecycleOwner即可。
  • 如果開發者想訪問FragmentView的生命周期,在Fragment中訪問viewLifecycleOwner即可。

3.3、ViewTreeLifecycleOwner

在上述的代碼中,能夠直接訪問ActivityFragment的Lifecycle的只能是它們的類中,而很多需要訪問生命周期的地方往往是一些View中,例如要在View中監聽其父組件生命周期,然而View的父控件有非常多,包括了ActivityFragment甚至是Dialog乃至更多,要想獲取父組件的生命周期,只能做類型判斷+類型強轉的工作,這樣就極大的限制了View的使用范圍:

為了緩解,谷歌的開發人員提出了一種叫ViewTreeLifecycleOwner的設計,其實這個東西并沒有什么神秘的,讓我們直接看看源碼:

通篇只有兩個View的擴展函數,第一個函數的意義是給對應的View綁上一個LifecycleOwner,第二個函數的意義是不斷往上查找父控件,直到查出之前綁定的LifecycleOwner

這段源碼的作用挺簡單的,也就是說只要給某個頂層的控件提前綁好了LifecycleOwner,那么他下轄的所有子View都可以通過往上查找的方式來找到LifecycleOwner,不得不說谷歌的開發人員真的是太厲害了,在簡陋的基礎下做出了非常強大的功能。

那么下面的問題是:LifecycleOwner的綁定發生在哪里呢?

3.3.1、Activity中的綁定

? Activity的直接子類ComponentActivityAppcompatActivity均自動完成了綁定的工作,我們以ComponentActivity為例看看相關的綁定代碼:

可見在ComponentActivitysetContentView被執行時,會將ActivityViewLifecycleOwner綁定其所在的WindowDecorView中,我們都知道Activity下面的所有View都是DecorView的子View,因此它們都可以直接通過谷歌開發人員提供的擴展函數直接訪問到最頂層的ActivityLifecycle

3.3.2、Fragment中的綁定

Activity類似,Fragment也采用了幾乎一致的綁定方式,只不過是將Lifecycle綁定在了FragmentView之上:

3.3.3、Dialog中的綁定

默認的DialogActivity是不支持ViewTreeLifecycleOwner的,因此谷歌的開發人員重新繼承實現了一個新的Dialog子類:ComponentDialog,其中的綁定大同小異,簡單看下源碼即可了解:

看來和Activity一樣,把LifecycleOwner綁定在了DecorView中。

3.3.4、意義與總結

那么谷歌的開發人員費盡心思的為以上的組件綁定ViewTreeLifecycleOwner有何用意呢?意義可大了,由于消除了組件之間的差異(均是通過View往上查找父控件直到找到LifecycleOwner的模式),我們不用在乎當前的View是在哪個控件中,都是統一通過findViewTreeLifecycleOwner()來獲取最頂層控件的生命周期。

例如下面的自定義View的代碼,無論在上述哪個控件中都可以用:

可見,開發者只需要關注生命周期本身,不再需要擔心不同組件之間的差異了。

4、結語

安卓原生的生命周期設計只能說是毛坯房都算不上的水平,然而通過「Lifecycle」庫的加持之后,開發者可以輕松訪問組件的生命周期,讓開發業務更加的合理與安全。

作為開發者的你,應該逐漸將重寫生命周期函數的方式逐漸過渡到「Lifecycle」的開發方式中來,在一些工具類亦或者其他業務類中,你也可以使用「Lifecycle」輔助強化與生命周期相關的業務。

Android 學習筆錄

Jetpack全家桶篇(內含Compose):https://qr18.cn/A0gajp
Android 性能優化篇:https://qr18.cn/FVlo89
Android Framework底層原理篇:https://qr18.cn/AQpN4J
Android 車載篇:https://qr18.cn/F05ZCM
Android 逆向安全學習筆記:https://qr18.cn/CQ5TcL
Android 音視頻篇:https://qr18.cn/Ei3VPD
OkHttp 源碼解析筆記:https://qr18.cn/Cw0pBD
Gradle 篇:https://qr18.cn/DzrmMB
Kotlin 篇:https://qr18.cn/CdjtAF
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知識體:https://qr18.cn/CyxarU
Android 核心筆記:https://qr21.cn/CaZQLo
Android 往年面試題錦:https://qr18.cn/CKV8OZ
2023年最新Android 面試題集:https://qr18.cn/CgxrRy
Android 車載開發崗位面試習題:https://qr18.cn/FTlyCJ
音視頻面試題錦:https://qr18.cn/AcV6Ap

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/164437.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/164437.shtml
英文地址,請注明出處:http://en.pswp.cn/news/164437.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

P6 C++控制流語句(continue, break, return)

前言 今天我們講的是控制流語句,本期內容是上期課程的延續。 控制流語句一般與循環語句一起工作,它們讓我們可以更好的控制這些循環的實際運行。 我們有三個主要的控制流語句可以使用,continue 、break 和 return,它們有不同的…

Python 訂閱 image_transport 壓縮后的深度圖 compressedDepth

image_transport 是ros的一個圖像處理工具,可以很方便地進行圖像數據的壓縮,可惜它目前并不支持python 當你如下安裝了image_transport及其plugin后 sudo apt install ros-foxy-image-transport*運行 ros2 run image_transport list_transports可看到如下內容 Declared tr…

打印樓梯,同時在樓梯上方打印兩個笑臉。

#include<stdio.h> int main() { int i,j; printf("\1\1\n"); /*輸出兩個笑臉*/ for(i1;i<11;i) { for(j1;j<i;j) printf("%c%c",219,219); printf("\n"); } return 0; }

【C++】POCO學習總結(五):功能介紹

【C】郭老二博文之&#xff1a;C目錄 1、POCO 簡介 github&#xff1a;https://github.com/pocoproject/poco 官網&#xff1a;https://pocoproject.org/index.html POCO第一個版本于 2005 年 2 月發布 POCO完全免費&#xff1a;POCO C 庫根據 Boost 軟件許可證獲得許可。非…

QMI8658A(6軸)-EVB 評估板-使用說明書

QMI8658A6<6軸>-EVB 評估板-使用說明書 0.前言 1.硬件準備 1.1 I2C 接口 1.2 USART 接口 1.3 引腳序號功能定義 2.程序運行 0.前言 【相關博文】 【QMI8658 - 姿態傳感器學習筆記 - Ⅰ】 【QMI8658 - 姿態傳感器學習筆記 - Ⅱ】 【QMI8658 - 姿態傳感器學習…

基于單片機的光伏發電并網系統設計(論文+源碼)

1.系統設計 片作為主控制器。由于太陽能板本身的能量輸出受到負載影響&#xff0c;因此需要在太陽能板后面加入一級DC/DC電路&#xff0c;來實現最大功率跟蹤&#xff0c;以提高整個系統的效率。接著&#xff0c;由于光伏逆變器需要產生220V的交流電給居民使用&#xff0c;因此…

[MySQL] MySQL 表的增刪查改

本篇文章對mysql表的增刪查改進行了詳細的舉例說明解釋。對表的增刪查改簡稱CRUD : Create(創建), Retrieve(讀取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;刪除&#xff09;。其中重點是對查詢select語句進行了詳細解釋&#xff0c;并且通過多個實際例子來幫助…

香港科技大學廣州|先進材料學域博士招生宣講會—華中科技大學大學專場!!!(暨全額獎學金政策)

“跨學科融合創新&#xff0c;引領新興與未來行業的突破與發展——先進材料學域” 世界一流的新型可持續材料創新研究 夯實的先進材料領域國際學術影響力 教授親臨現場&#xff0c;面對面答疑解惑助攻申請&#xff01; 一經錄取&#xff0c;享全額獎學金1.5萬/月&#xff01; …

【性能優化】JVM調優與寫出JVM友好高效的代碼

&#x1f4eb;作者簡介&#xff1a;小明java問道之路&#xff0c;2022年度博客之星全國TOP3&#xff0c;專注于后端、中間件、計算機底層、架構設計演進與穩定性建設優化&#xff0c;文章內容兼具廣度、深度、大廠技術方案&#xff0c;對待技術喜歡推理加驗證&#xff0c;就職于…

面試:Kafka相關問題

文章目錄 簡單介紹kafkakafka應用場景為什么需要zookeeperZookeeper 對于 Kafka 的作用是什么&#xff1f;kafka高效的原因kafka的特點kafka的核心組成Kafka中的Topic和Partition有什么關系&#xff1f;Kafka的消費消息是如何傳遞的&#xff1f;Kafka 的多副本機制了解嗎&#…

STM32:基本定時器原理和定時程序

一、初識定時器TIM 定時器就是計數器&#xff0c;定時器的作用就是設置一個時間&#xff0c;然后時間到后就會通過中斷等方式通知STM32執行某些程序。定時器除了可以實現普通的定時功能&#xff0c;還可以實現捕獲脈沖寬度&#xff0c;計算PWM占空比&#xff0c;輸出PWM波形&am…

Vue3 + Vite + TSX + vue3-ace-editor 踩坑

前言 由于 ace-editor 官網并沒有提供各個前端框架Vue&#xff0c;React&#xff0c;Angular的直接使用的適配版本&#xff0c; 所以本次使用的vue3-ace-editor 是個人開源者維護的版本&#xff0c;原生是支持 SFC 模版用的&#xff0c;由于我這里習慣使用 JSX 或 TSX的方式&a…

【03】ES6:解構賦值

一、數組的解構賦值 ES6 允許按照一定模式&#xff0c;從數組和對象中提取值&#xff0c;對變量進行賦值&#xff0c;這被稱為解構&#xff08;Destructuring&#xff09;。 1、基本使用 遵循 “模式匹配” &#xff0c;索引值相同的完成賦值 // 為變量賦值&#xff0c;只能…

Centos7 Python環境和yum修復

1、刪除現有殘余包 [rootlocalhost ]# rpm -qa|grep python|xargs rpm -ev --allmatches --nodeps[rootlocalhost ]# rpm -qa|grep yum|xargs rpm -ev --allmatches --nodeps[rootlocalhost ]# whereis python |xargs rm -frv[rootlocalhost ]# whereis python ##驗證清除&…

mybatis注解方式動態標簽時有特殊符號,出現元素內容必須由格式正確的字符數據或標記組成

原始代碼demo Select("SELECT COUNT(1) FROM AAAA WHERE name #{nage} AND age< 4") public Integer sumXxxxx(String nage, String age);現需求改為nage可以為空&#xff0c;因此使用了動態拼接 Select("<script> SELECT COUNT(1) FROM AAAA WHERE …

SWT/Jface(2): 表格的編輯

前言 上節說到, 創建和渲染表格需要如下幾個步驟: 接收源數據數組(也可以是單個對象或者其他集合類型): TableViewer.setInput(Object)渲染接收的數據 渲染表頭: TableViewer.setLabelProvider(IBaseLabelProvider)渲染內容: TableViewer.setContentProvider(IContentProvide…

java.lang.IllegalArgumentException: java.net.UnknownHostException: xxx

windows系統下連接hdfs進行操作時&#xff0c;上來就出現java.lang.IllegalArgumentException: java.net.UnknownHostException: xxx java.lang.IllegalArgumentException: java.net.UnknownHostException: liujianat org.apache.hadoop.security.SecurityUtil.buildTokenServ…

Keil Vision5—新建工程project

注意&#xff1a;創建的工程目錄必須是純英文目錄 目錄 1.開始配置 2.為該路徑下新建個文件夾 3.選擇器件 4.工程配置 4.右擊魔術棒&#xff0c;設置參數 ?編輯 &#xff08;1&#xff09;target配置 &#xff08;2&#xff09;output配置 &#xff08;3&#xff09;c…

字符串結尾空格比較相關參數BLANK_PAD_MODE(DM8:達夢數據庫)

DM8:達夢數據庫 字符串結尾空格比較相關參數BLANK_PAD_MODE 環境介紹1 BLANK_PAD_MODE01.1 初始化數據庫1.2 創建測試表 T0 2 BLANK_PAD_MODE12.1 初始化數據庫2.2 創建測試表 T1 3 BLANK_PAD_MODE只對字段varchar類型生效3.1 BLANK_PAD_MODE 對char 類型對比無效3.2 在兩個數據…

計算機中了halo勒索病毒怎么清除,halo勒索病毒解密數據恢復

科技的進步加快了企業發展的步伐&#xff0c;網絡技術的不斷應用為企業的生產運營提供了極大幫助&#xff0c;但隨之而來的網絡安全威脅也不斷增加&#xff0c;近期&#xff0c;云天數據恢復中心接到很多企業的求助&#xff0c;企業的計算機服務器遭到了halo勒索病毒攻擊&#…