《Android開發藝術探索》讀書筆記-Android的生命週期和啓動模式

Activity的生命週期全面分析

Activity的生命週期可分爲兩部分,一種是在正常的執行過程中的生命週期,另一種則是在執行過程中發生異常情況的生命週期,這兩種情況在實際開發中都有着的極爲重要的作用,因此弄清楚這裏面的關係就顯得十分必要。

典型情況下的生命週期

  • 由於正常情況下的生命週期已經是一個老生常談的問題,這裏也就不再多費口舌,給出一張Google官方的流程圖便能看出其調用順序。

activity生命週期

  • 額外補充的說明:

    • 針對一個特定的Activity,第一次啓動,回調如下:onCreate -> onStart -> onResume
    • 當用戶打開新的Activity或者切換到桌面的時候,回調如下:onPause -> onStop特殊情況:新的Activity採用了透明主題,那麼當前Activity不會調用onStop
    • 當用戶再次回到原Activity時,回調如下:onRestart -> onStart -> onResume
    • 當用戶按back鍵回退時,回調如下:onPause -> onStop -> onDestroy
    • 當Activity被系統回收後再次打開,此時生命週期任然是和第一次啓動一樣,不過其過程多了參數的保存和恢復,後面會集體講到
    • 各個生命週期都存在着一一配對的關係,可以看成是對稱的,正因爲有這個特性,很多時候在對稱的回調裏做註冊和反註冊
  • onStart對應onStoponResume對應onPause,使用中,二者極其類似,其之所以這樣存在,是應爲出發的角度不同。前者從Activity是否可見的角度來回調,後者從Activity是否位於前臺來回調。

  • 一個有趣的問題:當前 Activity 爲 A,在此時打開 Activity B,那麼 B 的onResume先執行還是 A 的onPause先執行?

    回答這個問題並不難,這裏就是採用 閱讀源碼 + 實驗驗證 的方式,在源碼中新的Activity啓動之前,棧頂的Activity需要先onPause後,新的 Activity 才能啓動。同時在 Activity 的生命週期的回調中輸出日誌,驗證正確性。最後的結論就是 A 的 Activity 先onPause,然後 B 的 Activity 再啓動。onPauseonStop中都不可執行耗時操作,尤其是在onPause

  • 另外,在Android的說明文檔中還有着這樣一張圖,看圖說話

enter description here

異常情況下的生命週期

情況1:資源相關的系統配置發生改變導致 Activity 被殺死並重新創建
  • 一個簡單的例子,在橫豎屏切換的時候,在默認配置的情況下,Activity 就會銷燬並重建,那麼在這個過程中發生的事情自然就是數據的保存和恢復了。

  • 這裏依舊是調用了之前圖片中的onSaveInstanceState,用以保存數據,這個調用發生在onStop之前,但其順序與onPause沒有關係,可能在之前也可能在之後。而當 Activity 被重建之時,會調用onRestoreInstanceState來恢復數據,其調用時機在onStart之後,值得注意的是,這個狀態值Bundle會同時傳遞給onCreateonRestoreInstanceState,也就是說在沒有數據保存的時候,onCreate的形參Bundlenull,而當有數據保存的時候則不爲null

  • 在系統調用onSaveInstanceStateonRestoreInstanceState的時候,會默認爲我們保存當前 Activity 的視圖結構以及恢復數據。這其中使用的是委託思想,一級一級委託上層保存元素,在一級一級通知下層恢復數據。這種思想在view繪製和事件分發中都有着應用。

情況2:資源內存不足導致低優先級的 Activity 被殺死
  • Activity有着優先級一說,優先級越高,越不容易被系統殺死。按照其高低可分爲以下三種

    • 前臺 Activity——正在和用戶交互的 Activity,這個 Activity 幾乎不可能被系統殺死,殺死的話會嚴重影響用戶體驗
    • 可見但非前臺 Activity——比如 Activity 中彈出一個對話框,導致 Activity 可見但是位於後臺無法直接和用戶直接交互,這種 Activity 也不容易被殺死
    • 後臺 Activity ——已經被暫停的 Activity,比如執行了 onStop,優先級最低,在內存不足的時候會優先回收此類 Activity
  • 另外說明一點,在沒有四大組件執行的時候,進程就會很快被殺死。

禁止重建
  • 就針對以上 Activity 的重建來說,能否在某些內容發生改變時不重建 Activity 呢?答案是肯定的,這時候就需要在AndroidManifest.xml中配置android:configChanges了,其目的是告訴系統不要重建在指定的變化時不要重建 Activity,其配置值有很多,但常用的只有screenSize(屏幕尺寸信息發生改變,例如屏幕旋轉),smallestScreenSize(物理屏幕尺寸發生改變,例如外接屏幕)和keyboardHidden(鍵盤的可訪問性發生改變,例如調出虛擬鍵盤)

Activity的啓動模式

啓動模式的必要性:爲了更好的對任務棧進行管理

