Intent学习笔记

Intent简介

Android中提供了Intent机制来协助应用间的交互与通讯,或者采用更准确的说法是,Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。Intent这个英语单词的本意是“目的、意向”等, Intent的使用并不是直接的函数调用,相对函数调用来说,它是更为抽象的概念,利用Intent所实现的软件复用的粒度是Activity/Service,比函数复用更高一些,另外耦合也更为松散。

Intent是一种运行时绑定(runtime binding)机制,它能在程序运行的过程中连接两个不同的组件,通过Intent应用程序可以向Android表达某种请求或者意愿,Android会根据意愿的内容选择适当的组件来相应。

组件名称 方法名称
Activity startActivity()
startActivityForResult()
Service startService()
bindService()
Broadcasts sendBroadcast()
sendOrderBroadcast()
sendStickyBroadcast()

Intent数据传递

Intent传递数据简单来说就两种形式,一种直接传递通过intent.putExtra,另一种是通过Bundle对象来传递,更深入一点事实上都是通过Bundle传递的,也是说当我们采用第一种的时候底层还是采用的Bundle,再深入就是他们都是通过Map对象传递数据的。

网上有人总结了也就是更细化了Intent传递数据的方式

  • 简单或基本数据类型
  • 传递复杂数据类型
  • 传递Serializable对象
  • Parcelable对象
  • 通过Singleton单例模式传递数据

前面1和2数据传递方式都很简单,这里就讨论一下3和4两种传递方式吧,在java中我们都知道Serializable适用于序列化和反序列化的,但是Android中又出现一个Parcelable呢,事实上重点在于两者的存储媒介不同,Serializable使用IO读写存储在硬盘上,而Parcelable是直接在内存中读写,很明显内存的读写速度通常大于IO读写,所以在Android中通常优先选择Parcelable。第3种数据传递方式在实现上非常方面,只需要我们在创建javabean时直接实现Serializable接口就可以了,第4中就相对复杂一些了。

选择序列化方法的原则

  • 在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
  • Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
  • Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

实现Parcelable步骤

  1. implements Parcelable
  2. 重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据
  3. 重写describeContents方法,内容接口描述,默认返回0就可以
  4. 实例化静态内部对象CREATOR实现接口Parcelable.Creator

下面是一个简单的demo:

单例模式传值

通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。通过单例模式传值也是效率最高的一种方式。

Intent相关属性

Intent有以下7个组成部分:

  1. component(组件):目的组件
  2. action(动作):用来表现意图的行为
  3. category(类别):用来表现动作的类别
  4. data(数据):用来表现动作操作的数据
  5. type(数据类型):对于data范围的描写
  6. extras(扩展信息):扩展信息
  7. flags(标志位):Activity的启动方式

IntentFilter

顾名思义,IntentFilter对象负责过滤掉组件无法响应和处理的Intent,只将自己关心的Intent接收进来进行处理。 IntentFilter实行“白名单”管理,即只列出组件乐意接受的Intent,但IntentFilter只会过滤隐式Intent,显式的Intent会直接传送到目标组件。 Android组件可以有一个或多个IntentFilter,每个IntentFilter之间相互独立,只需要其中一个验证通过则可。除了用于过滤广播的IntentFilter可以在代码中创建外,其他的IntentFilter必须在AndroidManifest.xml文件中进行声明。

IntentFilter中具有和Intent对应的用于过滤Action,Data和Category的字段,一个隐式Intent要想被一个组件处理,必须通过这三个环节的检查。

intent-filter

如果我们Android系统有多个应用程序的IntentFiletr都可以匹配(忽略优先级策略),则会弹出一个列表让我们选择。如果我们系统安装了多个浏览器,打开一个链接时这时候会弹出一个对话框:

action_browser

ComponentName属性

显示启动Activity的时候用到的该属性,可以查看另一篇文章Activity学习笔记一旦指定了该属性其它属性都是可选的,如果同时指定了显示启动和隐式启动,那么优先采用显示启动。组件名称通过setComponent()、setClass()、setClassName()设置,通过getComponent()获得,将要启动的组件必须包含包名称和全类名称(包名+类名)。

Action属性

Action是指Intent要完成的动作,是一个字符串常量。在Intent类中定义了大量的Action常量属性,例如,ACTION_CALL(打电话)、ACTION_SENDtO(发送短信)等。我们可以使用setAction()来设置Action属性,使用getAction()来获得Intent的Action属性。

下面是系统Action使用的demo:

自定义Action代码:

首先在清单文件AndroidManifest.xml配置:

启动的时候:

Category属性

