Android Activity使用拾遺

一、onWindowFocusChanged

有時我們需要測量一個Activity多長時間才能顯示出來,那麼在代碼中打點計時的時機選在哪兒呢?在onCreate和onResume執行完成後,Activity的界面仍不可見,在onResume之後,framework還會回調一個叫onWindowFocusChanged的函數,它表示用戶是否已經可以與Activity的界面進行交互了。onWindowFocusChanged為true意味著Activity的界面已經能夠被用戶看到了(自然也能和用戶交互了)。實際上activity變為visible的時間點出現在onWindowFocusChanged之前,但是這個狀態只能在ActivityManagerService中獲取,在客戶端還是只能通過onWindowFocusChanged作為界面可見或不可見的標誌。

Advertisements

在界面變為不可見時,先調用onPause,然後再是onWindowFocusChanged為false。這也容易理解,onPause本來就是Activity被部分遮住時調用的,調用完onPause后才會讓界面不能與用戶繼續交互。

如果想加快Activity的啟動時間,把在onCreate里一些耗時操作挪到onResume里,有時會發現並沒有什麼變化,所以如果要想先讓Activity的界面儘快展現給用戶還是得把這些邏輯放在onWindowFocusChanged變為true之後執行。

二、onUserLeaveHint

這個回調函數主要用來監聽按Home鍵退出到桌面的動作,發生在onPause之前。在啟動一個新的Activity時,ActivityStackSupervisor里會調用startActivityUncheckedLocked,在它裡面會給mUserLeaving賦值。mUserLeaving用於指示當前activity退到後台時函數onUserLeaving是否被調用。

Advertisements

可見,只有當設置了FLAG_ACTIVITY_NO_USER_ACTION標誌時mUserLeaving才會為false,其他情況下mUserLeaving均為true,也就是onUserLeaveHint會被調用,註釋里也說了onUserLeaveHint會在onPause之前被調用。

三、taskAffinity

task是一組用來處理某一任務的Activity的集合,位於一個回退棧內,當新啟動一個應用時framework就會創建一個新的task來承載相應的Activity。taskAffinity用來描述Activity的親和性,即Activity屬於哪個task。如果在AndroidManifest.xml的application中沒顯式定義taskAffinity,那麼預設的affinity名字就是包名。同一affinity的Activity會被放在同一task里,task的affinity名字由根Activity的taskAffinity決定,如果根Activity沒設taskAffinity屬性,則affinity就由application的affinity決定。

如果taskAffinity設為空字元串,那說明該Activity和其他的task都不存在親和性。不同應用的Activity也可以設置相同的affinity,這樣當啟動后它們就會位於同一task中。

四、launchMode

1. standard

默認的launchMode,可以實例化多次。

2.singleTop

使用這種launchMode的情況是,如果要啟動的Activity已經在棧的最頂端,那麼startActivity時不再會重新調用它的onCreate,而是調用它的onNewIntent。但如果要啟動的Activity不在棧頂,比如A –> B à C,這時C再啟動B,就會再實例化一個新的B,棧裡面變成A à Bà C à B。

如果A中點擊一個button來啟動B,但系統反應慢,用戶在極短的時間內點了兩次,如果是B是standard,則會有兩個B,如果是singleTop則只會有一個。更典型的場景是用戶使用外賣App支付了一筆訂單后,從支付頁面返回到訂單詳情頁面,如果訂單的狀態有發生變化,比如商家接單了,這時通知欄會有通知,點擊通知會跳入訂單詳情頁面。如果此時訂單詳情頁面是standard的話,那麼用戶按back鍵后發現還是在訂單詳情頁面,體驗就很差了。如果是singleTop,只需在onNewIntent里更新狀態信息,就可以顯示出最新的信息,而不必再新創建一個相同的Activity了。

3.singleTask

以這種模式啟動的Activity,如果在task中已經存在該Activity的實例,就調用它的onNewIntent方法,進入該task中。如果沒有該Activity的實例,就創建一個新的task,把該Activity作為新task的根Activity。所以以singleTask啟動的Activity不一定會新創建一個task。需要注意的是,即使Activity位於新的task里,按back鍵仍然會回到啟動它的上一個Activity中,雖然它們不在同一個task里。

如果singleTask的Activity的taskAffinity和現有task的affinity相同,那就直接在現有的task里創建該Activity,所以以singleTask啟動的Activity未必就是位於棧底的根Activity。

4.singleInstance

以這種模式啟動的Activity自己獨佔一個task,如果已經有該Activity的實例,再次啟動時會調它的onNewIntent。但以singleInstance啟動的Activity按back的行為卻與singleTask不同。比如 A啟動B,B是singleInstance,B再啟動C,則B自己單獨位於一個新的task中,A和C位於一個task中,則按back鍵時,從C不會退回到B,而是先退回到A,再按back鍵再退回到B。

