Android 基礎知識5:Intent 和 Intent 過濾器

回顧

在前 4 篇文章中,咱們介紹了 Android 四大組件的基礎知識,四大組件是構成咱們 App 的基礎,也是 Android 系統設計的最佳體現。各個組件之間徹底是解耦的,若是想訪問其餘組件或者啓動其餘組件可使用 Intent 來操做。在四種組件類型中,有三種(Activity、Service 和 Broadcast)都可以經過異步消息 Intent 進行啓動。Intent 會在運行時對各個組件進行互相綁定。因此咱們能夠把 Intent 看成是各個組件之間的信使(不管該組件是本身 App 的仍是其餘 App)。javascript

每一個組件都有不一樣的啓動方法:css

  • 如要啓動 Activity,能夠用 startActivity() 或 startActivityForResult() 傳遞 Intent(須要 Activity 返回結果時)html

  • 如要啓動 Service,能夠經過 startService()傳遞 Intent 來啓動服務,也可經過 bindService() 傳遞 Intent 來綁定到該服務java

  • 如要發送 Broadcast,能夠經過 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 等方法傳遞 Intent 來發起廣播android

與 Activity、Service 和 Broadcast,ContentProvider 並不是由 Intent 啓動。相反,它們會在成爲 ContentResolver 的請求目標時啓動。想要訪問 ContentProvider 的內容,能夠經過 ContentResolver 調用 query()、insert()、update()、delete()等方法。瀏覽器

目錄

目錄

1、Intent

(一)Intent 的定義

Intent 是一個消息傳遞對象,用來向其餘組件請求操做,不管該組件是當前應用仍是其餘應用。具體來講包含三大用處: 啓動一個 Activity啓動或者綁定 Service傳遞廣播安全

(二)Intent 的分類

Intent 分爲兩種類型:app

  • 顯式 Intent:經過提供目標應用的軟件包名稱或徹底限定的組件類名來指定可處理 Intent 的應用。一般,會在本身的應用中使用顯式 Intent 來啓動組件,這是由於本身的應用知道要啓動的 Activity 或 Service 的類名。
  • 隱式 Intent:不會指定特定的組件,而是聲明要執行的常規操做,從而容許其餘應用中的組件來處理。例如,如需在地圖上向用戶顯示位置,則可使用隱式 Intent,請求另外一具備此功能的應用在地圖上顯示指定的位置。

(三)Intent 的工做流程

假設有一個 Activity A 須要啓動一個 Activity B。若是經過顯示 Intent 啓動時,系統會當即啓動該組件。使用隱式 Intent 時,Android 系統經過將 Intent 的內容與在設備上其餘應用的清單文件中聲明的 Intent 過濾器進行比較,從而找到要啓動的相應組件。若是 Intent 與 Intent 過濾器匹配,則系統將啓動該組件,並向其傳遞 Intent 對象。若是多個 Intent 過濾器兼容,則系統會顯示一個對話框,支持用戶選取要使用的應用。大體流程以下圖:異步

Intent 的工做流程

特別注意:ide

爲了確保應用的安全性,啓動 Service 時,必須使用顯式 Intent,且不要爲服務聲明 Intent 過濾器。使用隱式 Intent 啓動服務存在安全隱患,由於沒法肯定哪些服務將響應 Intent,且用戶沒法看到哪些服務已啓動。從 Android 5.0(API 級別 21)開始,若是使用隱式 Intent 調用 bindService(),系統會拋出異常。

(四)構建 Intent

一個 Intent 對象會攜帶一些信息,Android 系統會根據這些信息來肯定要啓動哪一個組件。具體來講會攜帶如下七種信息: ComponentName、Action、Category、Data 、Type、Extra、Flags。咱們能夠把這七種信息稱之爲 Intent 的七大屬性。下面咱們具體的來分析每個屬性的含義和使用方法:

  1. ComponentName:要啓動的組件名稱 指定了ComponentName 屬性的 Intent 已經明確了它將要啓動哪一個具體組件,所以這種 Intent 被稱爲顯式 Intent,沒有指定 ComponentName 屬性的 Intent 被稱爲隱式 Intent。隱式 Intent 沒有明確要啓動哪一個組件,應用會根據 Intent 指定的其餘信息去啓動符合條件的組件。代碼示例:
//聲明一個顯示意圖
Intent intent = new Intent();
ComponentName componentName = new ComponentName(MainActivity.this,SecondActivity.class);
intent.setComponent(componentName);
startActivity(intent);
複製代碼

除了經過 setComponent 爲 intent 指定要啓動的組件名稱外,還可使用 Intent 的構造函數:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
複製代碼

使用 Intent 構造函數設置組件名稱的方式,代碼更加的簡潔。

  1. Action:操做 當咱們要聲明一個隱式意圖的時候,就要用到 Action,它指定了被啓動的組件要完成的具體的操做,好比我想啓動一個能夠瀏覽照片的 Activity,能夠設置 Action 爲 ACTION_VIEW,或者啓動一個能夠發送郵件的 Activity,能夠設置 Action 爲 ACTION_SEND。Action 一般是和 Category 結合起來使用。Action 自己是一個字符串,除了系統定義好的,咱們也能夠本身定義,下面經過代碼來演示經過定義一個隱式 Intent 來啓動 Activity:

step1:在啓動方定義一個隱式 Intent

Intent intent = new Intent();
//自定義了一個action:com.cyy.jump
intent.setAction("com.cyy.jump");
//必須指定一個category
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
複製代碼

step2:爲被啓動方配置 <intent-filter>。具體作法是在 AndroidManifest.xml 中,爲被啓動的 Activity 設置上 :

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.cyy.jump" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
複製代碼

<intent-filter> 中,咱們設置了兩個屬性:<action><category>。的值必須與啓動方的 setAction 的值同樣:com.cyy.jump。

下面咱們運行一下項目,看看效果:

隱式Intent跳轉

跳轉成功。

假如咱們爲多個 Activity 配置了一樣的 會有什麼效果呢?

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.cyy.jump" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<activity android:name=".ThirdActivity">
    <intent-filter>
        <action android:name="com.cyy.jump" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
複製代碼

咱們運行一下,看看效果:

多個頁面響應同一個自定義Action

能夠看到,在同一個應用內,若是有多個 Activity 設置了一樣的 ,當在調用 startActivity() 後,系統會彈出一個選擇框,讓用戶本身選擇須要跳轉的 Activity。

這是啓動同一個應用內的頁面,假設其餘應用的某個 Activity 也設置了一樣的 呢?咱們能夠來試驗一下,新建一個項目,名叫 IntentDemo2,在 IntentDemo2 中新建一個頁面叫作 HomeActivity:

<activity android:name=".HomeActivity">
    <intent-filter>
        <action android:name="com.cyy.jump" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
 </activity>
複製代碼

再次運行項目,看看效果:

隱式Intent跳轉到其餘應用的頁面

能夠看到,在對話框中顯示出了 IntentDemo2 的選項,點擊便可跳轉 IntentDemo2 的 HomeActivity 頁面。

以上是自定義的 Action,下面咱們來演示一下使用系統定義的 Action,以 ACTION_SEARCH 爲例:

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEARCH);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
複製代碼

運行項目,查看效果:

系統定義的Action:ACTION_SEARCH

此時,系統也會彈出全部知足 ACTION_SEARCH 的應用列表提供給用戶選擇。

系統定義的一些 Action

Action 含義
ACTION_MAIN Android 的程序入口
ACTION_VIEW 顯示指定數據
ACTION_EDIT 編輯指定數據
ACTION_DIAL 顯示撥號面板
ACTION_CALL 直接呼叫 Data 中所帶的號碼
ACTION_ANSWER 接聽來電
ACTION_SEND 向其餘人發送數據(例如:彩信/email)

