[學習筆記]Android開發藝術探索筆記:Activity的生命週期和啓動模式

Activity 生命週期的全面分析

典型情況下的生命週期分析

  1. onCreate : 生命週期第一個方法,可做一些初始化工作;
  2. onRestart : Activity 重新啓動,由不可見變爲可見;
  3. onStart : Activity 正在啓動,此時 Activity 已經可見,但沒有出現在前臺,還無法和用戶交互;
  4. onResume : Activity 可見,出現在前臺,並開始活動;
  5. onPause : Activity 正在停止,正常情況下 onStop 會緊接着被調用,但是該方法中不宜做耗時操作,因爲當該方法執行完畢後,新 Activity 的 onResume 方法纔會執行;打開新的 Activity 時,當前 Activity 的 onPause 方法先執行完畢,新 Activity 的 onCreate 方法纔會執行 ;
  6. onStop : Activity 即將停止,可以做一些耗時操作,但不能太耗時;
  7. onDestroy : Activity 即將被銷燬,這是 Activity 生命週期中最後一個回調,在這裏我們可以做一些回收工作和資源釋放;

注意當用戶打開新的 Activity 或者切換到桌面的時候,回調如下: onPause -> onStop ,這裏有一種特殊的情況,如果新的 Activity 採用了透明主題或 Dialog 主題,那麼當前的 Activity 不會回調 onStop 。
onStart和onStop是從activity是否可見的角度來回調的。onResume和onPause是從activity是否定位於前臺這個角度來回調的。

舊的Activity先onPause,新的Activity再啓動。

異常情況下的生命週期分析

資源相關的系統配置發生改變導致 Activity 被殺死並重新創建

當系統配置發生改變後,Activity 會被銷燬,其 onPause 、 onStop 、 onDestroy 均會被調用,同時由於 Activity 是在異常情況下終止的,系統會調用 onSaveInstanceState 來保存當前 Activity 的狀態。

這個方法的調用時機是在 onStop 之前,它和 onPause 沒有既定的時序關係,可能在 onPause 之前,也可能在 onPause 之後。

該方法只會在 Activity 被異常銷燬時纔會被調用,正常情況下不會調用該方法。

當 Activity 被重新創建後,系統會調用 onRestoreInstanceState ,並且把 Activity 銷燬時 onSaveInstanceState 方法所保存的 Bundle 對象作爲參數同時傳遞給 onRestoreInstanceState 方法和 OnCreate 方法。

因此,我們可以通過 onRestoreInstanceState 方法和 OnCreate 方法判斷 Activity 是否被重建了,進而恢復之前保存的數據,從時序上講 onRestoreInstanceState 的調用時機在 onStart 之後。

同時,在 onSaveInstanceState 和 onRestoreInstanceState 方法中,系統已經自動爲我們恢復了一些數據,如:用戶輸入的數據、 ListView 滾動的位置等,有關具體哪些信息被系統自動恢復,可以查看相應 View 的 onSaveInstanceState 和 onRestoreInstanceState 方法的具體實現。

資源內存不足導致低優先級的 Activity 被殺死

比較難模擬內存不足的情況,Activity 的優先級一般爲下面三種:

  1. 前臺 Activity ,和用戶交互的 Activity 優先級最高;
  2. 可見非前臺 Activity ,如彈出個 Dialog ,無法與用戶交互;
  3. 後臺 Activity ,已經被暫停的 Activity ,如執行了 onStop ,優先級最低;

當系統內存不足時,系統會按照上述優先級去殺死目標 Activity 所在的進程,並在後續通過 onSaveInstanceState 和 onRestoreInstanceState 來保存和恢復數據;如果一個進程中沒有四大組件在運行,那麼這個進程很容易被殺死。

