- 一個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中最重要的特征之一就是可以利用一個帶有action
的intent
使當前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;
如果isIntentSafe
為true
, 那么至少有一個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);
下面是一個演示了如何創建一個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)。
為了顯示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_SEND
或ACTION_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會被自動忽略。