Activity詳解

3.2 Activity詳解

3.2.1 生命周期分析

典型情況下生命周期分析

  1. 一般情況下,當當前Activity從不可見重新變為可見狀態時,onRestart()方法就會被調用。
  2. 當用戶打開新的Activity或者切換到桌面的時候,回調如下:onPause()->onStop(),但是如果新Activity采用了透明主題,那么onStop()方法不會被回調。當用戶再次回到原來的Activity時,回調如下:onRestart()->onStart()->onResume()。
  3. onResume()和onPause()方法對應,它們是從Activity是否位于前臺這個角度來回調的;onStart()和onStop()對應,它們是從Activity是否可見這個角度來回調的。
  4. 從Activity A進入到Activity B,回調順序是onPause(A)->onCreate(B)->onStart(B)->onResume(B)->onStop(A),所以不能在onPause方法中做重量級的操作。

異常情況下生命周期分析

  1. onSaveInstanceState方法只會出現在Activity被異常終止的情況下,它的調用時機是在onStop之前,它和onPause方法沒有既定的時序關系,可能在它之前,也可能在它之后。當Activity被重新創建的時候,onRestoreInstanceState會被回調,它的調用時機是onStart之后。系統只會在Activity即將被銷毀并且有機會重新顯示的情況下才會去調用onSaveInstanceState方法。當Activity在異常情況下需要重新創建時,系統會默認為我們保存當前Activity的視圖結構,并且在Activity重啟后為我們恢復這些數據,比如文本框中用戶輸入的數據、listview滾動的位置等,這些view相關的狀態系統都會默認為我們恢復。
  2. android:configChanges="xxx"屬性,常用的主要有下面三個選項:
    local:設備的本地位置發生了變化,一般指切換了系統語言;
    keyboardHidden:鍵盤的可訪問性發生了變化,比如用戶調出了鍵盤;
    orientation:屏幕方向發生了變化,比如旋轉了手機屏幕。
    配置了android:configChanges="xxx"屬性之后,Activity就不會在對應變化發生時重新創建,而是調用Activity的onConfigurationChanged方法。

3.2.2 Activity的啟動模式

Task

Task是一個從用戶角度出發的概念,它是一些activity的組合,它們組合起來是為了讓用戶完成某一件工作(或者說操作)。task內的activity們以棧的形式組織起來,即backStack。棧內的activity可以來自不同的app,因此可以是運行在不同的進程,但是它們都屬于同一個task內。

安卓系統是實時多task系統,用戶可以隨意在多個task之間切換。當一個task的棧內所有activity都pop之后,task也就銷毀了。有時系統為了回收內存,會銷毀activity,但是不會銷毀task。

Activity在manifest中有launchMode選項,intent可以指定intent flag,前者供被啟動者使用,后者供啟動者使用,同時使用時后者會覆蓋前者。

affinity

affinity對于Activity來說就好像它的身份證一樣,可以告訴所在的task,自己屬于這個task中的一員;擁有相同affinity的多個Activity理論上屬于同一個task,task自身的affinity決定于根Activity的affinity值。默認情況下,一個應用內的所有Activity都具有相同的affinity,都是從Application(application的taskAffinity屬性)繼承而來,而默認情況下Application的affinity是<manifest>中的包名,我們可以為<application>設置taskAffinity屬性值,這樣可以應用到<application>下的所有<activity>,也可以單獨為某個Activity設置taskAffinity例如:在系統自帶的Browser中,package為com.android.browser,但是<application>卻自定義一個taskAffinity屬性值:

<application
    android:name="Browser"
    android:taskAffinity="android.task.browser"
    .../>

affinity主要應用在以下場合:

  1. 根據affinity重新為Activity選擇宿主task(與allowTaskReparenting屬性配合工作);
  2. 啟動一個Activity過程中Intent使用了FLAG_ACTIVITY_NEW_TASK標記,根據affinity查找或創建一個新的具有對應affinity的task。

launchMode

Activity有以下四種死動模式:

  1. standard:默認模式,允許多實例。
  2. singleTop:相比于standard,有新的啟動請求時,當目標activity處于當前棧頂時,會調用onNewIntent()而不創建新實例,其他情況都和standard一致。
  3. singleTask:設置了”singleTask”啟動模式的Activity,它在啟動的時候,會先在系統中查找具有相同affinity的task,如果不存在這樣的task,則會在一個新的task中啟動該Activity;如果存在這樣的task,它就會在這個task中啟動,如果該task中已存在該Activity實例,則會把該Activity實例調到棧頂,并調用它的onNewIntent()方法(它之上的Activity會被迫出棧,所以singleTask模式具有FLAG_ACTIVITY_CLEAR_TOP效果);如果該Activity實例不存在,那么就創建Activity實例并把它壓入棧中。
  4. singleInstance:和singleTask相比,不同點在于singleInstance模式下,activity所在的task只會有這一個activity。

