Intent详解

Intent详解

一、什么是Intent

贴一个官方解释:

An intent is an abstract description of an operation to be performed. It can be used with Context#startActivity(Intent)to launch an Activity , broadcastIntent to send it to any interested BroadcastReceiver components, and android.content.Context#startService or android.content.Context#bindService to communicate with a background android.app.Service .
An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed.

Intent 名为意图,它是要执行操作的抽象描述,可以在activity、broadcast、service等组件进行请求操作时用来充当消息传递对象(intent可传递基础类型数据或者可序列化的对象数据)此时Intent包含要执行的动作的抽象描述。

二、分类

Intent主要分为两大类:显示Intent和隐式Intent。

显示Intent

定义 :显式的指定要启动组件的类名。 一般用于同应用内的组件启动 因为可以方便的知道启动组件的类名

使用方式:1、通过Intent构造函数传入要启动类名
2、直接设置要启动类名(调用setComponent(), setClass(), setClassName()传入组件名)

隐式Intent

定义 :不知道要启动的组件名,通过匹配组件在manifest文件注册的的Intent-filter,找到匹配组件并启动,一般用于启动外部应用的组件

使用方式:通过设置action、Category、data等去匹配符合条件的组件(注意:如果有多个组件的intent-filter满足条件,那么系统会弹出一个对话框,由用户决定启动哪个组件 )

假如我们不希望其他应用启动我们的组件,只希望在本应用中使用组件,那么我们就不要在清单中声明intent-filter,并且将该组件的 exported 属性设置为 false

三、intent工作流程

以Activity A启动Activity B为例说明:
1、首先Activity A调用startActivity()并传入的Intent
2、系统会根据该Intent的条件搜索Android系统中所有匹配的组件
3、若找到了匹配intent的intent-filters所属的组件(Activity B),则启动该组件,并回调onCreate()方法,同时将Intent传递过去

(此处步骤2中Android system搜索匹配组件实际是指PMS)

Intent

四、构建Intent

Intent的构建主要是为其设置各种属性包括:actiondatatypecomponentcategoryextrasflags。其中主要属性是actiondata 。下面我们来详细解析下每个属性的意义和作用。

action

action是指要执行的动作。它很大程度上决定了category和data中应传入的信息;除了官方定义的我们也可以自己定义action,以便让其他应用程序启动自己的组件。action可以通过setAction来设置或者在Intent构造函数中设置。

Action 含义
ACTION_MAIN Android 的程序入口
ACTION_VIEW 显示指定数据
ACTION_EDIT 编辑指定数据
ACTION_DIAL 显示拨号面板
ACTION_CALL 直接呼叫 Data 中所带的号码
ACTION_ANSWER 接听来电
ACTION_SEND 向其他人发送数据(例如:彩信/email)
常用Action:
public static final String ACTION_MAIN = “android.intent.action.MAIN”; //Android 的程序入口
public static final String ACTION_VIEW = “android.intent.action.VIEW”; //显示指定数据
public static final String ACTION_WEB_SEARCH = “android.intent.action.WEB_SEARCH”;//网页搜索关键字
public static final String ACTION_CALL = “android.intent.action.CALL”; //直接呼叫 Data 中所带的号码

data

data 属性通常是为 Action 属性提供要操作的数据,Data 属性的值是一个 Uri 对象,格式是:schema://host:port/path 。其各个字段含义如下:
* schema 协议 比如“http”、“https”、“tel”…
* host 主机名如“google.com”,如果定义为“*”则表示任意主机名
* port 端口号
* path 路径

可以通过setData设置data 。

type

Type 属性用于指定 Data 所制定的 Uri 对应的MIME 类型。MIME 类型是设定某种扩展名的 文件用一种应用程序来打开的方式类型。常见MIME类型 : “image/png”、”image/jpeg”

可以通过setType设置 type。
但是要注意的是如果您需要同时设置URI和MIME类型,只能调用setDataAndType()方法,而不能分别调用setData()和setType(),因为调用setData()时会首先将setType()中的内容置空,反之亦然。

component

