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

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

intent-filters.png

1. 啟動其他App的Activity

1.1 新建一個隱式的Intent對象

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

// 撥打電話
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ǎng)頁
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);

當(dāng)然Intent還有通過putExtra相關(guān)方法來傳入數(shù)據(jù),不過這個要接受和發(fā)送的雙方都約定好才能使用,而Android系統(tǒng)默認(rèn)的方式則是通過Uri并設(shè)置合適的MIME類型,這樣系統(tǒng)才能找到合理的處理這個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

// 建立一個日歷event,注意這個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比如地圖類的就不會被啟動.

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

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

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

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

b. 也可以通過這個Intent的resolveActivity()方法來判斷

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

1.3. 顯示App候選框

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

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

但是還有一種情況,就是我就是要每次都要選擇不同的activity來處理,比如分享,顯然我不可能只分享到某個app,那要如何操作呢?這就需要用到APP Chooser,會每次都顯示,讓用戶下選擇.使用時(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é)果

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

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

示例代碼如下:

// 啟動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的格式是什么,比如通訊錄會返回帶URI的結(jié)果,相機(jī)App會將Bitmap對象保存在"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)限請求,詳細(xì)參考Security and Permissions

3. 允許其他的App來啟動你的Activity

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

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

3.1 添加Intent Filter

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

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

如果有任何兩對action和data互斥,則需要添加一個獨(dú)立的intent filter將它們分開,比如你的Activity要處理text和image,Action為ACTION_SEND和ACTION_SENDTO,就必須定義兩個intent filter來將它們分開,因?yàn)锳CTION_SENDTO一定要用Uri數(shù)據(jù)來適配接收者地址(如果混在一起寫,未攜帶地址的Intent也能通過ACTION_SENDTO,進(jìn)而啟動你的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對象,你應(yīng)該在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 ...
    }
}

3.3 返回結(jié)果

要給啟動你的Activity的Activity放回結(jié)果很簡單,只需調(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(也可以自己給個任意int數(shù)值).

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

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

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

總結(jié)

這篇講的Intent都是比較基礎(chǔ)的,也比較全面,對于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對象與Intent Filter過濾匹配過程詳解
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容