显示Intent和隐式Intent
Android中的Intent是一个非常重要且常用的类,可以用来在一个组件中启动App中的另一个组件或者是启动另一个App的组件,这里所说的组件指的是Activity、Service以及Broadcast。
Intent功能
启动Activity
启动Service
发送广播
Intent类型
显示Intent,Intent中明确包含了要启动的组件的完整类名(包名及类名),那么这个Intent就是explict的,即显式的。适用于在App内使用,因为开发者一定知道自己开发的组件完整类名。
隐式Intent,Intent没有包含要启动的组件的完整类名,那么这个Intent就是implict的,即隐式的。使用情况,隐式的Intent只用在当我们想在自己的App中通过Intent启动另一个App的组件的时候,让另一个App的组件接收并处理该Intent。
需要注意的是,为了确保App的安全性,我们应该总是使用显式Intent去启动Service并且不要为该Service设置任何的Intent Filter。通过隐式的Intent启动Service是有风险的,因为你不确定最终哪个App中的哪个Service会启动起来以响应你的隐式Intent,更悲催的是,由于Service没有UI的在后台运行,所以用户也不知道哪个Service运行了。
Intent组成
MIME TYPE
在学习Intent的组成元素之前,先来了解一下基本知识:MIME TYPE。
什么是MIME TYPE
MIME type的缩写为(Multipurpose Internet Mail Extensions)代表互联网媒体类型(Internet media type),MIME使用一个简单的字符串组成,最初是为了标识邮件Email附件的类型。媒体类型通常是通过 HTTP 协议,由Web服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的,例如:
Content-Type: text/HTML
MIME类型能包含视频、图像、文本、音频、应用程序等数据。
MIME TYPE组成
通常只有一些在互联网上获得广泛应用的格式才会获得一个 MIME Type,如果是某个客户端自己定义的格式,一般只能以 application/x- 开头。例如application/vnd.ms-excel指定了Microsoft Excel文件类型。
每个MIME类型由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类。
常见的MIME Type请参考MIME 参考手册
MIME Type通配符
标准的MIME Type中为同一种资源的不同格式指定了不同的MIME Type。例如图片资源MIME Type有,image/bmp、image/cis-cod、image/gif、image/ief、image/jpeg、image/pipeg...如果需要指定大致的类型,比如指定所有图片资源,就可以使用通配符*,构成image/*。
MIME Type作用
在把输出结果传送到浏览器上的时候,浏览器必须启动适当的应用程序来处理这个输出文档。这可以通过MIME Type来判断启动哪个程序来处理。在HTTP中,MIME类型被定义在Content-Type header中
Android MIME Type
Android MIME Type类型
标准的MIME Type
自定义
属性会在后面的Intent组成元素中介绍。自定义的MIME Type格式也是有要求的,自定义的方式涉及到ContentProvider,所以下次再仔细阅读。
MIME Type参考
Intent组成部分
Android可以根据Intent所携带的信息去查找要启动的组件,Intent还携带了一些数据信息以便要启动的组件根据Intent中的这些数据做相应的处理。
Intent由6部分信息组成:Component Name、Action、Data、Category、Extras、Flags。可以根据作用分为以下三种:
Component Name、Action、Data、Category,决定了Android会启动哪个组件,其中Component Name用于在显式Intent中使用,Action、Data、Category、Extras、Flags用于在隐式Intent中使用。
Extras,里面包含了具体的用于组件实际处理的数据信息。
Flags,其是Intent的元数据,决定了Android对其操作的一些行为。
Component Name
要启动的组件的名称。一般在显示Intent中,指定要启动的组件类文件参数,就是Component Name。如果没有设置component name,那么该Intent就是隐式的,Android系统会根据其他的Intent的信息(例如action、data、category等)做一些比较判断决定最终要启动哪个组件。所以,如果你启动一个你自己App中的组件,你应该通过指定component name通过显式Intent去启动它(因为你知道该组件的完整类名)。
需要注意的是,当启动Service的时候,你应该总是指定Component Name。否则,你不确定最终哪个App的哪个组件被启动了,并且用户也看不到哪个Service启动了。
Component Name参数指定
component name在Intent中对应的field是ComponentName对象,你可以通过要启动的组件的完整类名(包括应用的包名)指定该值,例如com.example.ExampleActivity。你可以通过Intent的setComponent()方法、setClass()方法、setClassName()方法或Intent的构造函数指定component name。
Action
是表示了要执行操作的字符串,比如查看或选择,其对应着Intent Filter中的action标签。
Action定义
Action可以选用预定义的,也可以自定义。Intent类和Android中其他framework级别的一些类也提供了许多已经定义好的具有一定通用意义的action。
自定义是指定独有的action以便于你的App中的Intent的使用或其他App中通过Intent调用你的App中的组件。
如果自定义了action,请务必将App的包名作为该action的前缀,这是一种良好的编程习惯,避免造成混淆。
设定Action
通过调用intent对象的setAction()方法或在Intent的构造函数中指定intent的action。注意Intent只能由一个action。
Data
Intent中的data指的是Uri对象和数据的MIME类型,其对应着Intent Filter中的data标签。
Uri
一个完整的Uri由scheme、host、port、path组成,格式是://:/。Uri就像一个数据链接,组件可以根据此Uri获得最终的数据来源。通常将Uri和action结合使用,比如我们将action设置为ACTION_VIEW,我们应该提供将要被编辑修改的文档的Uri。
Android预定义的schema有很多,content协议、http协议、file协议,还有tel协议表示打电话,geo协议表示地理位置...
MIME Type
Android就会基于Uri和MIME类型将Intent传递给符合条件的组件。例如指定了MIME Type之后,不让Android误将一个含有视频Uri的Intent对象传递给一个只能显示图片的Activity。
如果Uri使用的是content:协议,那么这就说明Uri所提供的数据将来自于本地设备,即数据由ContentProvider提供,这种情况下Android会根据Uri自动推断出MIME类型,此种情况我们无需再自己指定MIME类型。因为此Uri的类型可以通过provider的getType(Uri)方法得到。
设置Data
如果只设置数据的Uri,需要调用Intent对象的setData()方法;如果只设置数据的MIME类型,需要调用Intent对象的setType()方法;如果要同时设置数据的Uri和MIME类型,需要调用Intent对象的setDataAndType()方法。
需要注意的是,如果你想要同时设置数据的Uri和MIME类型,不要先后调用Intent对象的setData()方法和setType()方法,因为setData()方法和setType()是互斥的,即如果调用了setData()方法,会将Intent中已经通过setType()方法设置的MIME类型重置为空。如果调用了setType()方法,会将Intent中已经通过setData()方法设置的Uri重置为空。所以在需要同时设置数据的Uri和MIME类型的时候,一定要调用Intent对象的setDataAndType()方法,而不是分别调用setData()方法和setType()方法。
Category
category包含了关于组件如何处理Intent的一些其他信息。同时可以更加准确的定位到符合条件的组件。可以在Intent类中查找到更多预定义的category。
在调用startActivity()或者startActivityForResult()方法时会自动给参数Intent添加category.DEFAULT。所以要在相应的IntentFilter中添加该category。
Extras
额外的数据信息。Intent中有一个Bundle对象存储着各种键值对,接收该Intent的组件可以从中读取出所需要的信息以便完成相应的工作。有的Intent需要靠Uri携带数据,有的Intent是靠extras携带数据信息。
定义Extra的键值
Intent类里面也指定了很多预定义的EXTRA_*形式的extra。同时也可以声明自己自定义的extra,请确保App的包名作为你的extra的前缀。
设置Extra
通过调用Intent对象的各种重载的putExtra(key, value)方法向Intent中加入各种键值对形式的额外数据。你也可以直接创建一个Bundle对象,向该Bundle对象传入很多键值对,然后通过调用Intent对象的putExtras(Bundle)方法将其一块设置给Intent对象中去。
Flags
flag就是标记的意思,Intent类中定义的flag能够起到作为Intent对象的元数据的作用。这些flag会告知Android系统如何启动Activity(例如,新启动的Activity属于哪个task)以及在该Activity启动后如何对待它(比如)。更多信息可参见Intent的setFlags()方法。
实践
mImageUri = Uri.fromFile(outPutImage);
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
startActivityForResult(intent, TAKE_PHOTO);
imageUri指定了图片存储的Uri,作为extra,让相机知道拍照完成后照片的存储位置信息。
使用隐式Intent最佳方式
当使用隐式Intent来启动一个组件时,如果Android系统没有相应的组件,会抛出异常。所以有必要在启动组件时,先去判断一下Android系统有无至少一个符合Intent条件的组件。判断方式有两种:PackageManager和Intent两个类中的方法。
两种方法会让Android根据该sendIntent找到潜在的适合启动的组件的信息,并以ResolveInfo类的对象的形式返回结果,如果返回null,表示当前系统中没有任何组件可以接收并处理该Intent。如果返回不是null,就表明系统中至少存在一个组件可以接收并处理该Intent。
PackageManager
PackageManager pm = getPackageManager();
//参数flag为什么赋予0,不知道
if(pm.resolveActivity(intent, 0) != null) {
startActivity(intent);
}
参数Flag用于指定寻找方式,文档中有一句话:The most important is MATCH_DEFAULT_ONLY, to limit the resolution to only those activities that support the CATEGORY_DEFAULT.指定MATCH_DEFAULT_ONLY flag用于寻找含有CATEGORY_DEFAULT的category的组件。
平时用这种方法来判断有无符合条件的组件,flag一般设置为0,不知道为什么。我个人的理解可能是,多个flag是通过位或来组成一个flag,这里给flag设置为0,当与原始的flag进行位或运算时,不改变原始的flag,也就是保持了默认。如有误,请指出,纯属瞎YY。
Intent
if(intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
App Chooser
当使用隐式Intent时,Android系统中可能有多个符合该Intent条件的组件。第一次打开时会弹出一个选择器供用户选择,用户可以勾选始终让其中一个作为默认启动组件。这样下次再启动时,就会默认打开选择的组件。但是,如果用户想分享某个文件时,不一定只会用一个应用来分享,所以这种情况下我们应该强制每一次都打开选择器,让用户选择。
intent = Intent.createChooser(intent, "Choose WebKit");
if(intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
这里调用Intent类中静态方法createChooser()来创建一个选择器Intent,每一次都去启动该Intent。
Intent过滤
设定好隐式Intent的各项参数后,需要和所有组件的IntentFilter中各项参数匹配,找到符合条件的组件。
IntentFilter
即Intent过滤器,一个组件可以包含0个或多个Intent Filter。Intent Filter是写在App的manifest文件中的,其通过设置action或uri数据类型等指明了组件能够处理接收的Intent的类型。如果你给你的Activity设置了Intent Filter,那么这就使得其他的App有可能通过隐式Intent启动你的这个Activity。
当Android系统接收到一个隐式Intent要启动一个Activity(或其他组件)时,Android会根据以下三个信息比较Intent的信息与注册的组件的intent-filter的信息,从而为该Intent选择出最匹配的Activity(或其他组件):
intent中的action
intent中的category
intent中的data(包含Uri以及data的MIME类型)
也就是隐式intent对象要分别满足要启动的目标组件中注册的intent-filter中的、、三个标签中的信息。
IntentFilter示例
Action测试
要想让intent对象通过action测试,那么intent-filter中声明的action不能为空且要包含intent对象中的action值(如果intent的action值不为空的话)。
如果intent-filter没有声明任何action,那么所有的intent的对象(即无论intent如何配置)都无法通过intent-filter的action测试。
Category测试
如果intent对象不包含任何category,并且该intent不是用来启动Activity的,那么该intent对象总是能通过所有任意的intent-filter的category测试;
如果intent对象包含category(至少一个),那么只有当intent-filter中声明的category全部包含intent对象中的所有category的时候才通过category测试。
如果允许Activity被隐式的Intent启动,那么我们必须在该Activity的intent-filter中声明值为android.intent.category.DEFAULT的category。
android.intent.category.DEFAULT的category,这是因为当我们把一个隐式的intent传递给startActivity()或startActivityForResult()方法时,Android会自动给该隐式intent添加值为android.intent.category.DEFAULT的category,所以为了能让intent-filter包含intent中全部的category,我们就需要在Activity的intent-filter中添加该category,在使用时需要特别注意。
Data测试
在IntentFilter中设置data属性的uri值时,使用uri的通配符*和#。其中*匹配任意字符串,#匹配任意数字。
参考