以上只是簡單列舉了一部分,更多的能夠參考官方文檔:Intent

  1. Category:類別 Category 屬性爲 Action 增長額外的附加類別信息。經常使用的如 CATEGORY_DEFAULT ,表示 Android 系統中默認的執行方式,按照普通的 Activity 的執行方式執行。在好比 CATEGORY_LAUNCHER ,設置該組件爲當前應用程序啓動器中優先級最高的,一般與程序入口動做 ACTION_MAIN 配合使用:
<activity android:name=".MainActivity">
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />

		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>
複製代碼
  1. Data & Type:數據&類型Data 屬性一般是爲 Action 屬性提供要操做的數據,例如撥打指定電話、發送短信時指定電話號碼和短信內容等數據。Data 屬性的值是一個 Uri 對象,格式以下:
schema://host:port/path
複製代碼
  • schema 協議
  • host 主機
  • port 端口
  • path 路徑

如:http://www.baidu.com:8080/a.jpg

系統內置的幾個 Data 屬性常量

協議 含義
tel: 號碼數據格式,後跟電話號碼
mailto: 郵件數據格式,後跟郵件收件人地址
smsto: 短信數據格式,後跟短信接收號碼
file:/// 文件數據格式,後跟文件路徑。注意必須是三根斜槓 ///
content:// 內容數據格式,後跟須要讀取的內容。ContentProvider 特有的格式

舉幾個例子給你們展現一下:

例1:打開一個網頁

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
複製代碼

打開一個網頁
例2:打電話

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("tel:18755555555"));
startActivity(intent);
複製代碼

撥打指定電話
Type 屬性用於指定 Data 所制定的 Uri 對應的 MIME 類型,一般應用於調用系統 App,好比實現查看文件(文本、圖片、音頻或者視頻等),經過指定文件的 MIME 類型,可讓系統知道用什麼程序打開該文件。

設置 Data 時,調用 setData() ,設置 Type 時,調用 setType ,注意,這兩個方法不能同時設置,會被覆蓋掉。若是想要同時設置 Data 和 Type,請調用 setDataAndType。咱們能夠查看一下源碼:

/** * Set the data this intent is operating on. This method automatically * clears any type that was previously set by {@link #setType} or * {@link #setTypeAndNormalize}. * * <p><em>Note: scheme matching in the Android framework is * case-sensitive, unlike the formal RFC. As a result, * you should always write your Uri with a lower case scheme, * or use {@link Uri#normalizeScheme} or * {@link #setDataAndNormalize} * to ensure that the scheme is converted to lower case.</em> * * @param data The Uri of the data this intent is now targeting. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #getData * @see #setDataAndNormalize * @see android.net.Uri#normalizeScheme() */
    public @NonNull Intent setData(@Nullable Uri data) {
        mData = data;
        mType = null;
        return this;
    }

	......
    
    /** * Set an explicit MIME data type. * * <p>This is used to create intents that only specify a type and not data, * for example to indicate the type of data to return. * * <p>This method automatically clears any data that was * previously set (for example by {@link #setData}). * * <p><em>Note: MIME type matching in the Android framework is * case-sensitive, unlike formal RFC MIME types. As a result, * you should always write your MIME types with lower case letters, * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize} * to ensure that it is converted to lower case.</em> * * @param type The MIME type of the data being handled by this intent. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #getType * @see #setTypeAndNormalize * @see #setDataAndType * @see #normalizeMimeType */
    public @NonNull Intent setType(@Nullable String type) {
        mData = null;
        mType = type;
        return this;
    }

	......
	
    /** * (Usually optional) Set the data for the intent along with an explicit * MIME data type. This method should very rarely be used -- it allows you * to override the MIME type that would ordinarily be inferred from the * data with your own type given here. * * <p><em>Note: MIME type and Uri scheme matching in the * Android framework is case-sensitive, unlike the formal RFC definitions. * As a result, you should always write these elements with lower case letters, * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalizeScheme} or * {@link #setDataAndTypeAndNormalize} * to ensure that they are converted to lower case.</em> * * @param data The Uri of the data this intent is now targeting. * @param type The MIME type of the data being handled by this intent. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #setType * @see #setData * @see #normalizeMimeType * @see android.net.Uri#normalizeScheme * @see #setDataAndTypeAndNormalize */
    public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
        mData = data;
        mType = type;
        return this;
    }
