[Android] Intent的使用

在某一個Activity創造一個intent要求送出時,通常是為了啟動App內的核心元件(Activity、Service或是Broadcast Receiver),我們會使用Intent。在Intent中標記要執行的動作,說明我們需要開啟的元件及其類型,以便讓系統去尋找,比如說:

Intent intent = new Intent(this, ResultActivity.class);

這是打開新的Activity,又或是:

intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

這是要在VIEW的Activity或App裡面打開網址。

所以,每個核心元件(Activity、Service或是Broadcast Receiver)能負責什麼要求的Intent,是由它們在AndroidManifest.xml裡面的的Intent-Filter元素來告知Android系統。Intent-Filter是用來決定每一個核心元件需要去回應的Intent為何,如果是Intent有清楚註記要啟動的元件那就用不到Intent-filter,反之,就需要倚仗Intent-filter了。

比方說上面提到的例子

Intent intent = new Intent(this, ResultActivity.class);

這個就是啟動名為ResultActivity的Activity,因為已經清楚指明要啟動的Activity名字了,所以不需要檢查是否滿足該元件的Intent-filter。

如何建立Intent

以下會介紹相關的名詞:
  • Component Name:這是可有可無的,雖然是這樣講,但如果有指明名稱,就是Explicit Intent,反之,就是Implicit Intent。如果要啟動的是某種Service,那就非得指明不可,不然你無法預期會啟動的是哪個Service。未指明的話,會由系統決定。
  • Action:描述要一般需要執行的action的字串。在Intent類別裡面已經有內定的action。
  • Data:參考到(reference)要操作的資料的Uri物件。
  • Category:是一個包含了有關於處理Intent的元件的種類之額外資訊的字串。Intent類別裡面已經定義了數種Category可供使用。
  • Extra:包含了有關於所要求執行的action所需資訊的鍵值對。Intent類別裡面已經定義好數種EXTRA.*的key名稱可供使用,你也可以定義自己的鍵值,記得前面要加上你的app的package名稱。
  • Flags:作為intent的metadata。告訴Android系統如何啟動以及在啟動後如何對待一個Activity
如前面所提,Intent分兩種:
  • Explicit Intent:有清楚指定要啟動的核心元件名稱的。這會直接啟動該元件。
    系統對於explicit intent,會直接啟動該intent內詳述的元件。
  • Implicit Intent:沒有指定名稱,但有指定action的。這種系統會幫你尋找符合的元件然後讓你選取使用。
    對於這類intent,系統會藉由比較intent的內容與intent filter來去尋找適當的元件啟動。如果系統有找到,那系統會將此intent遞送給該元件,若系統找到多個元件,系統會跳出dialog共使用者選擇。
注意:
  • 自己的Service如果不想被其他外部的App呼叫,那就不要設定Filter,而且呼叫自己的service時都用名稱。
  • 為了避免外部的App啟動自己的元件,可以在android:exported屬性設定為false。此值的預設值在此一提:如果該元件沒有去定義filter屬性,就是預設為false,只供內部使用;如果有定義filter屬性,那就預設為true。
  • 若某元件都沒有定義intent filter,那它只能以explicit intent啟動。

建立Explicit Intent

通常用在
  • 啟動特定activity
  • 啟動特定service
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downlontent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
以上是官方網站的範例碼。啟動下載的service。

建立Implicit Intent

通常用在執行自己的App無法提供的功能,透過詳細指明action以啟動設備上面代替執行此功能的app。

請看範例:

// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}
注意if判斷式,這是用來檢查有無任何App可執行ACTION_SEND的功能。因為不保證一定有可以執行此action的App,如果系統找不到,則有可能crash,此檢查可避免crash。

製作Chooser Dialog

送出Implicit Intent後,系統可能會找到一個以上的App可執行此intent,則我們必須處理給使用者選擇要使用哪個App來執行此Intent。要顯示Chooser,必須用createChooser()方法:

Intent intent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);
// Verify the intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