component是要启动目标组件的名字。对于显式启动,这是不可缺省的,没有指定 ComponentName 属性的 Intent 被称为隐式 Intent。

可以通过调用setComponent(), setClass(), setClassName()等方法设置或者通过Intent的构造方法设置。

category

category是要执行动作的目标所具有的特质或行为归类(为 Action 增加额外的附加类别信息)
几个常见的category如下:

Intent.CATEGORY_DEFAULT(android.intent.category.DEFAULT)// 默认的category

Intent.CATEGORY_PREFERENCE(android.intent.category.PREFERENCE) //表示该目标Activity是一个首选项界面;

Intent.CATEGORY_BROWSABLE(android.intent.category.BROWSABLE)//指定了此category后,在网页上点击图片或链接时,系统会考虑将此目标Activity列入可选列表,供用户选择以打开图片或链接。

Intent.CATEGORY_LAUNCHER:目标activity是任务栈的第一个activity,也就是应用程序的启示activity。

可以通过setCategory来进行设置。

flags

flags为intent添加元数据(meta-data),flag可以指导系统以何种方式启动一个activity、是否将启动的activity放在该应用的任务栈中,等等。
常用flags:

FLAG_ACTIVITY_NEW_TASK:设置这个标记位的话,是为 Activity 指定 “singleTask” 启动模式,它的作用和在清单文件中指定该启动模式的效果一样。
FLAG_ACTIVITY_SINGLE_TOP:设置这个标记位的话,是为 Activity 指定 “singleTop” 启动模式,它的作用和在清单文件中指定该启动模式的效果一样。
FLAG_ACTIVITY_CLEAR_TOP:具有此标记位的 Activity ,在它启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的 Activity 不会出现在历史 Activity 的列表中。它等同于在清单文件中指定 Activity 的属性 android:excludeFromRecents=“true”

可以通过setFlags来进行设置。

extras

extras用于添加一些附加信息,它的属性值是一个 Bundle 对象,通过键值对的形式存储数据。

需要注意的是在使用putExtras方法设置Bundle对象之后,系统进行的不是引用操作,而是复制操作,所以如果设置完之后再更改bundle实例中的数据,将不会影响Intent内部的附加信息。

可以通过putExtras来进行设置。

Intent属性小结

Component name, action, data, and category代表了intent的属性,通过设置这些参数,系统可以筛选出符合条件的目标组件。但是,Extras、Flags这两个参数系统不会用来筛选目标组件。

五、IntentFilter匹配规则

IntentFilter是manifest文件中组件内部的一个标签,该标签描述了组件具备什么特性,如果您未配置intent-filters,那个该组件只能被显式启动。我们在mainfest中设置的intent-filters如果可以匹配某个隐式Intent那么该组件就可以被启动。IntentFilter在做匹配时主要是根据action, type, category这三个属性且匹配优先级是:action>data>category

action匹配规则:

如果Intent指明定了action,则目标组件的IntentFilter的action列表中就必须包含有这个action,否则不能匹配。一个Intent Filter中可声明多个action,此时Intent中的action与其中的任一个action在字符串形式上完全相同即可匹配成功。

特殊情况:
如果filter中没有设置任何action 那么所有的intent匹配都会失败
如果action只和category组合使用(隐式调用的条件),intent中不指定action,那么无法启动目标组件
如果action和category、data组合使用,intent中不指定action但是filter中至少存在一个action 那么是可以匹配成功的

category的匹配规则:

Intent-filter可定义零到多个category标签,intent中的定义的每一个category都需要匹配上intent-filter中的category标签,反之不成立(intent-filter中的category标签可能比intent中的定义的category多)。所以无论intent-filter中是否定义了category标签,未添加category的intent总能匹配上该intent-filter。

注意:
通过startActivity()或startActivityForResult()方法隐式启动的intent中,将自动被添加一个CATEGORY_DEFAULT的category,所以若您希望自己的activity能够被隐式启动,则需要在intent-filter中添加一个android.intent.category.DEFAULT的category标签。

data匹配规则:

intent filter可定义零到多个data标签每个data标签都能设置mimeType和URI 结构,其中URI可分成四部分:scheme, host, port 和 path。但是是有一个线性依赖:若scheme 未指定,则host被忽略;若host未指定,则port被忽略;
若scheme和host均未指定,则path被忽略;

在intent中添加的data只需要匹配一部分intent-filter中的data:

  • 若filter只定义了scheme,则intent的data定义的URI中只要包含了相同的scheme,就能匹配;
  • 若filter只定义了scheme和host,则intent的data定义的URI中只要包含了相同的scheme和host,就能匹配;
  • 若filter只定义了scheme、host和port,则intent的data定义的URI中只要包含了相同的scheme、host和port,就能匹配

六、Intent源码解析

Intent的源码大约1W行其中对我们有用的大概有以下几类:

构造函数

public Intent() {
}
public Intent(String action) {
    setAction(action);
}
public Intent(String action, Uri uri) {
    setAction(action);
    mData = uri;
}

Intent提供了8中构造函数供我们使用。

属性的set和get方法

public @Nullable Uri getData() {
    return mData;
}
public @Nullable String getType() {
    return mType;
}
public @NonNull Intent setData(@Nullable Uri data) {
    mData = data;
    mType = null;
    return this;
}
public @NonNull Intent setType(@Nullable String type) {
    mData = null;
    mType = type;
    return this;
}
public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
    mData = data;
    mType = type;
    return this;
}
//...

此处我们看到下setData/Type,单独调用都会把对方置空,所以如果你想设置data和type需要调用setDataAndType,而不能先调用其中一个然后再调用另一个。
此外还有putExtra等相关函数这里就不做分析了,有兴趣的可自行查看源码。
最后看一个平时我们并不常用的函数toUri:

public String toUri(@UriFlags int flags) {
    StringBuilder uri = new StringBuilder(128);
    if ((flags&URI_ANDROID_APP_SCHEME) != 0) {
        if (mPackage == null) {
            throw new IllegalArgumentException(
                    "Intent must include an explicit package name to build an android-app: "
                    + this);
        }
        uri.append("android-app://");
        uri.append(mPackage);
        String scheme = null;
        if (mData != null) {
            scheme = mData.getScheme();
            if (scheme != null) {
                uri.append('/');
                uri.append(scheme);
                String authority = mData.getEncodedAuthority();
                if (authority != null) {
                    uri.append('/');
                    uri.append(authority);
                    String path = mData.getEncodedPath();
                    if (path != null) {
                        uri.append(path);
                    }
                    String queryParams = mData.getEncodedQuery();
                    if (queryParams != null) {
                        uri.append('?');
                        uri.append(queryParams);
                    }
                    String fragment = mData.getEncodedFragment();
                    if (fragment != null) {
                        uri.append('#');
                        uri.append(fragment);
                    }
                }
            }
        }
        toUriFragment(uri, null, scheme == null ? Intent.ACTION_MAIN : Intent.ACTION_VIEW,
                mPackage, flags);
        return uri.toString();
    }
    String scheme = null;
    if (mData != null) {
        String data = mData.toString();
        if ((flags&URI_INTENT_SCHEME) != 0) {
            final int N = data.length();
            for (int i=0; i<N; i++) {
                char c = data.charAt(i);
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
                        || c == '.' || c == '-') {
                    continue;
                }
                if (c == ':' && i > 0) {
                    // Valid scheme.
                    scheme = data.substring(0, i);
                    uri.append("intent:");
                    data = data.substring(i+1);
                    break;
                }

                // No scheme.
                break;
            }
        }
        uri.append(data);

    } else if ((flags&URI_INTENT_SCHEME) != 0) {
        uri.append("intent:");
    }

    toUriFragment(uri, scheme, Intent.ACTION_VIEW, null, flags);

    return uri.toString();
}

它的作用是把一个Intent转化为一个Uri,转化后的Uri包含原先Intent的action, categories, type, flags, package, component, and extras等属性。同时Intent还提供了getIntent方法把Uri转换回Intent。
Intent转换Uri过程就是将Intent的属性值读取出来进行拼接然后序列化。不过需要注意的是它没有关于 Bundle 的参数传递,所以转换过程会把设置的bundle数据丢失。
利用该函数我们可以不必在调用startActivity前去new Intent而是通过getIntent把一个Uri转化为Intent然后再startActivity,这样做的好处是startActivity中传入的intent变为”可控”的。

