11) 十分鍾學會android--Intent消息處理與傳遞詳解


  • 一個Android app通常都會有多個activities。 每個activity的界面都扮演者用戶接口的角色,允許用戶執行一些特定任務(例如查看地圖或者是開始拍照等)。為了讓用戶能夠從一個activity跳到另一個activity,我們的app必須使用Intent來定義自己的意圖。當使用startActivity()的方法,且參數是intent時,系統會使用這個 Intent 來定義並啟動合適的app組件。使用intents甚至還可以讓app啟動另一個app里面的activity。
  • 一個 Intent 可以顯式的指明需要啟動的模塊(用一個指定的Activity實例),也可以隱式的指明自己可以處理哪種類型的動作(比如拍一張照等)。
  • 本章節將演示如何使用Intent 與其他app執行一些基本的交互。比如啟動另外一個app,從其他app接受數據,以及使得我們的app能夠響應從其他app中發出的intent等。

Lessons

  • Intent的發送(Sending the User to Another App )

    演示如何創建一個隱式Intent喚起能夠接收這個動作的App。

  • 接收Activity返回的結果(Getting a Result from an Activity)

    演示如何啟動另外一個Activity並接收返回值。

  • Intent過濾(Allowing Other Apps to Start Your Activity)

    演示如何通過定義隱式的Intent的過濾器來使我們的應用能夠被其他應用喚起。

Intent的發送

Android中最重要的特征之一就是可以利用一個帶有actionintent使當前app能夠跳轉到其他app。例如:如果我們的app有一個地址想要顯示在地圖上,我們並不需要在app里面創建一個activity用來顯示地圖,而是使用Intent來發出查看地址的請求。Android系統則會啟動能夠顯示地圖的程序來呈現該地址。

正如在1.1章節:建立你的第一個App(Building Your First App)中所說的,我們必須使用intent來在同一個app的兩個activity之間進行切換。通常是定義一個顯式(explicit)的intent,它指定了需要啟動組件的類名。然而,當想要喚起不同的app來執行某個動作(比如查看地圖),則必須使用隱式(implicit)的intent。

本課會介紹如何為特殊的動作創建一個implicit intent,並使用它來啟動另一個app去執行intent中的action。

建立隱式的Intent

Implicit intents並不聲明要啟動組件的具體類名,而是聲明一個需要執行的action。這個action指定了我們想做的事情,例如查看,編輯,發送或者是獲取一些東西。Intents通常會在發送action的同時附帶一些數據,例如你想要查看的地址或者是你想要發送的郵件信息。數據的具體類型取決於我們想要創建的Intent,比如Uri或其他規定的數據類型,或者甚至也可能根本不需要數據。

如果數據是一個Uri,會有一個簡單的Intent() constructor 用於定義action與data。

例如,下面是一個帶有指定電話號碼的intent。

Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);

 

當app通過執行startActivity()啟動這個intent時,Phone app會使用之前的電話號碼來撥出這個電話。

下面是一些其他intent的例子:

  • 查看地圖:
// Map point based on address
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
// Or map point based on latitude/longitude
// Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);

 

  • 查看網頁:
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);

 

至於另外一些需要extra數據的implicit intent,我們可以使用 putExtra() 方法來添加那些數據。 默認的,系統會根據Uri數據類型來決定需要哪些合適的MIME type。如果我們沒有在intent中包含一個Uri, 則通常需要使用 setType() 方法來指定intent附帶的數據類型。設置MIME type 是為了指定應該接受這個intent的activity。例如:

  • 發送一個帶附件的email:
Intent emailIntent = new Intent(Intent.ACTION_SEND);
// The intent does not have a URI, so declare the "text/plain" MIME type
emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"jon@example.com"}); // recipients
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject");
emailIntent.putExtra(Intent.EXTRA_TEXT, "Email message text");
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"));
// You can also attach multiple items by passing an ArrayList of Uris

 

  • 創建一個日歷事件:
Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
Calendar beginTime = Calendar.getInstance().set(2012, 0, 19, 7, 30);
Calendar endTime = Calendar.getInstance().set(2012, 0, 19, 10, 30);
calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
calendarIntent.putExtra(Events.TITLE, "Ninja class");
calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo");

 