複製代碼

下面咱們經過一個例子來講明一下 Data 和 Type 的用法:

查看手機裏的一張圖片,地址爲:storage/emulated/0/DCIM/IMG_201910297_162012_328.jpg

File file = new File("storage/emulated/0/DCIM/IMG_201910297_162012_328.jpg");
Intent intent = new Intent();
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "image/jpeg");
startActivity(intent);
複製代碼

運行查看效果:

訪問手機裏的一張照片
訪問成功!

經常使用的 MIME 類型

文件格式 對應的MIME類型
.bmp image/bmp
.gif image/gif
.png image/png
.tif .tiff image/tiff
.jpe .jpeg .jpg image/jpeg
.txt text/plain
.xml text/xml
.html text/html
.css text/css
.js text/javascript
.mht .mhtml message/rfc822
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.rtf application/rtf
.xls application/vnd.ms-excel application/x-excel
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.ppt application/vnd.ms-powerpoint
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.pps application/vnd.ms-powerpoint
.ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow
.pdf application/pdf
.swf application/x-shockwave-flash
.dll application/x-msdownload
.exe application/octet-stream
.msi application/octet-stream
.chm application/octet-stream
.cab application/octet-stream
.ocx application/octet-stream
.rar application/octet-stream
.tar application/x-tar
.tgz application/x-compressed
.zip application/x-zip-compressed
.z application/x-compress
.wav audio/wav
.wma audio/x-ms-wma
.wmv video/x-ms-wmv
.mp3 .mp2 .mpe .mpeg .mpg audio/mpeg
.rm application/vnd.rn-realmedia
.mid .midi .rmi audio/mid
  1. Extra:額外 屬性用於添加一些附加信息,它的屬性值是一個 Bundle 對象,經過鍵值對的形式存儲數據。在隱式 Intent 中使用較少,主要用於顯示 Intent 傳遞數據。下面簡單演示一下:
//MainActivity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("name", "我叫 Android");
startActivity(intent);
複製代碼
//SecondActivity
Intent intent = getIntent();
if (intent != null) {
	String name = intent.getStringExtra("name");
	textView.setText(name);
}
複製代碼

Extra
6. Flags:標記 一般用來動態配置 Activity 的啓動模式。大部分狀況下,咱們都不須要設置 Flags,因此,對於 Flags 你們可以理解就行。下面,介紹幾個經常使用的:

FLAG_ACTIVITY_NEW_TASK:設置這個標記位的話,是爲 Activity 指定 「singleTask」 啓動模式,它的做用和在清單文件中指定該啓動模式的效果同樣。

FLAG_ACTIVITY_SINGLE_TOP:設置這個標記位的話,是爲 Activity 指定 「singleTop」 啓動模式,它的做用和在清單文件中指定該啓動模式的效果同樣。

FLAG_ACTIVITY_CLEAR_TOP:具備此標記位的 Activity ,在它啓動時,在同一個任務棧中全部位於它上面的 Activity 都要出棧。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具備這個標記的 Activity 不會出如今歷史 Activity 的列表中。它等同於在清單文件中指定 Activity 的屬性 android:excludeFromRecents="true"

2、隱式 Intent 詳解

在上一節的內容中,咱們介紹了 Intent 的七大屬性,也給你們演示瞭如何定義一個顯示 Intent和隱式 Intent,顯示 Intent 的使用更加的簡單,所以咱們就不過多介紹了,下面再給你們分析一下隱式 Intent。

(一)接收隱式 Intent

