Develop系列-API Guides-应用组件-Intents and Intent Filters

Intents 和 Intent Filters

(Intent译为意图,让人比较费解,实际上解释为“消息”更加合理,干脆就不翻译了)

Intent是能在app组件间传递的消息体,基本使用方式有如下三种:

  • 启动activity
    • startActivity:intent描述需要启动的activity和必须的数据
    • startActivityForResult:intent启动的activity结束后,会触发onActivityResult回调
  • 启动服务
    • startService:intent描述需要启动的service和必须的数据
    • bindService
  • 发送广播

Intent类型

  • 显式intents:通过指定包名+类名来明确需要启动的组件,一般用在app内部使用。
  • 隐式intents:不指定具体的组件,通过定义一些动作或者条件,由系统来匹配能够执行动作或者满足条件的组件。

  1. A创建intent,作为入参startActivity
  2. Android系统搜索所有app的intent filter用于适配A发出的intent。(如果有多个匹配上,会弹框给用户选择)
  3. Android系统通过onCreate启动匹配上的B,并把A的intent当做入参

Caution: 为了确保你的app是安全的,通常用显式intent的方式来启动service,因为用隐式intent启动服务是有安全风险的,隐式intent无法预知启动的service就是你想要的那个。

创建一个Intent

Intent主要包含如下属性:

组件名

需要启动的组件名,一般指包名+类名:com.example.ExampleActivity

对于显式intent是必选的,对于隐式intent,不能指定。

image

Action(动作?)

