Android Intent

顯示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參考

認識安卓中的MIME Type

什么是 MIME TYPE?

android中用到的MimeType的處理方式

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測試

  1. 要想讓intent對象通過action測試,那么intent-filter中聲明的action不能為空且要包含intent對象中的action值(如果intent的action值不為空的話)。
  2. 如果intent-filter沒有聲明任何action,那么所有的intent的對象(即無論intent如何配置)都無法通過intent-filter的action測試。

Category測試

  1. 如果intent對象不包含任何category,并且該intent不是用來啟動Activity的,那么該intent對象總是能通過所有任意的intent-filter的category測試;
  2. 如果intent對象包含category(至少一個),那么只有當intent-filter中聲明的category全部包含intent對象中的所有category的時候才通過category測試。
  3. 如果允許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的通配符*#。其中*匹配任意字符串,#匹配任意數字。

參考博客Android中Intent對象與Intent Filter過濾匹配過程詳解

參考

Android中Intent概述及使用

Android中Intent對象與Intent Filter過濾匹配過程詳解

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

推薦閱讀更多精彩內容