要聲明組件能夠接收哪些隱式 Intent,須要在清單文件中使用 <intent-filter> 元素爲組件聲明一個或多個 Intent 過濾器。每一個 <intent-filter> 中主要設置的屬性包括:<action><data><category>。當隱式 Intent 能夠匹配上其中一個<intent-filter> 時,系統就會將該 Intent 傳遞給應用組件(顯式 Intent 始終會傳遞給其目標組件,不管目標組件聲明的 <intent-filter> 是什麼)。

應用組件應該爲自身可執行的每一個獨特做業聲明單獨的 <intent-filter> 。例如,圖像庫應用中的一個 Activity 可能會有兩個 <intent-filter> ,分別用於查看圖像和編輯圖像。當 Activity 啓動時,將檢查 Intent 並根據 Intent 中的信息決定具體的行爲(例如,是否顯示編輯圖片控件)。

特別注意:在 <intent-filter> 中,必須設置一個默認的 <category><category android:name="android.intent.category.DEFAULT" />,否者組件不會接收隱式 Intent。

假如咱們不但願其餘應用啓動咱們的組件,只但願在本應用中使用組件,那麼咱們就不要在清單中聲明 <intent-filter> ,而且將該組件的 exported 屬性設置爲 false

下面咱們經過一個具體的例子來講明一下 <intent-filter> 的用法:

<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>
複製代碼

第一個 Activity MainActivity 是應用的主要入口:

  • ACTION_MAIN 操做指示這是主要入口,且不要求輸入任何 Intent 數據。
  • CATEGORY_LAUNCHER 類別指示此 Activity 的圖標應放入系統的應用啓動器。若是 元素未使用 icon 指定圖標,則系統將使用 元素中的圖標。

這兩個元素必須配對使用,Activity 纔會顯示在應用啓動器中。

第二個 Activity ShareActivity 旨在便於共享文本和媒體內容。儘管能夠經過從 MainActivity 進入此 Activity,但也能夠從發出隱式 Intent(與兩個 Intent 過濾器之一匹配)的另外一應用中直接進入 ShareActivity。

MIME 類型 application/vnd.google.panorama360+jpg 是一個指定全景照片的特殊數據類型

(二)聲明一個隱式 Intent 須要注意的地方

注意1:當沒有任何應用可以響應咱們調用 startActivity() 傳遞的隱式 Intent 時如何處理?

咱們自定義一個 Action,可是不讓任何 Activity 接收該 Intent:

Intent intent = new Intent();
intent.setAction("com.cyy.send");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
複製代碼

運行後,應用奔潰,Error 信息以下:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.cyy.send cat=[android.intent.category.DEFAULT] }

爲了不這種狀況的出現,咱們在調用 startActivity() 前,須要調用 resolveActivity() 驗證是否有 Activity 能夠接收 Intent,具體作法以下:

Intent intent = new Intent();
intent.setAction("com.cyy.send");
intent.addCategory(Intent.CATEGORY_DEFAULT);

if (intent.resolveActivity(getPackageManager()) != null) {
	startActivity(intent);
}
複製代碼

若是 resolveActivity 的結果爲非空,則表示至少有一個應用可以處理該 Intent,此時便可安全的調用 startActivity()。

注意2:當有多個應用能夠響應咱們的隱式 Activity 時,系統會彈出一個選擇框,讓用戶選擇須要打開的應用,用戶也能夠選擇記住要本身打開的應用,這樣下次就不會再彈出選擇框。那麼假如我但願每次都彈窗,不讓用戶記住呢?咱們可使用 createChooser() 建立 Intent。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));

String title = "請選擇瀏覽器";
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(intent, title);
if (intent.resolveActivity(getPackageManager()) != null) {
	startActivity(chooser);
}
複製代碼

運行效果以下:

createChooser
參考資料:

  1. Google 官方資料:Intent 和 Intent 過濾器
  2. 《Android 開發藝術探索》 任玉剛
相關文章
相關標籤/搜索