使用Intent來(lái)進(jìn)行App間的基本交互

Intent是Android中存放一個(gè)操作的抽象描述的數(shù)據(jù)結(jié)構(gòu),可用于在不同的組件以及不同的app間進(jìn)行傳遞,是消息的載體,有顯式和隱式之分(若兩者都有則執(zhí)行顯式的),下圖是隱式Intent的傳遞過(guò)程.

intent-filters.png

1. 啟動(dòng)其他App的Activity

1.1 新建一個(gè)隱式的Intent對(duì)象

隱式Intent不需要聲明目標(biāo)組件的class name,但是要聲明要執(zhí)行的action,這個(gè)action就是你想要執(zhí)行的操作,比如view,edit,send等等. 如果需要給這些action綁定數(shù)據(jù),比如要傳地址信息,郵件內(nèi)容等,就需要用到Uri類型的數(shù)據(jù),綁定到intent的對(duì)象中,如下示例:

// 撥打電話
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);

// 查看地圖
// 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);

// 訪問(wèn)網(wǎng)頁(yè)
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);

當(dāng)然Intent還有通過(guò)putExtra相關(guān)方法來(lái)傳入數(shù)據(jù),不過(guò)這個(gè)要接受和發(fā)送的雙方都約定好才能使用,而Android系統(tǒng)默認(rèn)的方式則是通過(guò)Uri并設(shè)置合適的MIME類型,這樣系統(tǒng)才能找到合理的處理這個(gè)Intent的activities,如下示例:

// 發(fā)送一封帶附件的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

// 建立一個(gè)日歷event,注意這個(gè)API 14及以上才支持
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");
  • 注意: 準(zhǔn)確的設(shè)置Intent的參數(shù)的意義在于系統(tǒng)能夠準(zhǔn)確的找到執(zhí)行的組件,比如你想要查看圖片,于是你使用了ACTION_VIEW的Intent,如果你再調(diào)用setType()設(shè)置類型為"image/*",那么其他的帶ACTION_VIEW的Activity比如地圖類的就不會(huì)被啟動(dòng).

1.2. 驗(yàn)證是否有app能夠接收你的Intent

雖然Android系統(tǒng)保證某些類型的Intent都至少有一個(gè)內(nèi)置的app能夠接收,但是由于Android系統(tǒng)的分散和自由,可能有的軟件被刪除等等原因,你想用Intent啟動(dòng)其他app的時(shí)候,一定要驗(yàn)證一下是否有app接收,如果你直接啟動(dòng)而沒(méi)有app接收則app會(huì)crash(ActivityNotFoundException);

a. 可以使用PackageManager的queryIntentActivities()來(lái)驗(yàn)證,該方法需要一個(gè)Intent的參數(shù),返回所有能夠接收該intent的activity的信息,包在一個(gè)List里面,可以直接判斷List的size是否大于0即可:

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

b. 也可以通過(guò)這個(gè)Intent的resolveActivity()方法來(lái)判斷

// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

1.3. 顯示App候選框

調(diào)用startActivity()的時(shí)候:

  • 如果只有一個(gè)activity能執(zhí)行,則系統(tǒng)會(huì)直接啟動(dòng)那個(gè)activity;
  • 如果有多個(gè)activity能執(zhí)行,系統(tǒng)會(huì)顯示一個(gè)dialog讓用戶來(lái)選擇使用哪個(gè)activity來(lái)執(zhí)行,并且?guī)в幸粋€(gè)"將此選擇設(shè)置為默認(rèn)"的選項(xiàng),也就是如果你選擇了"將此選擇設(shè)置為默認(rèn)"的選項(xiàng)后再點(diǎn)擊相應(yīng)的activity,那么這個(gè)intent以后都將直接由這個(gè)activity來(lái)執(zhí)行,這種適用于打開(kāi)網(wǎng)頁(yè)這種需要保持使用某個(gè)app的activity來(lái)執(zhí)行的情況.

但是還有一種情況,就是我就是要每次都要選擇不同的activity來(lái)處理,比如分享,顯然我不可能只分享到某個(gè)app,那要如何操作呢?這就需要用到APP Chooser,會(huì)每次都顯示,讓用戶下選擇.使用時(shí)只需調(diào)用Intent的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 (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

2. 從其他Activity中返回結(jié)果

啟動(dòng)其他App的Activity是雙向的,你可以傳給別的Activity數(shù)據(jù),你也可以接收它的返回結(jié)果,想要接收到結(jié)果則需要用startActivityForResult()來(lái)啟動(dòng)Intent,而不是startActivity(),然后在onActivityResult()的回調(diào)中接收.

  • 注意: 顯式和隱式的Intent用startActivityForResult()都能啟動(dòng),但是如果是在自己app內(nèi)的交互,你應(yīng)該要使用顯式的Intent來(lái)確保能接收到期望的結(jié)果.

示例代碼如下:

// 啟動(dòng)intent
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);
}

// 接收和處理返回結(jié)果
@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)
        }
    }
}

為了成功處理返回結(jié)果,你必須要知道返回的Intent的格式是什么,比如通訊錄會(huì)返回帶URI的結(jié)果,相機(jī)App會(huì)將Bitmap對(duì)象保存在"data"數(shù)據(jù)中返回,下面看下具體的通訊錄返回的聯(lián)系人數(shù)據(jù):

@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...
        }
    }
}

注意不同版本的通訊錄的權(quán)限請(qǐng)求,詳細(xì)參考Security and Permissions

3. 允許其他的App來(lái)啟動(dòng)你的Activity

上面兩點(diǎn)講的是如何啟動(dòng)其他App一起如何處理返回結(jié)果,現(xiàn)在要講的時(shí)如何讓其他App來(lái)啟動(dòng)你的Activity,要實(shí)現(xiàn)這個(gè)功能,你的Activity需要做一些準(zhǔn)備,比如你想支持分享操作,需要設(shè)置ACTION_SEND,具體實(shí)現(xiàn)在Manifest中添加<intent-filter>元素并將相應(yīng)action加入.

  • Tips: 系統(tǒng)對(duì)于Intent的操作是這樣的:當(dāng)你app安裝的時(shí)候,系統(tǒng)會(huì)識(shí)別你在intent filter中的配置并將相應(yīng)信息添加到系統(tǒng)內(nèi)部的一個(gè)catalog,當(dāng)一個(gè)app啟動(dòng)一個(gè)隱式的intent時(shí),系統(tǒng)就會(huì)把intent中的信息拿出來(lái)去catalog中查找.

3.1 添加Intent Filter

為了讓你Activity來(lái)處理合適的intent,你需要盡可能精細(xì)的設(shè)置好action和data的值. 系統(tǒng)的標(biāo)準(zhǔn)如下:

  • Action: 字符串,也就是intent filter中的<action>元素,指定activity要執(zhí)行的action,可以設(shè)置一個(gè)或多個(gè),當(dāng)然如果不設(shè)置就無(wú)法被其他App啟動(dòng)了.
  • Data: 與intent綁定的數(shù)據(jù)的描述,也就是<data>元素,可以設(shè)置一個(gè)或多個(gè),但是推薦的只設(shè)置 android:mimeType這個(gè)屬性即可,比如"text/plain"或"image/jpeg".
  • Category: 給Intent提供另外一種分類的方式,也就是<category>元素,可以設(shè)置一個(gè)或多個(gè),默認(rèn)都是"CATEGORY_DEFAULT"

示例代碼如下:

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

如果有任何兩對(duì)action和data互斥,則需要添加一個(gè)獨(dú)立的intent filter將它們分開(kāi),比如你的Activity要處理text和image,Action為ACTION_SEND和ACTION_SENDTO,就必須定義兩個(gè)intent filter來(lái)將它們分開(kāi),因?yàn)锳CTION_SENDTO一定要用Uri數(shù)據(jù)來(lái)適配接收者地址(如果混在一起寫,未攜帶地址的Intent也能通過(guò)ACTION_SENDTO,進(jìn)而啟動(dòng)你的Activity,這樣就不能很好的處理了),如下示例:

<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>
  • 注意: 為了接收隱式的intent,你的<intent-filter>中必須要設(shè)置CATEGORY_DEFAULT,不然匹配不了.

具體可以參考Receiving Simple Data from Other Apps.

3.2 在你的Activity中處理接收到的Intent

用getIntent()方法拿到Intent對(duì)象,你應(yīng)該在onCreate()或onStart()中來(lái)處理,如:

@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 ...
    }
}

3.3 返回結(jié)果

要給啟動(dòng)你的Activity的Activity放回結(jié)果很簡(jiǎn)單,只需調(diào)用setResult()方法然后結(jié)束時(shí)調(diào)用finish()方法即可.如下:

// 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();

返回結(jié)果中需要設(shè)置返回碼,選RESULT_OK或RESULT_CANCELED(也可以自己給個(gè)任意int數(shù)值).

  • 注意:默認(rèn)的返回碼是RESULT_CANCELED,如果你沒(méi)設(shè)置返回碼就返回,默認(rèn)就是該值.

如果不想返回?cái)?shù)據(jù),或者只想返回一個(gè)int類型的數(shù)據(jù),那么可以直接設(shè)置返回碼:

setResult(RESULT_COLOR_RED);
finish();
  • 注意: 沒(méi)必要去檢查你的Activity是被startActivity()啟動(dòng)還是startActivityForResult()啟動(dòng),如果啟動(dòng)你的intent可能需要返回,你就調(diào)用setResult()來(lái)設(shè)置相應(yīng)的返回結(jié)果,對(duì)方接收不了就會(huì)被系統(tǒng)ignore掉.-----------------------------------------------------說(shuō)了這么多你是不是想特別特別特別特別特別特別特別特別想知道如何檢查你的Activity是被哪個(gè)啟動(dòng)的???反正我是特別想就對(duì)了..................... 可以用getCallingActivity()的返回值為空與否來(lái)判斷.

總結(jié)

這篇講的Intent都是比較基礎(chǔ)的,也比較全面,對(duì)于Android其他部分的理解還是比較重要的.

Reference

  1. Interacting with Other Apps
  2. Sending the User to Another App
  3. Getting a Result from an Activity
  4. Allowing Other Apps to Start Your Activity
  5. How to know if an activity is called using startActivityForResult or simply called by using startActivity?
  6. Android中Intent對(duì)象與Intent Filter過(guò)濾匹配過(guò)程詳解
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容