七、相关问题

path、pathPrefix、pathPattern 之间的区别

path 用来匹配完整的路径,如:http://example.com/blog/abc.html,这里将 path 设置为 /blog/abc.html 才能够进行匹配;
pathPrefix 用来匹配路径的开头部分,拿上来的 Uri 来说,这里将 pathPrefix 设置为 /blog 就能进行匹配了;
pathPattern 用表达式来匹配整个路径,这里需要说下匹配符号与转义。
匹配符号:
“” 用来匹配0次或更多,如:“a” 可以匹配“a”、“aa”、“aaa”…
“.” 用来匹配任意字符,如:“.” 可以匹配“a”、“b”,“c”…
因此 “.” 就是用来匹配任意字符0次或更多,如:“.html” 可以匹配 “abchtml”、“chtml”,“html”,“sdf.html”…

Intent传递数据的大小限制

intent传递过大的数据会导致TransactionTooLargeException,其本质原因是intent使用binder进行数据传递。在过程中Intent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。但是Binder 的事务缓冲区大小为1M,并且该缓冲区是进程共享的。

解决方法:
1、避免传递过大数据
2、使用 EventBus 的粘性事件来解决

如果确定要传递过大数据则可以思考数据源是那种类型,然后传输数据源地址,这样传输的数据就会变为一个地址从而避免了TransactionTooLargeException。

查询是否有Activity可以匹配我们指定Intent的组件

在启动Activity时传入intent找不到符合条件的Activity那么程序将会崩溃,所以我们每次startActivity时最好查询下是否有Activity可以匹配我们指定Intent的组件,可以使用以下方法:

  • PackageManager的resolveActivity或者Intent的resolveActivity方法会获得最适合Intent的一个Activity
  • 调用PackageManager的queryIntentActivities会返回所有成功匹配Intent的Activity

android.intent.action.MAIN 与android.intent.category.LAUNCHER、android.intent.category.HOME的区别

android.intent.action.MAIN

应用的入口即最先启动的组件

android.intent.category.LAUNCHER

决定是否在桌面显示图标

android.intent.category.HOME

按住“HOME”键,该程序显示在HOME列表里

有多个匹配组件时如何设置每次都弹窗

当有多个应用可以响应我们的隐式 Activity 时,系统会弹出一个选择框,让用户选择需要打开的应用,用户也可以选择记住要自己打开的应用,这样下次就不会再弹出选择框。那么假如我希望每次都弹窗,不让用户记住呢?我们可以使用 createChooser() 创建 Intent

Intent chooser = Intent.createChooser(sendIntent, title);

pendingintent

pendingintent 大体有3中使用方式:

1、notification(包装一个Notification的启动Intent)

2、app widget (按Home键启动的activity)

3、延时版的intent(如AlarmManager)

使用方法:

PendingIntent.getActivity() for an Intent that starts an Activity.
PendingIntent.getService() for an Intent that starts a Service.
PendingIntent.getBroadcast() for an Intent that starts a BroadcastReceiver.

最后我们可以得知app luancher 是通过寻找 ACTION_MAIN action 和 CATEGORY_LAUNCHER 来确定要启动的app的

在packmanager中有queryXX函数可以返回匹配某种intent的所有app列表

八、小结

Android应用是基于组件化构成的,当我们想要启动某些组件时需要显式或隐式指定要启动的组件,此时Intent作为对要启动组件的一个抽象描述,它内部不同字段记录要启动组件的不同“特征”,然后我们把Intent发送给AMS,AMS会通过PMS去查询当前已安装的引用所有注册的组件从而找到符合Intent描述的组件,然后AMS会启动相应组件。

Intent还有一个应用就是deeplink,这个以后有时间再详细看下。

参考文章:

posted @ 2020-10-13 10:46  Robin132929  阅读(540)  评论(0编辑  收藏  举报