Note: 這個intent for Calendar的例子只使用於>=API Level 14。

Note: 請盡可能的將Intent定義的更加確切。例如,如果想要使用ACTION_VIEW 的intent來顯示一張圖片,則還應該指定 MIME type 為image/*.這樣能夠阻止其他能夠 "查看" 其他數據類型的app(比如一個地圖app) 被這個intent叫起。

驗證是否有App去接收這個Intent

盡管Android系統會確保每一個確定的intent會被系統內置的app(such as the Phone, Email, or Calendar app)之一接收,但是我們還是應該在觸發一個intent之前做驗證是否有App接受這個intent的步驟。

Caution: 如果觸發了一個intent,而且沒有任何一個app會去接收這個intent,則app會crash。

為了驗證是否有合適的activity會響應這個intent,需要執行queryIntentActivities() 來獲取到能夠接收這個intent的所有activity的list。若返回的List非空,那么我們才可以安全的使用這個intent。例如:

PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isIntentSafe = activities.size() > 0;

 

如果isIntentSafetrue, 那么至少有一個app可以響應這個intent。false則說明沒有app可以handle這個intent。

Note:我們必須在第一次使用之前做這個檢查,若是不可行,則應該關閉這個功能。如果知道某個確切的app能夠handle這個intent,我們也可以向用戶提供下載該app的鏈接。(see how to link to your product on Google Play).

使用Intent啟動Activity

當創建好了intent並且設置好了extra數據后,通過執行startActivity() 將intent發送到系統。若系統確定了多個activity可以handle這個intent,它會顯示出一個dialog,讓用戶選擇啟動哪個app。如果系統發現只有一個app可以handle這個intent,則系統將直接啟動該app。

startActivity(intent);

intents-choice.png

下面是一個演示了如何創建一個intent來查看地圖的完整例子,首先驗證有app可以handle這個intent,然后啟動它。

// Build the intent
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);

// Verify it resolves
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
boolean isIntentSafe = activities.size() > 0;

// Start an activity if it's safe
if (isIntentSafe) {
    startActivity(mapIntent);
}

 

顯示分享App的選擇界面

請注意,當以startActivity()的形式傳遞一個intent,並且有多個app可以handle時,用戶可以在彈出dialog的時候選擇默認啟動的app(通過勾選dialog下面的選擇框,如上圖所示)。該功能對於用戶有特殊偏好的時候非常有用(例如用戶總是喜歡啟動某個app來查看網頁,總是喜歡啟動某個camera來拍照)。

然而,如果用戶希望每次都彈出選擇界面,而且每次都不確定會選擇哪個app啟動,例如分享功能,用戶選擇分享到哪個app都是不確定的,這個時候,需要強制彈出選擇的對話框。(這種情況下用戶不能選擇默認啟動的app)。

intent-chooser.png

為了顯示chooser, 需要使用createChooser()來創建Intent

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().getText(R.string.chooser_title);
// Create and start the chooser
Intent chooser = Intent.createChooser(intent, title);
startActivity(chooser);

 

這樣就列出了可以響應createChooser()中Intent的app,並且指定了標題。

接收Activity返回的結果

啟動另外一個activity並不一定是單向的。我們也可以啟動另外一個activity然后接受一個返回的result。為接受result,我們需要使用startActivityForResult() ,而不是startActivity()

例如,我們的app可以啟動一個camera程序並接受拍的照片作為result。或者可以啟動聯系人程序並獲取其中聯系的人的詳情作為result。

當然,被啟動的activity需要指定返回的result。它需要把這個result作為另外一個intent對象返回,我們的activity需要在onActivityResult()的回調方法里面去接收result。

Note:在執行startActivityForResult()時,可以使用explicit 或者 implicit 的intent。當啟動另外一個位於的程序中的activity時,我們應該使用explicit intent來確保可以接收到期待的結果。

啟動Activity

對於startActivityForResult() 方法中的intent與之前介紹的並無太大差異,不過是需要在這個方法里面多添加一個int類型的參數。

該integer參數稱為"request code",用於標識請求。當我們接收到result Intent時,可從回調方法里面的參數去判斷這個result是否是我們想要的。

例如,下面是一個啟動activity來選擇聯系人的例子:

static final int PICK_CONTACT_REQUEST = 1;  // The request code
...
private void pickContact() {
    Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
    pickContactIntent.setType(Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
    startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
}

 

接收Result

當用戶完成了啟動之后activity操作之后,系統會調用我們activity中的onActivityResult() 回調方法。該方法有三個參數:

  • 通過startActivityForResult()傳遞的request code。
  • 第二個activity指定的result code。如果操作成功則是RESULT_OK ,如果用戶沒有操作成功,而是直接點擊回退或者其他什么原因,那么則是RESULT_CANCELED
  • 包含了所返回result數據的intent。

例如,下面顯示了如何處理pick a contact的result:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check which request we're responding to
    if (requestCode == PICK_CONTACT_REQUEST) {
        // Make sure the request was successful
        if (resultCode == RESULT_OK) {
            // The user picked a contact.
            // The Intent's data Uri identifies which contact was selected.

            // Do something with the contact here (bigger example below)
        }
    }
}

 

本例中被返回的Intent使用Uri的形式來表示返回的聯系人。

為正確處理這些result,我們必須了解那些result intent的格式。對於自己程序里面的返回result是比較簡單的。Apps都會有一些自己的api來指定特定的數據。例如,People app (Contacts app on some older versions) 總是返回一個URI來指定選擇的contact,Camera app 則是在data數據區返回一個 Bitmap (see the class about Capturing Photos).

讀取聯系人數據

上面的代碼展示了如何獲取聯系人的返回結果,但沒有說清楚如何從結果中讀取數據,因為這需要更多關於content providers的知識。但如果想知道的話,下面是一段代碼,展示如何從被選的聯系人中讀出電話號碼。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check which request it is that we're responding to
    if (requestCode == PICK_CONTACT_REQUEST) {
        // Make sure the request was successful
        if (resultCode == RESULT_OK) {
            // Get the URI that points to the selected contact
            Uri contactUri = data.getData();
            // We only need the NUMBER column, because there will be only one row in the result
            String[] projection = {Phone.NUMBER};

            // Perform the query on the contact to get the NUMBER column
            // We don't need a selection or sort order (there's only one result for the given URI)
            // CAUTION: The query() method should be called from a separate thread to avoid blocking
            // your app's UI thread. (For simplicity of the sample, this code doesn't do that.)
            // Consider using CursorLoader to perform the query.
            Cursor cursor = getContentResolver()
                    .query(contactUri, projection, null, null, null);
            cursor.moveToFirst();

            // Retrieve the phone number from the NUMBER column
            int column = cursor.getColumnIndex(Phone.NUMBER);
            String number = cursor.getString(column);

            // Do something with the phone number...
        }
    }
}

 

Note:在Android 2.3 (API level 9)之前對Contacts Provider的請求(比如上面的代碼),需要聲明READ_CONTACTS權限(更多詳見Security and Permissions)。但如果是Android 2.3以上的系統就不需要這么做。但這種臨時權限也僅限於特定的請求,所以仍無法獲取除返回的Intent以外的聯系人信息,除非聲明了READ_CONTACTS權限。

 

Intent過濾

前兩節課主要講了從一個app啟動另外一個app。但如果我們的app的功能對別的app也有用,那么其應該做好響應的准備。例如,如果創建了一個social app,它可以分享messages 或者 photos 給好友,那么最好我們的app能夠接收ACTION_SEND 的intent,這樣當用戶在其他app觸發分享功能的時候,我們的app能夠出現在待選對話框。

通過在manifest文件中的<activity>標簽下添加<intent-filter>的屬性,使其他的app能夠啟動我們的activity。

當app被安裝到設備上時,系統可以識別intent filter並把這些信息記錄下來。當其他app使用implicit intent執行 startActivity() 或者 startActivityForResult()時,系統會自動查找出那些可以響應該intent的activity。

添加Intent Filter

為了盡可能確切的定義activity能夠handle的intent,每一個intent filter都應該盡可能詳盡的定義好action與data。

若activity中的intent filter滿足以下intent對象的標准,系統就能夠把特定的intent發送給activity:

  • Action:一個想要執行的動作的名稱。通常是系統已經定義好的值,如ACTION_SENDACTION_VIEW。 在intent filter中通過<action>指定它的值,值的類型必須為字符串,而不是API中的常量(看下面的例子)

  • Data:Intent附帶數據的描述。在intent filter中通過<data>指定它的值,可以使用一個或者多個屬性,我們可以只定義MIME type或者是只指定URI prefix,也可以只定義一個URI scheme,或者是他們綜合使用。

Note: 如果不想handle Uri 類型的數據,那么應該指定 android:mimeType 屬性。例如 text/plain or image/jpeg.

  • Category:提供一個附加的方法來標識這個activity能夠handle的intent。通常與用戶的手勢或者是啟動位置有關。系統有支持幾種不同的categories,但是大多數都很少用到。而且,所有的implicit intents都默認是 CATEGORY_DEFAULT 類型的。在intent filter中用<category>指定它的值。

在我們的intent filter中,可以在<intent-filter>元素中定義對應的XML元素來聲明我們的activity使用何種標准。

例如,這個有intent filter的activity,當數據類型為文本或圖像時會處理ACTION_SEND的intent。

<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"/>
        <data android:mimeType="image/*"/>
    </intent-filter>
</activity>

 

每一個發送出來的intent只會包含一個action與data類型,但handle這個intent的activity的 <intent-filter>可以聲明多個<action><category><data> 。

如果任何的兩對action與data是互相矛盾的,就應該創建不同的intent filter來指定特定的action與type。

例如,假設我們的activity可以handle 文本與圖片,無論是ACTION_SEND還是ACTION_SENDTO 的intent。在這種情況下,就必須為兩個action定義兩個不同的intent filter。因為ACTION_SENDTO intent 必須使用 Uri 類型來指定接收者使用 send 或 sendto 的地址。例如:

<activity android:name="ShareActivity">
    <!-- filter for sending text; accepts SENDTO action with sms URI schemes -->
    <intent-filter>
        <action android:name="android.intent.action.SENDTO"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="sms" />
        <data android:scheme="smsto" />
    </intent-filter>
    <!-- filter for sending text or images; accepts SEND action and text or image data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

 

Note:為了接受implicit intents, 必須在我們的intent filter中包含 CATEGORY_DEFAULT 的category。startActivity()和startActivityForResult()方法將所有intent視為聲明了CATEGORY_DEFAULT category。如果沒有在的intent filter中聲明CATEGORY_DEFAULT,activity將無法對implicit intent做出響應。

更多sending 與 receiving ACTION_SEND intents執行social sharing行為的,請查看上一課:接收Activity返回的結果(Getting a Result from an Activity)

在Activity中Handle發送過來的Intent

為了決定采用哪個action,我們可以讀取Intent的內容。

可以執行getIntent() 來獲取啟動我們activity的那個intent。我們可以在activity生命周期的任何時候去執行這個方法,但最好是在onCreate()或者onStart()里面去執行。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    // Get the intent that started this activity
    Intent intent = getIntent();
    Uri data = intent.getData();

    // Figure out what to do based on the intent type
    if (intent.getType().indexOf("image/") != -1) {
        // Handle intents with image data ...
    } else if (intent.getType().equals("text/plain")) {
        // Handle intents with text ...
    }
}

 

返回Result

如果想返回一個result給啟動的那個activity,僅僅需要執行setResult(),通過指定一個result code與result intent。操作完成之后,用戶需要返回到原來的activity,通過執行finish() 關閉被喚起的activity。

 // Create intent to deliver some kind of result data
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
setResult(Activity.RESULT_OK, result);
finish();

 

我們必須總是指定一個result code。通常不是RESULT_OK就是RESULT_CANCELED。我們可以通過Intent 來添加需要返回的數據。

Note:默認的result code是RESULT_CANCELED.因此,如果用戶在沒有完成操作之前點擊了back key,那么之前的activity接受到的result code就是"canceled"。

如果只是純粹想要返回一個int來表示某些返回的result數據之一,則可以設置result code為任何大於0的數值。如果我們返回的result只是一個int,那么連intent都可以不需要返回了,可以調用setResult()然后只傳遞result code如下:

setResult(RESULT_COLOR_RED);
finish();

Note:我們沒有必要在意自己的activity是被用startActivity() 還是 startActivityForResult()方法所叫起的。系統會自動去判斷該如何傳遞result。在不需要的result的case下,result會被自動忽略。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM