顯示Intent和隱式Intent
Android中的Intent是一個非常重要且常用的類,可以用來在一個組件中啟動App中的另一個組件或者是啟動另一個App的組件,這里所說的組件指的是Activity、Service以及Broadcast。
-
Intent功能
- 啟動Activity
- 啟動Service
- 發送廣播
-
Intent類型
- 顯示Intent,Intent中明確包含了要啟動的組件的完整類名(包名及類名),那么這個Intent就是explict的,即顯式的。適用于在App內使用,因為開發者一定知道自己開發的組件完整類名。
- 隱式Intent,Intent沒有包含要啟動的組件的完整類名,那么這個Intent就是implict的,即隱式的。使用情況,隱式的Intent只用在當我們想在自己的App中通過Intent啟動另一個App的組件的時候,讓另一個App的組件接收并處理該Intent。
需要注意的是,為了確保App的安全性,我們應該總是使用顯式Intent去啟動Service并且不要為該Service設置任何的Intent Filter。通過隱式的Intent啟動Service是有風險的,因為你不確定最終哪個App中的哪個Service會啟動起來以響應你的隱式Intent,更悲催的是,由于Service沒有UI的在后臺運行,所以用戶也不知道哪個Service運行了。
Intent組成
MIME TYPE
在學習Intent的組成元素之前,先來了解一下基本知識:MIME TYPE。
什么是MIME TYPE
MIME type的縮寫為(Multipurpose Internet Mail Extensions)代表互聯網媒體類型(Internet media type),MIME使用一個簡單的字符串組成,最初是為了標識郵件Email附件的類型。媒體類型通常是通過 HTTP 協議,由Web服務器告知瀏覽器的,更準確地說,是通過 Content-Type
來表示的,例如:
Content-Type: text/HTML
MIME類型能包含視頻、圖像、文本、音頻、應用程序等數據。
MIME TYPE組成
通常只有一些在互聯網上獲得廣泛應用的格式才會獲得一個 MIME Type,如果是某個客戶端自己定義的格式,一般只能以 application/x- 開頭。例如application/vnd.ms-excel
指定了Microsoft Excel文件類型。
每個MIME類型由兩部分組成,前面是數據的大類別,例如聲音audio、圖象image等,后面定義具體的種類。
常見的MIME Type請參考MIME 參考手冊
MIME Type通配符
標準的MIME Type中為同一種資源的不同格式指定了不同的MIME Type。例如圖片資源MIME Type有,image/bmp、image/cis-cod、image/gif、image/ief、image/jpeg、image/pipeg...如果需要指定大致的類型,比如指定所有圖片資源,就可以使用通配符*
,構成image/*。
MIME Type作用
在把輸出結果傳送到瀏覽器上的時候,瀏覽器必須啟動適當的應用程序來處理這個輸出文檔。這可以通過MIME Type來判斷啟動哪個程序來處理。在HTTP中,MIME類型被定義在Content-Type header中
Android MIME Type
Android MIME Type類型
-
標準的MIME Type
<data android:mimeType="image/jpeg ">
-
自定義
<data android:mimeType="vnd.android.cursor.item/vnd.google.note"/>
<data />
屬性會在后面的Intent組成元素中介紹。自定義的MIME Type格式也是有要求的,自定義的方式涉及到ContentProvider,所以下次再仔細閱讀。
MIME Type參考
Intent組成部分
Android可以根據Intent所攜帶的信息去查找要啟動的組件,Intent還攜帶了一些數據信息以便要啟動的組件根據Intent中的這些數據做相應的處理。
Intent由6部分信息組成:Component Name、Action、Data、Category、Extras、Flags。可以根據作用分為以下三種:
- Component Name、Action、Data、Category,決定了Android會啟動哪個組件,其中Component Name用于在顯式Intent中使用,Action、Data、Category、Extras、Flags用于在隱式Intent中使用。
- Extras,里面包含了具體的用于組件實際處理的數據信息。
- Flags,其是Intent的元數據,決定了Android對其操作的一些行為。
Component Name
要啟動的組件的名稱。一般在顯示Intent中,指定要啟動的組件類文件參數,就是Component Name。如果沒有設置component name,那么該Intent就是隱式的,Android系統會根據其他的Intent的信息(例如action、data、category等)做一些比較判斷決定最終要啟動哪個組件。所以,如果你啟動一個你自己App中的組件,你應該通過指定component name通過顯式Intent去啟動它(因為你知道該組件的完整類名)。
需要注意的是,當啟動Service的時候,你應該總是指定Component Name。否則,你不確定最終哪個App的哪個組件被啟動了,并且用戶也看不到哪個Service啟動了。
Component Name參數指定
component name在Intent中對應的field是ComponentName對象,你可以通過要啟動的組件的完整類名(包括應用的包名)指定該值,例如com.example.ExampleActivity。你可以通過Intent的setComponent()方法、setClass()方法、setClassName()方法或Intent的構造函數指定component name。
Action
是表示了要執行操作的字符串,比如查看或選擇,其對應著Intent Filter中的action標簽<action />
。
Action定義
Action可以選用預定義的,也可以自定義。Intent類和Android中其他framework級別的一些類也提供了許多已經定義好的具有一定通用意義的action。
自定義是指定獨有的action以便于你的App中的Intent的使用或其他App中通過Intent調用你的App中的組件。
如果自定義了action,請務必將App的包名作為該action的前綴,這是一種良好的編程習慣,避免造成混淆。
設定Action
通過調用intent對象的setAction()方法或在Intent的構造函數中指定intent的action。注意Intent只能由一個action。
Data
Intent中的data指的是Uri對象和數據的MIME類型,其對應著Intent Filter中的data標簽<data />
。
Uri
一個完整的Uri由scheme、host、port、path組成,格式是<scheme>://<host>:<port>/<path>。Uri就像一個數據鏈接,組件可以根據此Uri獲得最終的數據來源。通常將Uri和action結合使用,比如我們將action設置為ACTION_VIEW,我們應該提供將要被編輯修改的文檔的Uri。
Android預定義的schema有很多,content協議、http協議、file協議,還有tel協議表示打電話,geo協議表示地理位置...
MIME Type
Android就會基于Uri和MIME類型將Intent傳遞給符合條件的組件。例如指定了MIME Type之后,不讓Android誤將一個含有視頻Uri的Intent對象傳遞給一個只能顯示圖片的Activity。
如果Uri使用的是content:協議,那么這就說明Uri所提供的數據將來自于本地設備,即數據由ContentProvider提供,這種情況下Android會根據Uri自動推斷出MIME類型,此種情況我們無需再自己指定MIME類型。因為此Uri的類型可以通過provider的getType(Uri)方法得到。
設置Data
如果只設置數據的Uri,需要調用Intent對象的setData()方法;如果只設置數據的MIME類型,需要調用Intent對象的setType()方法;如果要同時設置數據的Uri和MIME類型,需要調用Intent對象的setDataAndType()方法。
需要注意的是,如果你想要同時設置數據的Uri和MIME類型,不要先后調用Intent對象的setData()方法和setType()方法,因為setData()方法和setType()是互斥的,即如果調用了setData()方法,會將Intent中已經通過setType()方法設置的MIME類型重置為空。如果調用了setType()方法,會將Intent中已經通過setData()方法設置的Uri重置為空。所以在需要同時設置數據的Uri和MIME類型的時候,一定要調用Intent對象的setDataAndType()方法,而不是分別調用setData()方法和setType()方法。
Category
category包含了關于組件如何處理Intent的一些其他信息。同時可以更加準確的定位到符合條件的組件。可以在Intent類中查找到更多預定義的category。
<category android:name="android.intent.category.DEFAULT" />
在調用startActivity()或者startActivityForResult()方法時會自動給參數Intent添加category.DEFAULT。所以要在相應的IntentFilter中添加該category。
Extras
額外的數據信息。Intent中有一個Bundle對象存儲著各種鍵值對,接收該Intent的組件可以從中讀取出所需要的信息以便完成相應的工作。有的Intent需要靠Uri攜帶數據,有的Intent是靠extras攜帶數據信息。
定義Extra的鍵值
Intent類里面也指定了很多預定義的EXTRA_*形式的extra。同時也可以聲明自己自定義的extra,請確保App的包名作為你的extra的前綴。
設置Extra
通過調用Intent對象的各種重載的putExtra(key, value)方法向Intent中加入各種鍵值對形式的額外數據。你也可以直接創建一個Bundle對象,向該Bundle對象傳入很多鍵值對,然后通過調用Intent對象的putExtras(Bundle)方法將其一塊設置給Intent對象中去。
Flags
flag就是標記的意思,Intent類中定義的flag能夠起到作為Intent對象的元數據的作用。這些flag會告知Android系統如何啟動Activity(例如,新啟動的Activity屬于哪個task)以及在該Activity啟動后如何對待它(比如)。更多信息可參見Intent的setFlags()方法。
實踐
mImageUri = Uri.fromFile(outPutImage);
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
startActivityForResult(intent, TAKE_PHOTO);
imageUri指定了圖片存儲的Uri,作為extra,讓相機知道拍照完成后照片的存儲位置信息。
使用隱式Intent最佳方式
當使用隱式Intent來啟動一個組件時,如果Android系統沒有相應的組件,會拋出異常。所以有必要在啟動組件時,先去判斷一下Android系統有無至少一個符合Intent條件的組件。判斷方式有兩種:PackageManager和Intent兩個類中的方法。
兩種方法會讓Android根據該sendIntent找到潛在的適合啟動的組件的信息,并以ResolveInfo類的對象的形式返回結果,如果返回null,表示當前系統中沒有任何組件可以接收并處理該Intent。如果返回不是null,就表明系統中至少存在一個組件可以接收并處理該Intent。
PackageManager
PackageManager pm = getPackageManager();
//參數flag為什么賦予0,不知道
if(pm.resolveActivity(intent, 0) != null) {
startActivity(intent);
}
參數Flag用于指定尋找方式,文檔中有一句話:The most important is MATCH_DEFAULT_ONLY, to limit the resolution to only those activities that support the CATEGORY_DEFAULT.指定MATCH_DEFAULT_ONLY flag用于尋找含有CATEGORY_DEFAULT的category的組件。
平時用這種方法來判斷有無符合條件的組件,flag一般設置為0,不知道為什么。我個人的理解可能是,多個flag是通過位或來組成一個flag,這里給flag設置為0,當與原始的flag進行位或運算時,不改變原始的flag,也就是保持了默認。如有誤,請指出,純屬瞎YY。
Intent
if(intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
App Chooser
當使用隱式Intent時,Android系統中可能有多個符合該Intent條件的組件。第一次打開時會彈出一個選擇器供用戶選擇,用戶可以勾選始終讓其中一個作為默認啟動組件。這樣下次再啟動時,就會默認打開選擇的組件。但是,如果用戶想分享某個文件時,不一定只會用一個應用來分享,所以這種情況下我們應該強制每一次都打開選擇器,讓用戶選擇。
intent = Intent.createChooser(intent, "Choose WebKit");
if(intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
這里調用Intent類中靜態方法createChooser()來創建一個選擇器Intent,每一次都去啟動該Intent。
Intent過濾
設定好隱式Intent的各項參數后,需要和所有組件的IntentFilter中各項參數匹配,找到符合條件的組件。
IntentFilter
即Intent過濾器,一個組件可以包含0個或多個Intent Filter。Intent Filter是寫在App的manifest文件中的,其通過設置action或uri數據類型等指明了組件能夠處理接收的Intent的類型。如果你給你的Activity設置了Intent Filter,那么這就使得其他的App有可能通過隱式Intent啟動你的這個Activity。
當Android系統接收到一個隱式Intent要啟動一個Activity(或其他組件)時,Android會根據以下三個信息比較Intent的信息與注冊的組件的intent-filter的信息,從而為該Intent選擇出最匹配的Activity(或其他組件):
- intent中的action
- intent中的category
- intent中的data(包含Uri以及data的MIME類型)
也就是隱式intent對象要分別滿足要啟動的目標組件中注冊的intent-filter中的<action />、<category />、<data />三個標簽中的信息。
IntentFilter示例
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".activity.ImplicitActivity">
<intent-filter>
<action android:name="android.example.com.intentusage.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name=".activity.TakePhotoActivity">
<intent-filter>
<action android:name="android.example.com.intentusage.ACTION_SHOW_PIC"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Action測試
- 要想讓intent對象通過action測試,那么intent-filter中聲明的action不能為空且要包含intent對象中的action值(如果intent的action值不為空的話)。
- 如果intent-filter沒有聲明任何action,那么所有的intent的對象(即無論intent如何配置)都無法通過intent-filter的action測試。
Category測試
- 如果intent對象不包含任何category,并且該intent不是用來啟動Activity的,那么該intent對象總是能通過所有任意的intent-filter的category測試;
- 如果intent對象包含category(至少一個),那么只有當intent-filter中聲明的category全部包含intent對象中的所有category的時候才通過category測試。
- 如果允許Activity被隱式的Intent啟動,那么我們必須在該Activity的intent-filter中聲明值為android.intent.category.DEFAULT的category。
android.intent.category.DEFAULT的category,這是因為當我們把一個隱式的intent傳遞給startActivity()或startActivityForResult()方法時,Android會自動給該隱式intent添加值為android.intent.category.DEFAULT的category,所以為了能讓intent-filter包含intent中全部的category,我們就需要在Activity的intent-filter中添加該category,在使用時需要特別注意。
Data測試
在IntentFilter中設置data屬性的uri值時,使用uri的通配符*
和#
。其中*匹配任意字符串,#匹配任意數字。