Intent對象解析
Intent能夠被分成兩組:
1. 用組件的名稱把Intent對象明確的指向目標組件(在Intent對象的組件名欄位指定目標組件名)。因為一般情況下其他應用的開發人員不會瞭解目標組件的名字,所以通常針對應用程式的內部訊息使用明確命名的Intent對象,如一個Activity啟動一個下屬服務或啟動一個姊妹Activity。
2. 沒有命名目標(Intent對象的組件名欄位是空的)的隱式的Intent對象。隱式的Intent對象經常被用於啟用其他應用程式中的組件。
Android系統把一個明確命名的Intent對象發送給目標類的一個執行個體。除了組件名以外,不再用Intent對象內任何其他資訊來判斷哪個組件應該獲得這個Intent對象。
對於隱式的Intent對象,需要不同的分類。在缺少目標組件的情況下,Android系統必須尋找最適合的組件(一個能夠執行請求動作的Activity或Service,或者是一組能夠響應廣播通知的Broadcast Receiver)來處理這個Intent對象。系統通過把Intent對象的內容跟Intent過濾器比較,跟組件的結構關聯就能夠潛在接收Intent對象。過濾器會公開組件的能力和它能夠處理的Intent對象限制。它們開啟組件接收可能的公開類型的隱式Intent對象。如果組件沒有任何過濾器,那麼它僅能接收明確命名的Intent對象。帶有過濾器的組件能夠接收命名和匿名的Intent對象。
Intent過濾器會參考Intent對象以下三個方面來檢測是否接收這個Intent對象:
1. 動作(action)
2. 資料(URI和資料類型)
3. 分類(category)
附加資訊(extras)和標記(flags)不作為判斷哪個組件接收這個Intent對象標準。
Intent過濾器
Intent過濾器是用來通知系統它們能夠處理那種類型隱式的Intent對象,Activity、Service、Broadcast Receiver能夠有一個或多個Intent過濾器。每個過濾器都描述了組件的一種能力,說明了組件將會接受的Intent對象集。它濾如有效期望類型的Intent對象,濾出不想要的Intent對象---但是僅是不想要的隱式Intent對象(那些沒有命名目標類的Intent對象)。一個有明確命名的Intent對象總是包被發送給它的目標類的執行個體,而不管它包含了什麼;過濾器不起作用。但是隱式的Intent對象僅能發送給能夠通過組件的一個過濾器來傳遞它的一個組件。
對於組件能夠做的每項工作,它都會有一個獨立的過濾器,並且每一工作都能夠展現給使用者。例如,執行個體應用Note Pad應用程式的NoteEditor Activity就有兩個過濾器---一個是用於啟動帶有使用者能夠查看或編輯的特定注釋資訊的過濾器;另一個是用於啟動一個新的,使用者能夠填充和儲存的空白注釋過濾器。(Note Pad的所有過濾器在“Note Pad樣本”一節中介紹)
一個Intent過濾器是IntentFilter類的執行個體。但是因為Android系統在它啟動組件之前必須瞭解有關組件的能力,所以Intent過濾器通常都不是用Java代碼來建立的,而是在應用程式的資訊清單檔(AndroidManifest.xml)中用<intent-filter>元素來聲明。(通過調用Context.registerReceiver()方法來動態註冊的Broadcast Receiver是一個例外,它們直接建立IntentFilter對象做為過濾器。)
過濾器有類似於Intent對象的動作、資料、和分類的欄位,過濾器會用這三個域來檢測一個隱式的Intent對象。對於要傳遞給擁有過濾器的組件的Intent對象,必須傳遞所有的這三個要檢測的欄位。如果其中之一失敗了,Android系統也不會把它發送給對應的組件---至少在基於那個過濾器的基礎上不會發送。但是,因為一個組件能夠有多個Intent過濾器,即使不能通過組件的一個過濾器來傳遞Intent對象,也可以使用其他的過濾器。
以下詳細說明對三個域的檢測
1. 動作域檢測
在資訊清單檔中的<intent-filter>元素內列出對應動作的<action>子項目。如:
<intent-filter . . . >
<action android:name="com.example.project.SHOW_CURRENT" />
<action android:name="com.example.project.SHOW_RECENT" />
<action android:name="com.example.project.SHOW_PENDING" />
. . .
</intent-filter>
像上例顯示的那樣,一個Intent對象就是一個命名動作,一個過濾器可以列出多個動作。這個列表不能是空的,一個過濾器必須包含至少一個<action>元素,否則它會阻塞所有的Intent對象。
要通過這個檢測,在Intent對象中指定的動作必須跟這個過濾器的動作列表中動作一致匹配。如果Intent對象或過濾器沒有指定的動作,會產生以下結果:
A. 如果對列表中所有動作都過濾失敗,那麼對於要匹配的Intent對象不做任何事情,而且所有的其他Intent檢測都失敗。沒有Intent對象能夠通過這個過濾器;
B. 另一方面,沒有指定動作的Intent對象會自動的通過檢測---只要這個過濾器包含至少一個動作。
2. 分類域檢測
<intent-filter>元素也要列出分類作為子項目。例如:
<intent-filter . . . >
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
. . .
</intent-filter>
注意,對於資訊清單檔中的動作和分類沒有使用早先介紹的常量,而是使用了完整字串值來替代。例如,上例中“android.intent.category.BROWSABLE”字串對應本文檔前面提到的CATEGOR_BROWSABLE常量。類似的“android.intent.action.EDIT”字串對應ACTION_EDIT常量。
對於一個要通過分類檢測的Intent對象,在Intent對象中每個分類都必須跟過濾器中的一個分類匹配。過濾器能夠列出額外的分類,但是它不能忽略Intent對象中的任何分類。
因此,原則上一個沒有分類的Intent對象應該始終通過這個檢測,而不管過濾器中聲明的分類。大多數情況都是這樣的,但是,有一個例外,Android處理所有傳遞給startActivity()方法的隱式Intent對象,就像它們至少包含了一個“android.intent.category.DEFAULT(對應CATEGORY_DEFAULT常量)”分類一樣。因此接收隱式Intent對象的Activity必須在它們的Intent過濾器中包含“android.intent.category.DEFAULT”分類。(帶有“android.intent,action.MAIN”和“android.intent.category.LAUNCHER”設定的過濾器是個例外。因為它們把Activity標記為新任務的開始,並且代表了啟動屏。它們能夠在分類列表中包含“android.intent.category.DEFAULT”,但是不需要。)
3. 資料域檢測
像動作分類檢測一樣,針對Intent過濾器的資料規則也要包含在一個子項目中,並且,跟動作和分類的情況一樣,這個子項目也能夠出現多次,或者不出現。例如:
<intent-filter . . . >
<data android:mimeType="video/mpeg" android:scheme="http" . . . />
<data android:mimeType="audio/mpeg" android:scheme="http" . . . />
. . .
</intent-filter>
每個<data>元素能夠指定一個URI和一個資料類型(MIME媒體類型)對於每個URI部分都會有獨立的屬性---scheme、host、port、path:scheme://host:port/path
例如,以下URI:
content://com.example.project:200/folder/subfolder/etc
scheme是content,host是“com.example.project”,port是“200”,path是“folder/subfolder/etc”。host和port一起構成了URI授權,如果沒有指定host,那麼port也會被忽略。
這些屬性是可選的,但是,它們不是彼此獨立的,如一個授權意味著必須指定一個scheme,一個path意味著必須指定scheme和授權。
當Intent對象中的URI跟過濾器的一個URI規則比較時,它僅比較在過濾器總實際提到的URI部分。如,如果一個過濾器僅指定了一個scheme,那麼帶有這個scheme的所有的URIs都會跟這個過濾器匹配。如果一個過濾器指定了一個scheme和授權,但是沒有路徑,那麼帶有相同scheme和授權的所有URIs的Intent對象都會匹配,而不管它們的路徑。如果一個過濾器指定了一個scheme、授權、和路徑,那麼就只有相同的scheme、授權和路徑Intent對象才會匹配。但是,在過濾器中的路徑規則能夠包含只要求路徑部分匹配的萬用字元。
<data>元素的type屬性指定了資料的MIME類型。它在過濾器中比URI更共同。對於子類型域,Intent對象和過濾器都能夠使用“*”萬用字元---例如,“text/*”或“audio/*”指明可以跟任意子類型匹配。
資料檢測會比較Intent對象和過濾器中的URI和資料類型。規則如下:
A.只有過濾器沒有指定任何URI或資料類型的情況下,既沒有URI也沒有資料類型的Intent對象才能通過檢測;
B.一個包含URI但沒有資料類型的Intent對象(並且不能從URI中推斷出資料類型)只有跟過濾器中的一個URI匹配,並且同樣這個過濾器沒有指定資料類型時,才能通過檢測。這種情況僅針對不指向實際資料的URIs,如mailto:和tel:。
C.一個包含了資料類型但不沒有URI的Intent對象,只有過濾器也列出相同的資料類型,並也沒有指定URI的情況下,才能通過檢測。
D.包含了URI和資料類型的Intent對象(或者是資料類型能夠從URI中推斷出來)只有它的類型跟過濾器中列出的一個類型匹配,才能通過資料類型部分的檢測,如果它的URI部分跟過濾器中的一個URI匹配或者Intent對象有一個content:或file:URI並且過濾器沒有指定URI,那麼才能能夠URI部分的檢測。換句話說,如果過濾器僅列出了資料類型,那麼一個組件被假設為支援content:和file:資料。
如果一個Intent對象能夠通過多個過濾器傳遞給一個Activity或Service,那麼可以詢問使用者要啟用哪個組件。如果沒有找到目標,就會產生一個異常。
常見情況
以上資料檢測規則中的最後一條(規則d),反映了組件能夠獲得從檔案或內容提供器中擷取本機資料的期望。因此,它們的過濾器能夠只列出資料類型,並且不需要明確的命名content:和file:方案。這是一種典型的情況。例如,像下面的<data>元素那樣,告訴Android,組件能夠從一個內容提供器中擷取圖片並顯示它:<data android:mimeType="image/*" />
因為大多數有效資料是通過內容提供器來配發的,所以,指定一個資料類型但沒有URI的過濾器或許是最常見的。
另一個常見的配置是帶有方案和資料類型的過濾器。例如,像下面這樣的<data>元素會告訴Android組件能夠從網路上擷取視頻並顯示它。
<data android:scheme="http" android:type="video/*" />
例如,我們來研究一下當使用者點擊一個網頁上的一個連結時,瀏覽器應用程式要做的事情。首先,它會試著來顯示資料(如果這個連結是一個HTML頁,那麼就顯示)。如果不能顯示這個資料,那麼就會把方案和資料類型一起放到一個隱式Intent對象中,並試著啟動一個能夠做這項工作的Activity,如果沒有接受者,它就會要求下載管理員來下載資料。然後把資料放在一個內容提供器的控制之下,以便一個潛在的更大的Activity池(那些只帶有命名資料類型的過濾器)能夠響應。
大多數應用程式都有不引用任何特殊資料就啟動重新整理的方法。能夠初始啟動應用程式的Activity都有帶有“android.intent.action.MAIN”作為指定動作的過濾器。如果它們代表了在應用程式中的啟動器,那麼它們也要指定“android.intent.category.LAUNCHER”分類:
<intent-filter . . . >
<action android:name="code android.intent.action.MAIN" />
<category android:name="code android.intent.category.LAUNCHER" />
</intent-filter>
使用Intent對象進行匹配
Intent對象跟過濾器匹配不僅是要發現要啟用的目標組件,而且也發現裝置上有關組件集的一些事情。例如,Android系統通過尋找所有的擁有指定的“android.intent.action.MAIN”動作和“android.intent.category.LAUNCHER”分類的過濾器的Activity,把它們填充到應用程式的啟動器,把使用者啟動的有效應用程式顯示在螢幕的頂層,然後顯示啟動器中的那些Activity的表徵圖和標籤。類似地,系統通過尋找它的過濾中帶有“android.intent.category.HOME”的Activity來發現主屏介面。
應用程式能夠用於Intent對象匹配的是組類似的方法。PackageManager類中有一組query…()方法,它們返回能夠接受一個特殊Intent對象的所有組件,並且還有一組類似的resolve…()方法,用來判斷響應一個Intent對象的最好組件。例如,queryIntentActivities()方法返回一個能夠執行這個Intent對象要求動作的所有Activity;queryIntentServices()方法類似地返回Service列表。這些方法都不啟用組件,它們只是列出能夠響應這個Intent對象的所有組件。對於Broadcast
Receiver也有類似的方法:queryBroadcastReceivers()。