最近在拜讀任主席的Android開發藝術探索,現在看了一半,再回頭看前面的,感覺跟沒有看一樣,所以還是把知識點總結一下吧
Actvity的LaunchMode
這節來講一下Activity的啓動方式,Activity有四種啓動方式:standard、singleTop、singleTask、singleInstance。在默認情況下,多次啓動同一個Activity的時候,系統會創建多個實例並把他們一一放入任務棧中,單擊back鍵,這些Activity會一一回退。下面介紹一下這四種啓動模式:
standard:標準模式,也是系統默認模式。每次啓動不管是否存在都會新建一個實例,一個任務棧中可以有多個實例,每個實例也可以屬於不同的任務棧。誰啓動的Activity,這個Activity就運行在它的那個Activity所在的棧中。這裏有一條需要注意的,當我們用ApplicationContext去啓動standard模式的Activity時會報錯:
calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.Is this really what you want?
這是因爲standard模式的Activity默認會進入啓動它的Activity所屬的任務棧中,但是由於非Activity類型的Context並沒有任務棧,所以就報錯了。解決這個問題的方式是在Intent中addFlag–FLAG_ACTIVITY_NEW_TASK,這個時候的Activity是以singleTask模式啓動的,放在一個新的任務棧中(這個問題碰到過幾次,以前只知道要加上flag,但是沒有仔細想過爲什麼,現在看到這塊就豁然開朗了)
- singleTop:棧頂複用模式。如果此模式的Activity當前位於任務棧的棧頂,那麼再啓動此Activity的時候不會重新創建, 但是會調用它的onNewIntent方法,此方法相當有用,如果此Activity不在棧頂,那麼這時會再次創建實例
- singleTask:棧內複用模式。這是一種單實例模式,在這種模式下,只要當前任務棧中有目標Activity,那麼該Acticity不會被重新創建,同時它的onNewIntent方法會被回調,如果該Activity不在棧頂,則系統會把該Activity之上的元素全部退棧,此時該Activity調到棧頂。
- singleInstance:單例模式,這是一種加強的singleTask模式,除了具有singleTask模式的所有特性之外,還加強了一點,具有此模式的Activity只能單獨位於一個任務棧,並且此棧中只有此一個Activity
這裏有一個參數需要說明一下TaskAffinity,這個參數標識了一個Activity歸屬哪個任務棧,默認情況下所有Avtivity所需要的任務棧的名字是包名,通過這個參數可以指定,這個參數通過配合singTask啓動模式或者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。具體說明看這篇博客android當中taskAffinity屬性與launchMode相關。
再來說一下常用的幾個Activity的Flags:
- FLAG_ACTIVITY_NEW_TASK:指定singleTask啓動模式,效果和XML中指定相同
- FLAG_ACTIVITY_SINGLE_TOP:指定singleTop模式,效果和XML中指定相同
- FLAG_ACTIVITY_CLEAR_TOP:當此標記位的Activity啓動時,在同一個任務棧中所有位於它上面的Activity都要出棧,這個標記位一般會和singleTask啓動模式一起出現,在這種情況下,被啓動的Activity如果已經存在,那麼系統就會調用它的onNewIntent。如果被啓動的Activity採用standard模式,那麼它連同它之上的Activity都要出棧,再重新創建新的Activity實例並放入棧頂。
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有這個標記的Activity不會出現在歷史Activity的列表中,當某些情況下我們不希望用戶通過歷史列表回到我們的Activity的時候這個標記比較有用。
IntentFilter的匹配原則
啓動Activity的方式有兩種:顯示調用和隱式調用。顯示調用就不多說了,來說一說隱式調用,隱式調用需要Intent能夠匹配目標組件的IntentFilter中所設置的過濾信息。IntentFilter中的過濾信息有action、category、data,下面是一個過濾規則的示例:
<intent-filter>
<action android:name="com.wpp.action"/>
<category android:name="com.wpp.category"/>
<data android:name="com.wpp.data"/>
</intent-filter>
爲了匹配過濾列表,需要同時匹配過濾列表中的action、category、data信息,否則匹配失敗,一個過濾列表中的action、category、data可以有多個,只有完全匹配activity才能啓動成功,另外,一個activity中可以有多個intent-filter,一個Intent只要能匹配任何一組intent-filter即可匹配成功。
下面詳細分析各種屬性的匹配規則
1. action的匹配規則
action是個字符串,系統預定義了一些action,同時我們可以在應用中定義自己的action。action的匹配規則是Intent中的action必須能夠和過濾規則中的action匹配,即字符串值完全一樣,一個過濾規則中可以有多個action,只要任何一個action相同即可匹配成功。 如果Intent中沒有指定action則匹配失敗,注意,action區分大小寫!
2.category的匹配規則
category也是字符串,跟action一樣,系統有預定義的,我們也可以在應用中自定義。匹配規則和action不同,它要求Intent中如果含有category,則intent中所有的category都必須和一組intent-filter中的任何一個category完全匹配,如果沒有category也可以匹配成功,但是一旦有category,不管幾個,都必須和intent-filter中任何一個category匹配。爲什麼不設置category也可以匹配成功呢?因爲系統在startActivity的時候會默認加上”android.intent.category.DEFAULT”這個category,爲了我們的activity能夠接收隱式調用,就必須在intent-filter中指定這個category
3.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,mimeType指媒體類型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示圖片、文本、視頻等不同的媒體格式,而URI的結構:
<scheme>://<host>:<port>/[path]|[pathPattern]|[pathPrefix]
比如:http://www.baidu.com:80/search/info
- Scheme:URI的模式,比如http、file、content等,如果URI中沒有指定scheme,那麼URI無效
- Host:URI的主機名,如果Host沒有指定,那麼整個URI無效
- Port:URI中的端口號,比如80,僅當URI指定了scheme和host參數的時候port參數纔是有意義的
- Path、pathPattern、pathPrefix:這三個參數表述路徑信息,其中path表示完整的路徑信息;pathPattern也表示完整的路徑信息,但是它裏面包含通配符”*”,表示0個或任意字符。由於正則表達式的規範,如果想表示真實的字符串,”*”要寫成”\\*”,”\”要寫成”\\\\”;path表示路徑的前綴信息。
再講一下匹配規則:
(1)如下過濾規則
<intent-filter>
<data android:mimtType="image/*"/>
……
</intent-filter>
這種規則制定了媒體類型爲所有類型的圖標,那麼Intent中的mimeType屬性必須爲”image/*”才能匹配,這種清下雖然過濾規則中沒有指定URI,但是卻有默認值,URI的默認值爲content和file,示例比如:
intent.setDataAndType(Uri.parse("file://abc"),"image/png")
如果要爲Intent指定完整的data,必須要調用setDataAndType方法,不能分開調用,因爲setData和setType會彼此清除對方的值
(2)如下過濾規則
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ···/>
<data android:mimeType="audio/mpeg" android:scheme="http" ···>
···
</intent-filter>
這種規則指定了兩組data規則,且每個data都指定了完整的屬性值,既有URI,又有mimeType,示例如下:
intent.setDataAndType(Uri.pase("http://abc"),"video/mpeg")
或者
intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg")
IntentFilter的過濾規則已經講完了,這裏有個地方需要注意一下,如果通過隱式調用來啓動activity,當intent匹配不到activity的時候會報錯,提示無法找到activity,所以通常情況下,當我們需要通過隱式調用來啓動一個activity時,需要先做一下判斷,方法有兩種:通過PackageManager的resolveActivity或者Intent的resolveActivity方法,如果找不到匹配的Activity就會返回null,我們再來做處理就能避免activity找不到的錯誤了。另外,PackageManager還提供了queryIntentActivities方法,這個方法返回所有匹配的Activity信息。
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags);
public abstract ResolveInfo resolveActivity(Intent intent, int flags);
第二個參數要使用MATCH_DEFAULT_ONLY這個標記位,這個標記位含義是僅僅匹配那些在intent-filter中聲明瞭DEFAULT這個category的Activity,使用這個標記位只要上面兩個不返回null,那麼startActivity一定成功。在action和category中,有一類action和category比較重要,
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
二者同時作用來標記一個入口activity。