指定执行一般动作的字符串,比如:

    /**
     * Activity Action: 给用户显示数据。比如:在联系人中显示具体
     * 联系人的信息;在邮件发件人中,显示可用的发件人列表。
     * <p>Input: {@link #getData} is URI from which to retrieve data.
     * <p>Output: nothing.
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_VIEW = "android.intent.action.VIEW";

你可以自定义Action,一般优先使用Intent类中默认支持的Action。

在Intent构造函数或者通过setAction指定:

    public Intent setAction(String action) {
        mAction = action != null ? action.intern() : null;
        return this;
    }
    public Intent(String action) {
        setAction(action);
    }

Data(数据)

URI一般是具体MIME类型的数据,由action来执行。比如:action是ACTION_EDIT,那么数据一般是可编辑文档的URI。

为了让系统能够更精确地适配到你发出来的Intent,在指定Data的同时最好把Type也指定。当你的数据以content:开头时,系统可以判断出具体类型。

    public Intent setType(String type) {
        mData = null;
        mType = type;
        return this;
    }
    public Intent setData(Uri data) {
        mData = data;
        mType = null;
        return this;
    }
    public Intent setDataAndType(Uri data, String type) {
        mData = data;
        mType = type;
        return this;
    }

上述代码说明type和data如果需要同时指定,请使用setDataAndType,否则不论是setType还是setData都会将另一个设置为null。

Category(类别)

任意数量的category可以放在一个intent中,但是大多数intents不需要category。

常用的如下:

  • CATEGORY_BROWSABLE:activity可以通用web浏览器来显示链接索引,比如图片或者emai信息。
  • CATEGORY_LAUNCHER:activity是此应用初始启动的activity,并且会在launcher中显示。
    public Intent addCategory(String category) {
        if (mCategories == null) {
            mCategories = new ArraySet<String>();
        }
        mCategories.add(category.intern());
        return this;
    }

系统可以通过componet name、action、data、category这四个属性来匹配具体启动的组件,然而intent还可以设置更多不影响组件匹配的属性,如下介绍的Extras和Flags:

Extras(额外信息)

    public Intent putExtra(String name, 类型 value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.put类型(name, value);
        return this;
    }

其中类型可以是:boolean、byte、char,short、int、long、float、double、String、CharSequence、Parcelable +上述类型的数组(类型[ ]) + Serializable、Bundle、IBinder

    public Intent putExtras(Bundle extras) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.putAll(extras);
        return this;
    }
    public Intent putExtras(Intent src) {
        if (src.mExtras != null) {
            if (mExtras == null) {
                mExtras = new Bundle(src.mExtras);
            } else {
                mExtras.putAll(src.mExtras);
            }
        }
        return this;
    }

比如,当创建发送email的intent,可以用EXTRA_EMAIL 来指定收件人,用EXTRA_SUBJECT 来指定邮件主题。

可以自定义Extra_*的常量。

Flags(标记)

flags可以通知Android系统怎么启动一个activity(比如,activity的所属task)和如何对待启动后的activity(比如,activity是否属于最近activities启动列表)

    public Intent setFlags(int flags) {
        mFlags = flags;
        return this;
    }

显式intent例子

// 在Activity中执行,this表示当前Context
// fileUrl是个URL字串,比如"http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

隐式intent例子

Caution: 隐式intent通过startActivity时,有可能系统没有任何apps可以处理你发出的intent,这会导致你的app crash,为了确保系统中至少有一个app能够处理你发出的intent,请发送intent之前先用resolveActivity进行判断

// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

应用程序选择器

当有多个app可以处理你发出的隐式intent,系统会弹框给用户选择,用户可以选择一个并且将其设为默认项。

然后如果多个app可以处理你发出的隐式intent,并且用户想每次都选择不同的app来处理,有就必须强制显示选择框给用户选择。这种选择框每次都会询问用户使用哪个app,并且没法将其中一个设为默认。比较常见的例子:通过ACTION_SEND来分享数据,用户通常根据使用场景选择不同的app进行分享。

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);
}

接收隐式intent

在manifest文件中定义<intent-filter>可以指定app能够处理的隐式intent,当然显式intent的话只匹配组件名,其他的都直接忽略。

一组filter定义一个app能够支撑的能力

  • <action>
  • <data>
  • <category>:对于activity,filter必须包含CATEGORY_DEFAULT,系统在startActivity和startActivityForResult时只会处理带CATEGORY_DEFAULT类型的activities。
<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"/>
    </intent-filter>
</activity>

系统对<action>, <data>, <category>三个属性都会进行匹配,缺一不可。

用filter并不是一种安全的方式来保护你的组件不被其他apps启动,何况开发者可用通过包名+类名来直接显式intent启动你的组件,如果你的组件只能由同一个app里面的其他组件来启动,那么请将exported属性设置为false

Caution:还是那句话,服务请显式启动。

Note:对于广播来说,可以在代码中动态注册和去注册,运行时指定时间内有效的广播可以用此方法。

过滤器例子

<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

使用Pending Intent

需要预定好,但是有触发条件之后才能执行的intent,常用场景:

  • Notification:Android中点击每个下拉栏的通知,可以跳转到其他界面,这个通知中包含的用户点击后才执行的动作就是pendingintent,在NotificationManager中执行这个intent
  • App Widget:用于预先定义widget上具体需要执行的动作,比如按Music Widget上的播放按钮,来播放音乐同时刷新widget视图,滚动显示歌词,这个响应按键动作的intent就是pendingintent
  • AlarmManager:指定时间之后执行的intent

创建方式:

Intent匹配测试

Action测试

可匹配场景:

  1. intent包含的action在filter定义的action列表中
  2. intent如果没有action,filter包含至少一个action

filter没有定义action,必定匹配失败

Category测试

可匹配场景:

  1. filter中的category至少包含所有intent中的category
  2. intent如果没有category

Data测试

每个<data>可以指定一个URI结构:<scheme>://<host>:<port>/<path>

结构中每个属性都是可选项,但是有具体关系:

  • scheme不匹配,host直接忽略
  • host不匹配,port直接忽略
  • scheme和host都不匹配,path直接忽略

intent中URI和filter中URI比较时,只匹配filter中的URI

  • filter只定义scheme,所有scheme的URIs都匹配
  • filter定义scheme和authority,但是没有path,那么同样scheme和authority能匹配上,而不管path
  • filter定义scheme、authority、path,全匹配

Note:path可用通配符*来适配


intent中URI和MIME type,与filter中URI和MIME type匹配规则:

  1. intent定义URI或者MIME,而filter都不定义,匹配上。
  2. intent定义URI,但没有定义MIME(没有定义,也不能从URI中识别出MIME),filter定义能匹配上的URI并且没有定义MIME,匹配上
  3. intent定义MIME,但没有定义URI,filer定义相同的MIME,并且没有指定URI
  4. intent定义MIME(可从URI中识别出MIME也算)和URI,filter定义的MIME必须包含intent定义的MIME,同时URI必须匹配上,有一种情况例外:intent定义的URI是以content:和file:开头的,那么filter只需要能匹配MIME,就算匹配上。

Intent匹配

intent匹配不仅仅用于启动组件,还可以有其他用途,比如Launcher通过匹配规格来查找需要在桌面显示的所有图标(activities必须包含ACTION_MAINCATEGORY_LAUNCHER category)

packageManager有一系列query…()和resolve…()方法来列出可适配对应intent的组件集合。

posted @ 2014-08-09 00:18  konger  阅读(351)  评论(0编辑  收藏  举报