注意事項

  1. 當任務棧中沒有任何Activity的時候,系統就會回收這個任務棧。
  2. 從非Activity類型的Context(如Application、Service等)中以standard模式啟動新的Activity是不行的,因為這類context并沒有任務棧,所以需要為待啟動Activity指定FLAG_ACTIVITY_NEW_TASK標志位。
  3. 參數TaskAffinity用來指定Activity所需要的任務棧,意為任務相關性。默認情況下,所有Activity所需的任務棧的名字為應用的包名。TaskAffinity屬性主要和singleTask啟動模式或者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。當TaskAffinity和singleTask啟動模式配對使用的時候,它是具有該模式的Activity的目前任務棧的名字,待啟動的Activity會運行在名字和TaskAffinity相同的任務棧中;當TaskAffinity和allowTaskReparenting結合的時候,當一個應用A啟動了應用B的某個Activity C后,如果Activity C的allowTaskReparenting屬性設置為true的話,那么當應用B被啟動后,系統會發現Activity C所需的任務棧存在了,就將Activity C從A的任務棧中轉移到B的任務棧中。
  4. 設置啟動模式既可以使用xml屬性android:launchMode,也可以使用代碼intent.addFlags()。區別在于限定范圍不同,前者無法直接為Activity設置FLAG_ACTIVITY_CLEAR_TOP標識,而后者無法為Activity指定singleInstance模式。

Intent Flags

在android.content.Intent中定義了若干個flags,其中最重要的有以下幾個:

  1. FLAG_ACTIVITY_NEW_TASK:當Intent對象包含這個標記時,首先系統依據目標Activity的taskAffinity屬性進行匹配,如果找到一個task的taskAffinity與之相同,就將目標Activity壓入此task中,否則創建一個新的task,并將該task的taskAffinity設置為目標Activity的taskActivity,將目標Activity放置于此task。注意,如果同一個應用中Activity的taskAffinity都使用默認值或都設置相同值時,應用內的Activity之間的跳轉使用這個標記是沒有意義的,因為當前應用task就是目標Activity的宿主。
  2. FLAG_ACTIVITY_CLEAR_TOP:當Intent對象包含這個標記時,如果在棧中發現存在目標Activity實例,則清空這個實例之上的其他Activity,使其處于棧頂。而目標Activity是關閉重建(onCreate)還是使用已存在的實例(onNewIntent),具體分析如下所示:standard啟動模式下,如果Intent沒有使用FLAG_ACTIVITY_SINGLE_TOP標記,那么它將關閉后重建(onCreate),如果使用了這個FLAG_ACTIVITY_SINGLE_TOP標記,則會使用已存在的實例(onNewIntent);對于其他啟動模式,無需再使用FLAG_ACTIVITY_SINGLE_TOP,它都將使用已存在的實例,Intent會被傳遞到這個實例的onNewIntent中。
  3. FLAG_ACTIVITY_SINGLE_TOP:當Intent對象包含這個標記時,目標task中存在目標Activity實例并且位于棧的頂端時,不再創建一個新的,直接利用這個實例。(見2中的分析)
  4. FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:當Intent對象包含這個標記時,則目標Activity以及該Activity其上的所有Activity都會在task重置時被清除出task。當一個后臺的task重新回到前臺時,系統會為這個動作附帶一個FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,意味著必要時重置task,這時FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就會生效。經過測試發現,對于一個處于后臺的應用,如果在桌面點擊應用,這個動作中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,而從任務欄中點擊該應用,這個動作不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,所以前者會清除,后者不會。關于這個標記,可以下圖示之:

這個標記對于應用存在分割點的情況會非常有用。比如我們在應用主界面要選擇一個圖片,然后我們啟動了圖片瀏覽界面,但是把這個應用從后臺恢復到前臺時,為了避免讓用戶感到困惑,我們希望用戶仍然看到主界面,而不是圖片瀏覽界面,這個時候我們就要在轉到圖片瀏覽界面時的Intent中加入此標記。

  1. FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:當Intent對象包含這個標記時,這個標記在以下情況下會生效:1.啟動Activity時創建新的task來放置Activity實例;2.已存在的task被放置于前臺。系統會根據affinity對指定的task進行重置操作,task會壓入某些Activity實例或移除某些Activity實例。我們結合上面的FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET可以加深理解。

Manifest中task相關屬性