Activity 的啓動模式

  1. standard : 標準模式。即系統默認模式,每次啓動 Activity 時都會重新創建一個該 Activity 的實例,無論之前是否創建過。 Activity 只能在任務棧中創建,由於 ApplicationContext 不在任務棧中,所以當我們直接用 ApplicationContext 啓動一個 Activity 時,總是會報異常,此時需要爲待啓動的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 標記位,這樣啓動的時候就會爲它創建一個任務棧,當任務棧中沒有 Activity 時,系統就會回收任務棧,通過這種方式啓動的 Activity 其啓動模式爲 singleTask;
  2. singleTop : 棧頂複用模式。如果新 Activity 已經位於任務棧的棧頂,那麼此時 Activity 不會重新被創建,同時它的 onNewIntent 方法會被調用,通過此方法的參數我們可以取出當前的請求信息。
  3. singleTask : 棧內複用模式。如果任一棧內已有該 Activity 的實例,那麼系統會將該 Activity 放到棧頂,並調用它的 onNewIntent 方法,通過此方法的參數我們可以取出當前的請求信息。
  4. singleInstance : 單實例模式。這是一種加強的 singleTask 模式,具備 singleTask 的所有特性,另外此模式的 Activity 只能單獨位於一個任務棧中。

配置 Activity 的啓動模式一般有一下兩個方法:

1.通過 AndroidManifest.xml 爲 Activity 指定啓動模式;

<activity
	android:launchMode="singleTask"
/>

2.通過在 Intent 中設置標誌位來爲 Activity 指定啓動模式;

	Intent intent = new Intent() ;
	intent.setClass(xx , xxx.class) ;
	intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ;
	startActivity(intent) ;

上面兩種方式都可以爲 Activity 指定啓動方式,但是二者之間還是有區別的。
首先,優先級上,第二種啓動方式的優先級要高於第一種,即兩種指定都存在時,以第二種方式爲準;其次,在限定範圍上不同,例如,第一種無法爲 Activity 設定 FLAG_ACTIVITY_CLEAR_TOP 標識,而第二種無法爲 Activity 指定 singleInstance 模式。

注意 adb shell dumpsys activity 命令可以查看 Activity 啓動的相關信息。

任務棧相關參數 TaskAffinity

TaskAffinity 可以翻譯爲“任務相關性”,這個參數標示了一個 Activity 所需的任務棧名稱,默認情況下,所有 Activity 的任務棧名都爲應用包名;當然,我們也可以爲每個 Activity 單獨指定 TaskAffinity 屬性,如果該屬性值與應用包名一樣,就相當於沒有指定,TaskAffinity 主要和 singleTask 啓動模式或者 allowTaskReparenting 屬性配對使用,在其他情況下沒意義。另外,任務棧爲前臺任務棧和後臺任務棧,後臺任務棧中的 Activity 處於暫停狀態,用戶可以通過切換將後臺任務棧再次調到前臺。

當 TaskAffinity 和 singleTask啓動模式配合使用時,該 Activity 會運行在名字和 TaskAffinity 相同的任務棧中。

當 TaskAffinity 和 allowTaskReparenting 結合的時候,若應用 A 啓動了一個應用 B 中的某個 Activity C 後,如果 Activity C的 allowTaskReparenting 屬性爲 true , 那麼當應用 B 被啓動後,此 Activity 會直接從應用 A 的任務棧轉移到應用 B 的任務棧中;此時點擊 Home 鍵回到桌面,點擊應用 B 的桌面圖標,這是不是啓動應用 B 的主 Activity ,而是從新顯示了被應用 A 啓動的 Activity C ,或者說 C 中 A 的任務棧轉移到了 B 的任務棧中。

<activity
	android:taskAffinity="com.xxx.task"
/>

Activity 的 Flags

Activity 的 Flags 比較多,這裏不一一介紹,要注意的是:有些 Flag 是系統內部使用的,應用程序不需要手動去設置這些 Flag ,以防出現問題。

IntentFilter 的匹配規則

啓動 Activity 分爲兩種:顯示調用和隱式調用。顯示調用需要明確地指定被啓動對象的信息,包括包名和類名;隱式調用需要明確指定組件信息。原則上一個 Intent 不應該既是顯示調用又是隱式調用,如果二者共存,以顯示調用爲準。
隱式調用需要 Intent 能夠匹配目標組件的 IntentFilter 中所設置的過濾信息,若不匹配則無法啓動目標 Activity 。 IntentFilter 中的過濾信息有 action 、 category 、 data , 只有一個 Activity 同時匹配 action 、 category 、 data 纔算完全匹配,只有完全匹配才能成功啓動目標 Activity ;另外,一個 Activity 中可以有多組 IntentFilter ,只要 Intent 可以匹配任何一組 IntentFilter ,都可以成功啓動該 Activity 。

action 的匹配規則

