使用Intent来进行App间的基本交互

Intent是Android中存放一个操作的抽象描述的数据结构,可用于在不同的组件以及不同的app间进行传递,是消息的载体,有显式和隐式之分(若两者都有则执行显式的),下图是隐式Intent的传递过程.

intent-filters.png

1. 启动其他App的Activity

1.1 新建一个隐式的Intent对象

隐式Intent不需要声明目标组件的class name,但是要声明要执行的action,这个action就是你想要执行的操作,比如view,edit,send等等. 如果需要给这些action绑定数据,比如要传地址信息,邮件内容等,就需要用到Uri类型的数据,绑定到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);

// 访问网页
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);

当然Intent还有通过putExtra相关方法来传入数据,不过这个要接受和发送的双方都约定好才能使用,而Android系统默认的方式则是通过Uri并设置合适的MIME类型,这样系统才能找到合理的处理这个Intent的activities,如下示例:

// 发送一封带附件的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");
  • 注意: 准确的设置Intent的参数的意义在于系统能够准确的找到执行的组件,比如你想要查看图片,于是你使用了ACTION_VIEW的Intent,如果你再调用setType()设置类型为"image/*",那么其他的带ACTION_VIEW的Activity比如地图类的就不会被启动.

1.2. 验证是否有app能够接收你的Intent

虽然Android系统保证某些类型的Intent都至少有一个内置的app能够接收,但是由于Android系统的分散和自由,可能有的软件被删除等等原因,你想用Intent启动其他app的时候,一定要验证一下是否有app接收,如果你直接启动而没有app接收则app会crash(ActivityNotFoundException);

a. 可以使用PackageManager的queryIntentActivities()来验证,该方法需要一个Intent的参数,返回所有能够接收该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候选框

调用startActivity()的时候:

  • 如果只有一个activity能执行,则系统会直接启动那个activity;
  • 如果有多个activity能执行,系统会显示一个dialog让用户来选择使用哪个activity来执行,并且带有一个"将此选择设置为默认"的选项,也就是如果你选择了"将此选择设置为默认"的选项后再点击相应的activity,那么这个intent以后都将直接由这个activity来执行,这种适用于打开网页这种需要保持使用某个app的activity来执行的情况.

但是还有一种情况,就是我就是要每次都要选择不同的activity来处理,比如分享,显然我不可能只分享到某个app,那要如何操作呢?这就需要用到APP Chooser,会每次都显示,让用户下选择.使用时只需调用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中返回结果

启动其他App的Activity是双向的,你可以传给别的Activity数据,你也可以接收它的返回结果,想要接收到结果则需要用startActivityForResult()来启动Intent,而不是startActivity(),然后在onActivityResult()的回调中接收.

  • 注意: 显式和隐式的Intent用startActivityForResult()都能启动,但是如果是在自己app内的交互,你应该要使用显式的Intent来确保能接收到期望的结果.

示例代码如下:

// 启动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);
}

// 接收和处理返回结果
@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的结果,相机App会将Bitmap对象保存在"data"数据中返回,下面看下具体的通讯录返回的联系人数据:

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

注意不同版本的通讯录的权限请求,详细参考Security and Permissions

3. 允许其他的App来启动你的Activity

上面两点讲的是如何启动其他App一起如何处理返回结果,现在要讲的时如何让其他App来启动你的Activity,要实现这个功能,你的Activity需要做一些准备,比如你想支持分享操作,需要设置ACTION_SEND,具体实现在Manifest中添加<intent-filter>元素并将相应action加入.

  • Tips: 系统对于Intent的操作是这样的:当你app安装的时候,系统会识别你在intent filter中的配置并将相应信息添加到系统内部的一个catalog,当一个app启动一个隐式的intent时,系统就会把intent中的信息拿出来去catalog中查找.

3.1 添加Intent Filter

为了让你Activity来处理合适的intent,你需要尽可能精细的设置好action和data的值. 系统的标准如下:

  • Action: 字符串,也就是intent filter中的<action>元素,指定activity要执行的action,可以设置一个或多个,当然如果不设置就无法被其他App启动了.
  • Data: 与intent绑定的数据的描述,也就是<data>元素,可以设置一个或多个,但是推荐的只设置 android:mimeType这个属性即可,比如"text/plain"或"image/jpeg".
  • Category: 给Intent提供另外一种分类的方式,也就是<category>元素,可以设置一个或多个,默认都是"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互斥,则需要添加一个独立的intent filter将它们分开,比如你的Activity要处理text和image,Action为ACTION_SEND和ACTION_SENDTO,就必须定义两个intent filter来将它们分开,因为ACTION_SENDTO一定要用Uri数据来适配接收者地址(如果混在一起写,未携带地址的Intent也能通过ACTION_SENDTO,进而启动你的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>中必须要设置CATEGORY_DEFAULT,不然匹配不了.

具体可以参考Receiving Simple Data from Other Apps.

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

用getIntent()方法拿到Intent对象,你应该在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 返回结果

要给启动你的Activity的Activity放回结果很简单,只需调用setResult()方法然后结束时调用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();

返回结果中需要设置返回码,选RESULT_OK或RESULT_CANCELED(也可以自己给个任意int数值).

  • 注意:默认的返回码是RESULT_CANCELED,如果你没设置返回码就返回,默认就是该值.

如果不想返回数据,或者只想返回一个int类型的数据,那么可以直接设置返回码:

setResult(RESULT_COLOR_RED);
finish();
  • 注意: 没必要去检查你的Activity是被startActivity()启动还是startActivityForResult()启动,如果启动你的intent可能需要返回,你就调用setResult()来设置相应的返回结果,对方接收不了就会被系统ignore掉.-----------------------------------------------------说了这么多你是不是想特别特别特别特别特别特别特别特别想知道如何检查你的Activity是被哪个启动的???反正我是特别想就对了..................... 可以用getCallingActivity()的返回值为空与否来判断.

总结

这篇讲的Intent都是比较基础的,也比较全面,对于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过滤匹配过程详解
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容