在AndroidManifest中定義了幾個常見的task相關屬性,它們分別代表了task內部不同的行為特征,我們就來逐個介紹一下:

  1. android:allowTaskReparenting,這個屬性用來標記一個Activity實例在當前應用退居后臺后,是否能從啟動它的那個task移動到有共同affinity的task,true表示可以移動,false表示它必須呆在當前應用的task中,默認值為false。如果<activity>沒有設定此屬性,則<application>上的此屬性會對此Activity起作用。例如在一個應用中要查看一個web頁面,會啟動系統瀏覽器Activity,這個瀏覽器Activity實例和當前應用處于同一個task,當該應用退居后臺之后再次從桌面中啟動該應用,將不會再看到這個瀏覽器Activity實例,因為此時這個瀏覽器Activity實例已經重新宿主到Browser應用的task內,而如果此時啟動Browser應用,就會發現,第一個界面就是我們剛才打開的web頁面,證明了這個瀏覽器Activity實例確實是宿主到了Browser應用的task內。需要注意的是,如果該應用退居后臺之后,沒有再次啟動,而是直接啟動瀏覽器,將不會出現以上現象。重新宿主的動作發生在該應用再次啟動的過程中。
    android:allowReparenting的效果圖如下:
  1. android:alwaysRetainTaskState,這個屬性用來標記應用的task是否保持原來的狀態,“true”表示總是保持,“false”表示不能夠保持,默認為“false”。此屬性只對task的根Activity起作用,其他的Activity都會被忽略。默認情況下,如果一個應用在后臺呆的太久,用戶從桌面重新進入該應用時,系統就會對該應用的task進行清理,除了根Activity,其他Activity都會被清除出棧,但是如果在根Activity中設置了此屬性之后,用戶再次啟動應用時,仍然可以看到上一次操作的界面。
    這個屬性對于一些應用非常有用,例如Browser可能打開很多的tab,用戶不想丟失這些狀態,使用這個屬性就極為恰當。
  2. android:clearTaskOnLaunch,這個屬性用來標記是否從task清除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默認為“false”。同樣,這個屬性也只對根Activity起作用,其他的Activity都會被忽略。如果設置了這個屬性為“true”,每次用戶重新啟動這個應用時,都只會看到根Activity,task中的其他Activity都會被清除出棧。如果我們的應用中引用到了其他應用的Activity,這些Activity設置了allowTaskReparenting屬性為“true”,則它們會被重新宿主到有共同affinity的task中。
  3. android:finishOnTaskLaunch,這個屬性和android:allowReparenting屬性相似,不同之處在于allowReparenting屬性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch屬性是銷毀實例。如果這個屬性和android:allowReparenting都設定為“true”,則這個屬性勝出。

3.2.3 IntentFilter的匹配規則

IntentFilter中的過濾信息有action、category、data,為了匹配過濾列表,需要同時匹配過濾列表中的action、category、data信息,否則匹配失敗。一個過濾列表中的action、category、data可以有多個,所有的
action、category、data分別構成不同類別,同一類別的信息共同約束當前類別的匹配過程。只有一個Intent同時匹配action類別、category類別和data類別才算完全匹配,只有完全匹配才能成功啟動目標Activity。此外,一個Activity中可以有多個intent-filter,一個Intent只要能匹配任何一組intenf-filter即可成功啟動對應的Activity。

<intent-filter>
    <action android:name="com.ryg.charpter_1.c" />
    <action android:name="com.ryg.charpter_1.d" />

    <category android:name="com.ryg.category.c" />
    <category android:name="com.ryg.category.d" />
    <category android:name="android.intent.category.DEFAULT" />

    <data android:mimeType="text/plain" />
</intent-filter>
  1. action匹配規則:只要Intent中的action能夠和過濾規則中的任何一個action相同即可匹配成功,action匹配區分大小寫。
  2. category匹配規則:Intent中如果有category那么所有的category都必須和過濾規則中的其中一個category相同,如果沒有category的話那么就是默認的category,即android.intent.category.DEFAULT,所以為了
    Activity能夠接收隱式調用,配置多個category的時候必須加上默認的category。
  3. data匹配規則:
    data的結構很復雜,主要由mimeType和URI組成,語法大致如下:
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />

data匹配規則:Intent中必須含有data數據,并且data數據能夠完全匹配過濾規則中的某一個data。
URI有默認的scheme!
如果過濾規則中的mimeType指定為image/* 或者text/* 等這種類型的話,那么即使過濾規則中沒有指定URI,URI有默認的scheme是content和file,如果過濾規則中指定了scheme的話那就不是默認的scheme了。

<intent-filter>
    <data android:mimeType="image/*"/>
    ...
</intent-filter>
//URI默認值被覆蓋
<intent-filter>
    <data android:mimeType="image/*" android:scheme="http" .../>
    ...
</intent-filter>

如果要為Intent指定完整的data,必須要調用setDataAndType方法。不能先調用setData然后調用setType,因為這兩個方法會彼此清除對方的值。intent.setDataAndType(Uri.parse("file://abc"), "image/png");
data的下面兩種寫法作用是一樣的:

<intent-filter>
    <data android:scheme="file" android:host="www.github.com"/>
</intent-filter>

<intent-filter>
    <data android:scheme="file"/>
    <data android:host="www.github.com"/>
</intent-filter>

如何判斷是否有Activity能夠匹配我們的隱式Intent?

  1. PackageManager的resolveActivity方法或者Intent的resolveActivity方法:如果找不到就會返回null
  2. PackageManager的queryIntentActivities方法:它返回所有成功匹配的Activity信息
    針對Service和BroadcastReceiver等組件,PackageManager同樣提供了類似的方法去獲取成功匹配的組件信息,例如queryIntentServices、queryBroadcastReceivers等方法

有一類action和category比較重要,它們在一起用來標明這是一個入口Activity,并且會出現在系統的應用列表中。

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容