注意,在代碼中也可以設置Intent的flag來控制Activity的啟動模式,代碼中動態設置的比AndroidManifest.xml中靜態寫的android:lauchMode優先順序高。

五、Intent的flags

1.FLAG_ACTIVITY_NEW_TASK

為Activity新創建一個task,如果startActivity的context不是Activity,而是service或Application,那一定要加上這個flag。以這個flag啟動Activity的行為跟launchMode為singleTask的一致。但並不是每次都新創建一個task,把Activity放在裡面,而是會先找是否已經有taskAffinity相同的task,如果有就把要啟動Activity放在該task里,如果沒有taskAffinity相同的task,才會新建一個task。

2.FLAG_ACTIVITY_SINGLE_TOP

作用同singleTop。

3.FLAG_ACTIVITY_CLEAR_TOP

啟動Activity時,如果所在task里已經有該Activity的實例,則會清除在它上面所有的Activity。例如,task里的Activity啟動關係是A à B à C à D,然後在D啟動B時,加上了FLAG_ACTIVITY_CLEAR_TOP標誌,在B啟動后task里就只有A和B了,C和D被清除了。如果B的launchMode是standard,那麼當它收到Intent后,會先銷毀掉原來B的實例,然後重新onCreate構建一個新的B;如果B是singleTask類型,那麼會保留原有的B的實例,調用它的onNewIntent,傳入新的Intent。

4.FLAG_ACTIVITY_CLEAR_TASK

啟動Activity時,會清除所在task里其他所有的Activity。例如,task里的Activity啟動關係是A à B à C,在B啟動C時,加上FLAG_ACTIVITY_CLEAR_TASK,那麼在C啟動后,task里就只剩C了,A、B被清除了。如果這個應用中只有這一個task,那麼在C中按back鍵就退回到桌面了。

5.FLAG_ACTIVITY_REORDER_TO_FRONT

以該標誌啟動的Activity如果已經在task中存在,會被移動到棧頂,其他的Activity順序不變。如果在task中不存在,則新建一個。例如,task里的Activity啟動關係是A à B à C à D,在D啟動B時,加上這個flag,則B就被移到了棧頂,task里就變成了A à C à D à B了。如果B已經調了finish,但系統還沒來得及把它從task中移出,這時以FLAG_ACTIVITY_REORDER_TO_FRONT方式啟動B,則無法啟動B。只有當B destroy以後才可以啟動B。

6.FLAG_ACTIVITY_NO_HISTORY

這個FLAG可以讓啟動的Activity一旦退出,就finish掉,不再存在於棧中。例如,Activity的啟動關係是A àB àCà D,在B啟動C時加上FLAG_ACTIVITY_NO_HISTORY,在C啟動完D后,task里仍然是 A à B à C àD的棧布局,C並沒從棧中清除。當在D中按back鍵,不會退到C而是退到B,這時dumpsys activity會發現task中沒有C了,只有A和B,在D中按back鍵時,C就finish掉了。

7.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

以這個flag啟動的Activity將不會在最近啟動的應用列表中出現。

8.FLAG_ACTIVITY_FORWARD_RESULT

我們用startActivityForResult和setResult在兩個Activity之間傳遞數據,如果中間還隔著另一個Activity,比如A à B à C,要想在A和C之間用startActivityForResult和setResult的話,就要在B啟動C時加上這個參數。也就是A正常的startActivityForResult啟動B,B以FLAG_ACTIVITY_FORWARD_RESULT方式啟動C,在C中setResult,這樣當從C退回B,再退回到A,即C和B都finish后,A的onActivityResult會收到C中setResult傳的值。

9.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

這個是從最近任務列表中啟動Activity時由系統設置的,第三方應用不會用到這個標誌,系統的SystemUI在最近任務列表中才會設這個標誌。

10.FLAG_ACTIVITY_NO_ANIMATION

要啟動Activity將不執行入場動畫。

11.FLAG_ACTIVITY_TASK_ON_HOME

以這個flag啟動的Activity所在的task將會位於Home所在的task之上,也就是說在這個Activity中按back鍵會回到home,而不是啟動它的那個Activity。例如,A à B,B的taskAffinity與A不同,A啟動B時同時加上了FLAG_ACTIVITY_TASK_ON_HOME和FLAG_ACTIVITY_NEW_TASK這兩個標誌,那麼A和B位於不同的task中,如果僅是以new task方式啟動B,那麼即使B和A不在一個task中,那麼在B中按back鍵還是會退到A的,但加上了FLAG_ACTIVITY_TASK_ON_HOME后,按back鍵就退回到home了,此時A已經被移動到了後台。

Advertisements

你可能會喜歡