action 是一個字符串,當 Intent 中的 action 和 IntentFilter 中的 action 完全一樣(包括大小寫)時,纔算匹配成功。存在多個action只需匹配一個即可完全匹配

category 的匹配規則

category 也是一個字符串,Intent 可以沒有 category ,此時系統會默認給 Intent 添加 “android.intent.category.DEFAULT” 這個 category ,如果爲 Intent 指定了 category ,那麼不管爲 Intent 設置了多少個 category 都要能夠和 IntentFilter 中的任何一個 category 相同。有多少個category就要匹配多少個。

data 的匹配規則

data 的語法格式如下:

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

        // api 19 and higher 
		android:ssp="string"
		android:sspPattern="string"
		android:sspPrefix="string"
        >
	</data>

data 由兩部分組成 mimeType 和 URI ,mimeType 是指媒體類型,如 image/jpeg , video/mp4 等; URI 的結構如下:

	<scheme>://<host>:<port>[<path>|<pathPattern>|<pathPrefix>]

如果 URI 中沒有指定 scheme 和 host ,那麼這個 URI 是無效的。path 、 pathPattern 和 pathPrefix : path 表示完整的路徑信息;pathPattern 也表示完整的路徑信息,但是他裏面可以包含通配符 “*” , “*” 表示 0 個或多個任意字符,需要注意的是,由於正則表達式的規範,如果想表示真實的字符串,那麼 “*” 要寫成 “\\*” , “\” 要寫成 “\\\\”;data 匹配和 action 類似,也需要完全匹配,下面分兩種情況介紹下 data 的匹配規則:

1.只有 mimeType

	<data android:mimeType="image/*" />

這種情況下雖然沒有指定 URI ,但卻有默認值,URI 的默認值爲 content 和 file 。也就是說 data 使用默認值的時候,Intent 中的 URI 部分必須爲 content 或 file 才能匹配成功。另外,如果要爲 Intent 指定完整的 data ,必須要調用 setDataAndType 方法,不能先調用 setData 和 setType 中的任何一個,再調用另一個,因爲 setData 和 setType 會彼此情況對方的值。

2.完整的 data 格式
這種和上面的 “data 語法格式” 內容一樣,信息比較全,需要完全匹配。

**注意:**關於 AndroidManifest.xml 中 data 的書寫格式有種特殊情況,下面的兩種寫法是等效的:

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

最後,當我們隱式啓動一個 Activity 的時候,需要先判斷一下是否能夠找到能夠匹配我們 Intent 的 Activity ,否則會報 ActivityNotFoundException 異常。判斷方法可以使用 PackageManager 的 resolveActivity 或 Intent 的 resolveActivity ,如果它們找不到匹配的 Activity 則返回 null ,這樣我們可以規避上述異常。另外,PackageManager 還提供了 queryIntentActivities ,這個方法不是返回最佳匹配的 Activity 信息,而是返回所有成功匹配的 Activity 信息。 下面讓我們看一下這兩個方法原型:

	public abstract List<ResolveInfo> queryIntentActivities(Intent intent , int flag) ;
	public abstract ResolveInfo resolveActivity(Intent intent , int flag) ;

上面兩個方法的第一個參數好理解,第二個參數需要注意,我們要使用 MATCH_DEFAULT_ONLY 這個標記位,這個標記位的含義是僅僅匹配那些在 intent-filter 中聲明瞭 這個 category 的 Activity 。使用這個標記位的意義在於,只有上述兩個方法不返回 null ,那麼 startActivity 一定可以成功。如果不使用這個標記位,就可以把 intent-filter 中 category 不含 DEFAULT 的那些 Activity 給匹配出來,從而導致 startActivity 可能失敗。因爲不含 DEFAULT 這個 category 的 Activity 是無法接收隱式 Intent 的。在 action 和 category 中,有一類 action 和 category 比較重要,它們是:

	<action android:name="android.intent.action.MAIN" />
	<category android:name="android.intent.category.LAUNCHER" />

這二者共同作用是用來標明這是一個入口 Activity 並且會出現在系統的應用列表中,少了任何一個都沒意義,也無法出現在系統應用列表中。另外,針對 Service 和 BroadcastReceive,PackageManager 同樣提供了類似的方法去獲取成功匹配的組件信息。

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