這是官網上的範例(原始是startActivity(sendIntent),應為筆誤,我已修改),注意他的次序,第一個intent是要做send,然後用這個intent去做chooser的intent(名為chooser),最後才檢查有沒有能回應的App,再啟動intent。

接收一個Implicit Intent

你的App也可以公開宣告你可以接收那些implicit intent,你可以在你的manifest file下每個元件裡面的<intent-filter> 宣告intent filter屬性:
  • <action>:宣告可接受的Intent action。
  • <data>:宣告可接受的data種類。可用一個以上的屬性說明清楚資料的URI和MIME type。
  • <category>:宣告可接受的Intent category。
下面是官方範例:

<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

這是宣告了可以接收SEND的action的intent。

  • 雖然可接收多個action、data和category的設定,但你要考慮你的元件是否可以處理這些屬性設定的組合。
  • 如果你只是要處理特定action、data和category的設定的組合,可以設定一個以上的intent filter。
  • 系統會比較intent和元件的intent filter的內容。intent要被遞送到某個元件,必須通過這三個屬性的檢查。如果受測元件有一個以上的intent filter,那只要通過其中一個intent filter算通過了。

Filter測試過程

  • Action:
    • Filter裡面可以設定0至多個action。
    • 如果filter裡面有列出一個或一個以上的action,那intent必須滿足其中一個,反之,如果filter裡面沒有列出action,除非受測的intent裡面沒有設定action,否則不會通過測試。
  • Category:
    • Filter裡面可以設定0至多個category。
    • Intent內的每一個category都必須符合filter裡面的一個category,反之,如果filter設定的category數目較intent多,則該intent不需要符合該filter裡面所有的category。所以即使intent裡面沒有設定任何category,也可以通過category的測試。
  • Data:
    • Filter裡面可以設定0至多個 <data> 元素。
    • 每一個data元素可以指明一個URI結構和data type(MIME media type)。URI的屬性結構如下:
      <scheme>://<host>:<port>/<path>
      • 如果沒有指明scheme,host會被忽略。
      • 如果沒有指明host,port會被忽略。
      • 如果scheme和host都沒有指明,path會被忽略。
    • 對於filter和intent內的URI的比較:
      • 若filter僅指明了一個scheme,有該scheme的URI都符合該filter。
      • 若filter指明了一個scheme和一個authority,不管這些URI的path為何,有相同的scheme和authority的URI都符合該filter。
      • 若filter指明了一個scheme、一個authority和一個path,只要URI有相同的scheme、authority和path就符合該filter。
      • path若含有wildcar asterisk(*)也允許。
    • data測試會比較intent和filter內的URI和MIME type:
      • 若filter內沒有任何URI和MIME type,那沒有包含任何URI和MIME type的intent就會通過測試。
      • 包含一個URI但沒有MIME type的intent會通過測試只有當他的URI和filter的URI格式相同並且filter沒有指明MIME type。
      • 包含一個MIME type但沒有URI的intent會通過測試只有當filter列出相同的MIME type並且filter沒有指明任何URI格式。
      • 包含一個MIME type和URI的intent:
        • intent會通過MIME type測試是當他的MIME type符合filter列出來的MIME type
        • intent會通過URI測試是當他的URI符合filter所列出的URI或是它有一個content: 或 file: URI並且filter沒有指明任何URI。換句話說,即使該元件的filter只有指明MIME type,元件是推定支援content: 和 file: 的。
        • 這表示元件本來就預期可以從file或content provider取得本地data,元件的filter僅需表示他可以接收處理的MIME type即可。
        • 另一種情況是filter僅表明scheme和data type。比方說元件希望從網路上(scheme = "http")取得影音資料(type = "video/*")撥放。

使用Pending Intent

我們會使用PendingIntent物件來包裝一個intent物件。PendingIntent主要目的就是用來允許外面的App去使用裡面所包含的intent,就像是直接從你自己的App內執行一樣。

主要使用情況如下,:
  • 當使用者用你的Notification執行一個動作時,宣告一個intent來執行。
  • 當使用者用你的App Widget執行一個動作時,宣告一個intent來執行。
  • 在未來某個特定時間宣告一個intent來執行。



留言