Activity的LaunchMode

  • standard:標準模式,系統默認的啓動模式,每次伴隨 Activity 的啓動而創建一個新的實例,不管這個實例是否已經存在。在這種啓動模式下,注意兩點,第一:每次 Activity 的啓動都會調用完整的 Activity 生命週期流程。第二:誰啓動了這個 Activity,那麼這個 Activity 就會運行在啓動它的那個 Activity 所在的棧中,用一個例子來說明,Activity A 啓動了 Activity B(標準模式),那麼 B 就會進入 A 所在的棧中。這裏引申出一個問題:用 ApplicationContext 啓動標準模式的 Activity 會報錯,這是因爲非 Activity 類型的 Context 是不存在任務棧一說的,所以就會報錯,當添加了 FLAG_ACTIVITY_NEW_TASK 時,便不會有問題,這是因爲當存在這個Flag的時候,啓動時就會創建出一個新的任務棧,而這時候啓動的 Activity 實際上是以 singleTask 模式啓動的。
  • singleTop:棧頂複用模式。這種模式下,新的 Activity 已經位於棧頂,那麼此 Activity 實例將不再創建,並且回調onNewIntent方法。但如果新的 Activity 不在棧頂,不管其是否存在都會新建 Activity 的實例,並將新建的實例置於棧頂。
  • singleTask:棧內複用模式。簡單來說,這就是 Activity 實例的單例模式,只要棧中存在 Activity 實例,就不會創建這個 Activity 的新實例,同時會回調onNewIntent。對於Activity A,當棧中不存在 A 需要的任務棧,那麼便會創建一個新的任務棧,同時將 A 放入新的任務棧中;如果存在 A 需要的任務棧,此時若 A 的實例存在,那麼 A 會被調到棧頂(會清空 A 之上的實例),並且執行onNewIntent,如果 A 的實例不存在,那麼便會創建 A 的實例,並將其放入棧中。
  • singleInstance:單實例模式。加強的singleTask模式,擁有其全部特性。具有此模式的 Activity 只能單獨位於一個任務棧,並且只有這麼一個實例存在。

默認情況下所有 Activity 所需的任務棧的名字爲應用的包名,而任務棧可以通過TaskAffinity標識,TaskAffinity屬性主要和singleTask啓動模式或者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。任務棧分爲前臺任務棧與後天任務棧,後臺任務棧中的 Activity 位於暫停狀態,用戶可以通過切換將後臺任務棧再次調到前臺。

TaskAffinitysingleTask啓動模式配對使用時,他是具有該模式的 Activity 的目前任務棧的名字,待啓動的 Activity 就會運行在名字和TaskAffinity相同的任務棧中。

TaskAffinityallowTaskReparenting結合使用的時候,當一個應用 A 啓動了應用 B 的某個 Activity 後,如果這個 Activity 的allowTaskReparenting屬性爲true的話,那麼應用 B 被啓動後,此 Activity 會直接從應用 A 的任務棧轉移到應用 B 的任務棧中。

指定 Activity 的啓動模式主要有兩種方式,一種是在AndroidManifest.xml中指定,另外一種是通過Intent的標誌位來指定。

<activity
	android:name="com.cj5785.demo.MainActivity"
	android:configChanges="screenLayout"
	android:launchMode="singleTask"
	android:label="@string/app_name" />
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

二者的區別在於:

1. 優先級不同,代碼添加的優先級高於XML中添加,同時存在時,以代碼爲準
2. 範圍不同,XML方式無法設置`FLAG_ACTIVITY_CLEAR_TOP`,代碼無法設置`singleInstance`

查看任務棧信息:adb shell dumpsys activity

關於任務棧,當前臺任務棧都出棧時,回將後臺任務棧調至前臺。

Activity的Flags

Activity 的 Flags 有很多,除了幾個常用的,其餘可以在用到的時候去文檔查詢

FLAG_ACTIVITY_NEW_TASK

爲 Activity 指定singleTask啓動模式,效果和在XML中指定該啓動模式相同

FLAG_ACTIVITY_SINGLE_TOP

爲 Activity 指定singleTop啓動模式,效果和在XML中指定該啓動模式相同

FLAG_ACTIVITY_CLEAR_TOP

具有此標記的的 Activity,當其啓動時,在同一個任務棧中所有位於它上面的 Activity 都要出棧

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

具有這個標記的 Activity 不會出現在歷史 Activity 列表中,等同於XML中的android:excludeFromRecents="true"

IntentFilter的匹配規則

Activity 的啓動分爲兩種,隱式調用和顯式調用,而 IntentFilter 正是用來匹配隱式調用的。IntentFilter 中的過濾信息有action,category,data。只有一個 Intent 同時匹配一組action,category,data纔算完全匹配,也只有完全匹配才能啓動目標 Activity,一個 Activity 可以包含多個intent-filter,而intent-filter中正是包含了三個信息的組合。

action的匹配規則

action 是一個字符串,系統預定義了一些 action,也可以自定義 action。action的匹配要求字符串完全一致(區分大小寫),一個intent-filter可以存在多個action,只要能夠與其中任意一個完全一致則視爲匹配成功。也就是說,action 的匹配要求 Intent 中的 action 存在且必須和過濾規則中的其中一個 action 相同。

category的匹配規則

category 是一個字符串,系統預定義了一些 category,也可以自定義 category。category 匹配要求 Intent 中如果有 category,那麼所有的 category 都必須和過濾規則中的其中一個 category 相同。如果不寫,那麼在startActivity的時候會默認加上android.intent.category.DEFAULT,爲了能夠接受隱式調用,也必須加上android.intent.category.DEFAULT

data的匹配規則

data 的匹配規則與 action 類似,如果過濾規則中定義了 data,那麼 Intent 中也必須要定義可匹配的 data。

data 的語法:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />

data 由兩部分組成,mimeType 和 URI。

URI的默認值爲content和file,也就是說如果指定了,那麼就必須是content或者file

<data android:mimeType="image/*" />
intent.setDataAndType(Uri.parse("file://abc"), "image/*");

intent的setDatasetType會使得後調用的清除先調用的值

隱式調用啓動 Activity,如果沒有能夠匹配的項,就會報錯ActivityNotFoundException,這種情況一般使用PackageManagerresolveActivity或者IntentresolveActivity方法先做判斷,找不到匹配項則會返回null,其參數第二位爲標誌爲,一般設置爲MATCH_DEFAULT_ONLY,以防止匹配到不含 default category 的 Activity,而那些 Activity 是無法通過隱式調用啓動的。

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