Intent中的Category属性是一个执行Action的附加信息。例如CATEGORY_LAUNCHER意味着加载程序时,Activity出现在最上面。还有CATEGORY_HOME则表示回到Home界面。跟前面的两个属性不同,Category可以添加多个,所以方法才是addCategory()。

  • 默认的Category是必须的
  • 在启动另一个组件时可以不指定Category,但是如果指定则必须是注册Activity是已经定义好的,否则抛出异常。可以指定任意单个自定义的Category;
  • 自定义的Category,只要这里有一个匹配就可以通过,如果在启动另一个组件加入所有自定义的Category这时候必须都匹配成功才可以通过,否则抛出异常。

Data和Type

type的配置在清单文件AndroidManifest.xml是配置在data中的,作为data属性之一:

data-type

type作为data的一个属性mimeType,在代码中也很容易设置,但是有一点需要注意,如果设置了Data就会忽略Type,同样如果设置了Type也会忽略Data,所以Android系统给我们提供了另外一个属性setDataAndType(Uri data, String type)。

data和type的设置与Category的有很大的不同,在Category中我们设置后可以在代码上不用添加也可以测试通过,就是说Category可以不添加,要么必须添加已经注册过的。data和type的设置就不是如此了,一旦我们在清单文件注册过后,在Intent启动组件的时候就必须设置data或type,否则一定不会通过。

  • 如果Intent没有指定data和data type,则只有没有定义data和datetype的filter才能通过测试;
  • 如果intent定义了data没有定义datatype,则只有定义了相同data且没有定义datetype的filter才能通过测试;
  • 如果intent没有定义data却定义了datatype,则只有未定义data且定义了相同的datatype的filter才能通过测试;
  • 如果intent既定义了data也定义了datatype, 则只有定义了相同的data和datatype的filter才能通过测试。

注:data属性是一个URI, URI中包含scheme,host, post和path, 典型的URI为:scheme://host:port/path,scheme、host、post和path都是可选的。比较2个data时,只比较filter中包含的部分。比如filter的一个data只是指定了scheme部分, 则测试时只是比较data的scheme部分,只要两者的scheme部分相同, 就视为”相同的data”。

Flags标志位

extras属性在上面传值已经有了比较全面的介绍。

这个标志位是一个相当重要的属性,在启动Activity是它决定了Activity的运行模式,在AndroidManifest.xml中的标签的android:launchMode属性设置。

启动模式有4种,分别为standard、singleTop、singleTask、singleInstance;

  1. standard:Activity的默认加载方法,该方法会通过跳转到一个新的activity,同时将该实例压入到栈中(不管该activity是否已经存在在Task栈中,都是采用new操作)。例如: 栈中顺序是A B C D ,此时D通过Intent跳转到A,那么栈中结构就变成 A B C D A ,点击返回按钮的 显示顺序是 D C B A,依次摧毁。
  2. singleTop:singleTop模式下,当前Activity D位于栈顶的时候,如果通过Intent跳转到它本身的Activity (即D),那么不会重新创建一个新的D实例,所以栈中的结构依旧为A B C D,如果跳转到B,那么由于B不处于栈顶,所以会新建一个B实例并压入到栈中,结构就变成了A B C D B。如:浏览器添加书签页面、设置页面、拨号页面
  3. singleTask:singleTask模式下,Task栈中只能有一个对应Activity的实例。例如:现在栈的结构为:A B C D。此时D通过Intent跳转到B,则栈的结构变成了:A B。其中的C和D被栈弹出销毁了,也就是说位于B之上的实例都被销毁了。如:手机中闹铃、桌面、音乐播放页面、浏览器等
  4. singleInstance:singleInstance模式下,会将打开的Activity压入一个新建的任务栈中。例如:Task栈1中结构为:A B C ,C通过Intent跳转到了D(D的模式为singleInstance),那么则会新建一个Task 栈2,栈1中结构依旧为A B C,栈2中结构为D,此时屏幕中显示D,之后D通过Intent跳转到D,栈2中不会压入新的D,所以2个栈中的情况没发生改变。如果D跳转到了C,那么就会根据C对应的launchMode的在栈1中进行对应的操作,C如果为standard,那么D跳转到C,栈1的结构为A B C C ,此时点击返回按钮,还是在C,栈1的结构变为A B C,而不会回到D。如:接听电话页面

注:如果在非Activity中使用startActivity()方法,会抛出异常Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?这时候解决办法也很简单:intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),因为非Activity的Context是没有所属Task的,所以必须新建Task。

Demo截图和代码下载地址

intent-demo

代码下载地址

发表评论