Intent----android中的伟大邮差
在android中,intent就像是一个邮差,辛勤高效的在各个组件之间来回穿梭。我们可以通过它启动一个Activity或者Service,或者是发送给广播组件,又或者是与后台的Service进行通信。所谓的Intent,字面意思就是"意图,目的",在android中的定义就是一个动作的抽象描述,类似于接口是抽象的行为协议一样,但这两者在实现上是不同的东西。
即使是好的邮差,如果没有邮递地址,依然无法正确的将货物送到指定的地方。这个完全交给系统来处理,它会帮Intent寻找合适的邮递地址,像是发送给Activity的Intent就会准确的发送给该Activity,发送个各个广播的Intent就会发送给指定的广播,从不会出错。
一.Intent的组成
Intent所包含的信息主要包括两个方面:action和data。
action就是所要执行的动作,像是我们经常在根文件中声明的Activity的动作:ACTION_MAIN,表示应用程序的入口。action通常是一个大写表示的字符串,用来简要的描述动作,当然,我们也可以自定义自己应用的Intent,不过在使用的时候必须指定全称,也就是包名。
正如方法签名包含了参数和返回值一样,action的名字也必须尽量包含了该action的信息,所以在自定义自己的action的时候,名字是非常重要的,必须尽可能的与其他的action区分开来,而且还要将该名字与intent的其他部分紧密的结合在一起,简而言之,不是单独的定义action,而是要定义其他组件能够处理的完整协议。
我们可以通过setAction()来设置action和通过getAction()来获取action。
一般的action像是这样:
public static final String ACTION_DIAL = "android.intent.action.DIAL"
data是我们的动作所要操作的数据,通常是以Uri的形式表示。data并不是单独的出现,它们经常和action成对出现,像是这样:ACTION_VIEW content://contacts/people/1,就是用于展现手机中通讯录标识为1的人的信息,而且还提示了数据的存放地点是在设备上,并且是由content provider所控制。
当一个Intent被发送给适合的组件时,我们除了要知道data的Uri之外,还必须知道数据的类型,也就是MIME type。我们可以显式地指明data的Uri和MIME type,可以通过setData()指明数据的uri,setType()指明数据的MIME type,setDataAndType()指明数据的Uri和MIME type,getData()用于获取Uri,而getType()用于获取type。
除了上面的主要属性外,Intent还可以包含其他信息:category,type,component和extras。
category给出了action执行的其他信息,像是CATEGORY_LAUNCHER表示目标Activity是应用程序中最优先被执行的Activity,也就是所谓的top-level Activity。还有CATEGORY_HOME,表示的就是手机开机启动后或者按下HOME键后显示的Activity,CATEGORY_BROWSABLE表示的就是能通过在网页浏览器中点击链接而激活的Activity,而CATEGORY_PREFERENCE表示Activity是用于设置的Activity。
type是显式的指明data的MIME type,一旦指明了type,就只能使用该类型的data。
component是显式的指明了使用Intent的组件,这时其他属性的设置就是可选的,因为它们的设置都是为了方便寻找合适的组件。
extras正如字面意思,就是额外的信息,它们使用Bundle类型进行数据传递,也就是键值对,像是time-zone代表的就是新的时间区域。通常它代表的是Intent的扩展信息,像是我们的action如果能够发送邮件,那么我们可以将邮件的标题和内容放在extras里面。
像是这样:
Bundle bundle = new Bundle(); intent.putExtras(bundle);
flags的作用像是告诉系统应该如何启动Activity,或者在启动后应该如何处理该Activity等。
二.Intent的使用
Intent可以分为两类:显式的Intent和隐式的Intent。
显式的Intent指定了使用Intent的组件,所以它并不需要其他信息。通常它使用在应用程序的内部,像是启动应用程序的其他Activity或者内部定义的Service。
隐式的Intent并没有指定组件,所以它需要更加详细的信息以确保系统能够发送给正确的组件。但是这些还是不够的,组件本身还是要提供信息来确保它接收到正确的Intent,这就是intent-filter的作用。我们经常在根文件中注册一个组件的时候,指明它的intent-filter,像是在程序中使用广播组件的时候,我们可以显式的指明intent-filter,像是这样:registerReceiver(BroadcastReceiver, IntentFilter)。
我们在声明intent-filter的时候,通常只需要指定三个属性:action,data和category,但是一般情况下我们有时候也不会完整的设定这三个属性,像是只设定action和category,那么这时的filter就只会匹配没有data的intent。在intent-filter中的data还进一步划分成几个属性:type,scheme,authority和path。type就是我们熟悉的MIME type,我们简单的讲解一下其他我们不熟悉的属性。
所谓的sheme就是我们上面讲的content:Uri等信息,值得注意的是,如果我们只是声明了scheme而没有声明type的话,那么filter就只会匹配没有type的Intent,而像是content:Uri是不会被匹配的,因为content provider在存储的时候是有保存MIME type的信息,所以如果没有提供type的话,是无法从里面提取出data。如果我们只提供type而没有提供scheme的话,那就表示匹配没有Uri或者包含content:Uri和file:Uri的Intent。如果scheme和type都没有,那么匹配的将会是没有data或者type的Intent。值得注意的是,当我们需要指定authority的时候,authority的scheme列表和我们的intent-filter的scheme列表必须相同,而path则需要包括authority和scheme列表。
我们来一个例子:
content://com.example.project:200/folder/subfolder/etc
其中,content就是scheme,com.example.project就是host,而200是port,path就是folder/subfolder/etc,host和port加起来就是URI authority,但注意,如果host没有指定,那么port也是没有必要的。
以上介绍的属性都是可选的,但这并不意味着它们是独立的:如果想要声明一个authority,那么scheme就是必要的。如果想要声明一个path,scheme和authority都是必要的。
我们再来看一个例子:
<intent-filter> <data android:mimeType="audio/mpeg"/> </intent-filter>
这里我们可以使用audio/*,这就表示audio的所有子类型都可以匹配。
像是这样:
<data android:scheme="http" android:type="video/*"/>
表示我们的数据类型是video,然后可以通过网络来获取。
在实际的应用中,我们使用隐式的Intent比较多,之所以使用隐式的Intent,是因为我们想要让系统来决定哪个组件是最适合该Intent的,通常系统会比人更加清楚什么才是最适合的,只要我们给出的信息足够详细。
最好的情况就是我们在注册一个组件的时候,如果希望它接收Intent,那么就必须在根文件中声明它的intent-filter,这样无论是显式的还是隐式的Intent它都能接收到,如果没有,那么它只会接收显式的Intent。
IntentFilter并不像它的名字一样,具有过滤保护的作用,因为它无法阻止显式的Intent发送给组件,它只能阻止隐式的Intent发送给该组件。
一个intent-filter可以包含多个action:
<intent-filter> <action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.EDIT"/> <action android:name="android.intent.action.PICK"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="vnd.android.cursor.dir/vnd.google.note"/> </intent-filter>
这个filter声明了很多action,它允许用户view或者edit目录,也允许从目录中pick一个note出来。
值得注意的是,filter声明了DEFAULT这个category。之所以声明为DEFAULT,是因为Context.startActivity()和Activity.startActivityForResult()这两个方法都默认Intent包含DEFAULT这个category,除了两个例外情况:显式的声明目标activity的名字和包含action-MAIN和category-LAUNCHER的Intent。
这里还有一个特别的东西:data的mimeType。我们看到,这里的的mimeType声明为vnd.android.cursor.dir/vnd.google.note,说明我们可以从Content Provider中获取Cursor并进一步获取Note Pad的数据。
最后发送给Activity的Intent大概像是这样子:
{action:android.intent.action.VIEW data:content://com.google.provider.NotePad/notes}
{action:android.intent.action.PICK data:content://com.google.provider.NotePad/notes}
{action:android.intent.action.EDIT data:content://com.google.provider.NotePad/notes}
当然,这里的Intent并不是完整的,还得考虑一下Activity的情况。如果该Activity用于展示所有的数据,Intent就是上面这样子, 但如果只是单独作用于一条数据的Activity,就算intent-filter是一样的,实际上的Intent应该像是这样子:
{action:android.intent.action.VIEW data:content://com.google.provider.NotePad/notes/ID}
再来看一个例子:
<intent-filter android:label="@string/resolve_title"> <action android:name="com.android.notepad.action.EDIT_TITLE"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.ALTERNATIVE"/> <category android:name="android.intent.category.SELECTED_ALTERNATIVE"/> <data android:mimeType="vnd.android.cursor.item/vnd.google.note"/> </intent-filter>
这个Filter声明的action是修改title。为了能够支持DEFAULT这个category,我们还必须支持其他两个标准的categories:ALTERNATIVE和SELECTED_ALTERNATIVE。这个Intent大概就是这样子:
{action:com.android.notepad.action.EDIT_TITLE data:content://com.google.provider.NotePad/notes/ID}
android系统本身就定义了大量的标准action和category,如果有需要的话,可以自己查阅官方文档。
接下来我们就针对常见的使用场景来分析如何使用Intent。
1.Activity的跳转:
Intent intent = new Intent(Activity1.this, Activity2.class); startActivity(intent);
关于Activity的跳转,还有一个知识点需要补充。我们知道,在android中有一个Task(栈),专门用于存放Activity,它遵循的是"先进后出"的原则,但如果我们不想要遵循这个原则,想要取出指定的Activity,我们就可以利用一个东西:Flag,也就是上面提到的Flag:
Intent intent = new Intent(Activity1.this, Activity2.class); //如果Activity在Task中存在,并且是在最顶端,不会启动新的Activity intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); //如果Activity在Task中存在,将Activity上面的所有Activity结束掉 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //默认的跳转类型。将Activity放到一个新的Task中 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //如果Activity已经运行到Task,再次跳转不会再运行这个Activity
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
2.向另一个Activity传递数据:
Intent intent = new Intent(Activity1.this, Activity2.class); Bundle bundle = new Bundle(); bundle.putString("name", "Jack"); intent.putExtras(bundle); startActivity(intent);
至于接收数据:
Bundle bundle = new Bundle(); String name = bundle.getString("name");
3.向上一个Activity返回结果,这种情况针对的是startActivityForResult(intent, requestCode)启动的Activity:
Intent intent = getIntent(); Bundle bundle = new Bundle(); bundle.putString("name", "Jack"); intent.putExtras(bundle); setResult(RESULT_OK, intent);
4.回调上一个Activity的结果处理函数:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ super.onActivityResult(requestCode, resultCode, data); if(requestCode == REQUEST_CODE){ if(resultCode == RESULT_OK){ String temp = null; Bundle bundle = data.getExtras(); .... } } }
接下来就是一些调用官方应用程序的使用场景:
1.显示网页:
Uri uri = Uri.parse("http://google.com"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent);
2.显示地图:
Uri uri = Uri.parse("geo:38.899533,-77.036476"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent);
3.路径规划:
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent);
4.打电话:
(1)叫出拨号程序:
Uri uri = Uri.parse("tel:0800000123"); Intent intent = new Intent(Intent.ACTION_DIAL, uri);
(2)直接打电话:
Uri uri = Uri.parse("tel:0800000123"); Intent intent = new Intent(Intent.ACTION_CALL, uri); startActivity(intent);
这些都要在根文件中声明权限:<user-permission id="android.permission.CALL_PHONE"/>
5.发送SMS(短信)和MMS(彩信):
//调用短信程序 Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra("content", "你好"); it.setType("vnd.android-dir/mms-sms"); startActivity(intent); //发送短信 Uri uri = Uri.parse("smsto://0800000123"); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); intent.putExtra("content", "你好"); startActivity(intent); //发送彩信 Uri uri = Uri.parse("content://media/external/images/media/23"); Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra("content", "。。。"); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.setType("image/png"); startActivity(intent);
6.发送Email:
Uri uri = Uri.parse("mailto:xxx@abc.com"); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); startActivity(intent); Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_EMAIL, "me@abc.com"); intent.putExtra(Intent.EXTRA_TEXT, "The email body text"); intent.setType("text/plain"); startActivity(Intent.createChooser(intent, "Choose Email Client")); Intent intent=new Intent(Intent.ACTION_SEND); String[] tos={"me@abc.com"}; String[] ccs={"you@abc.com"}; intent.putExtra(Intent.EXTRA_EMAIL, tos); intent.putExtra(Intent.EXTRA_CC, ccs); intent.putExtra(Intent.EXTRA_TEXT, "The email body text"); intent.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); intent.setType("message/rfc822"); startActivity(Intent.createChooser(intent, "Choose Email Client")); //传送附件 Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); intent.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3"); sendIntent.setType("audio/mp3"); startActivity(Intent.createChooser(intent, "Choose Email Client"));
7.播放多媒体:
Uri uri = Uri.parse("file:///sdcard/song.mp3"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.setType("audio/mp3"); startActivity(intent); Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent);
8.Market相关:
//寻找某个应用 Uri uri = Uri.parse("market://search?q=pname:pkg_name"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); //显示某个应用的相关信息 Uri uri = Uri.parse("market://details?id=app_id"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent);
9.Uninstall应用程序:
Uri uri = Uri.fromParts("package", strPackageName, null); Intent intent = new Intent(Intent.ACTION_DELETE, uri); startActivity(intent);