Activity Intent详解

时间秒杀一切 提交于 2019-11-27 07:44:47

1、Intent简介

Intent代表了Android应用的启动“意图”,Android应用将会根据Intent来启动指定组件,至于到底启动哪个组件,则取决于Intent的各个属性。下面就来介绍一下Intent的各种发展,以及Android如何根据不同属性来启动相应的组件。Intent是由Component、Action、Data、Category、Extra及Flag六部分组成的,接下来将分别对其进行详细介绍。

(1)Component name

组件名称实际上就是一个ComponentName对象,用于标识唯一的应用程序组件,即指明了期望的Intent组件,这种对象的名称是由目标组件的类名与目标组件的包名组合而成的。需要注意的是Component name是一个可选项,如果被设置,那么Intent对象就显式指定了要转向的组件,如果没有被设置,则Intent对象需要根据其他信息进行筛选查找。

组件名称通过 setComponent(),setClass(),setClassName()设置,通过getComponent()获取。

intent.setClassName("com.example.testb", "com.example.testb.MainActivity");
ComponentName com=new ComponentName("com.example.testb","com.example.testb.MainActivity");  
intent.setComponent(com);
intent.setClass(this, MainActivity.class);
ComponentName com = intent.getComponent();  
String pkgName = com.getPackageName();  
String className = com.getClassName();


(2)Action

Action实际上就是一个描述了Intent所触发动作名称的字符串,在Intent类中,已经定义好多字符串常量来表示不同的Action,当然,开发人员也可以自定义Action,其定义规则同样非常简单。动作名必须是独一无二的字符串,所以,一个好的习惯是使用基于Java包的命名方式的命名系统。

系统定义的Activity Action常量:

ACTION_CALL,拨出Data里封装的电话号码,直接打电话出去。

ACTION_DIAL,跳到拨号界面,未打出去。

ACTION_ANSWER,接听来电。

ACTION_DELETE,从容器中删除给定的数据。

ACTION_PICK,从数据中选择一个项目 (item),将被选中的项目返回。

ACTION_DEFAULT,和 ACTION_VIEW 相同,是在数据上执行的标准动作。

ACTION_ALL_APPS,列举所有可用的应用。

ACTION_GET_CONTENT,让用户选择数据并返回。

ACTION_EDIT,为制定的数据显示可编辑界面。

ACTION_BUG_REPORT显示 activity 报告错误。

ACTION_SEND由用户指直接发送方式进行数据发送操作

ACTION_SENDTO系统根据不同的Data类型,通过已注册的对应Application进行数据发送操作

ACTION_VIEW向用户显示数据。

ACTION_PICK_ACTIVITY,选择一个 activity,返回被选择的 activity 的类(名)。

ACTION_RUN,运行数据(指定的应用),无论它(应用)是什么。

ACTION_INSERT,在容器中插入一个空项 (item)

ACTION_WEB_SEARCH,执行 web 搜索。

ACTION_SYNC,执行数据同步。

ACTION_MAIN,声明程序的入口,该Action并不会接收任何数据,同时结束后也不会返回任何数据。

ACTION_CHOOSER, 选择器

系统定义的BroadcastIntent Action常量

Action很大程度上决定了Intent的另外部分的结构, 就像一个方法名决定了它接受的参数和返回值一样. 因此, 最好给Action一个最能反映其作用的名字.一个Intent对象中的Action是使用getAction()和setAction()来读写的。

//打电话
Intent intent = new Intent();
//记得加权限<uses-permission android:name="android.permission.CALL_PHONE"/>
//Intent.ACTION_DIAL 这里是跳到拨号界面
//Intent.ACTION_CALL 直接打电话出去  
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:0-123-456-789"));
startActivity(intent);
// 选择一张图片并剪切图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true"); // 开启剪切
intent.putExtra("aspectX", 1); // 剪切的宽高比为1:2
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", 20); // 保存图片的宽和高
intent.putExtra("outputY", 40); 
intent.putExtra("output", Uri.fromFile(new File("/mnt/sdcard/temp"))); // 保存路径
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);
//打开相册
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// 如果要限制上传到服务器的图片类型时可以直接写如:image/jpeg 、 image/png等的类型
intent.setType("image/*");
startActivityForResult(intent, 0);

其它:

MediaStore.ACTION_IMAGE_CAPTURE,打开拍照程序

// 打开拍照程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 0);
// 取出照片数据
Bundle extras = intent.getExtras(); 
Bitmap bitmap = (Bitmap) extras.get("data");

com.android.camera.action.CROP,裁剪

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");//uri 图片地址
// 设置裁剪
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
intent.putExtra("outputX", 320);
intent.putExtra("outputY", 320);
intent.putExtra("return-data", true);
startActivityForResult(intent, CROP_REQUEST_CODE);

android.provider.Settings.ACTION_WIRELESS_SETTINGS ,进入无线网络设置界面

Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);  
startActivityForResult(intent, 0);

Media.RECORD_SOUND_ACTION,打开录音机

Intent intent = new Intent(Media.RECORD_SOUND_ACTION);
startActivity(intent);

(3)Data

Data主要是对Intent消息中数据的封装,主要描述Intent的动作所操作到的数据的URI及类型。不同类型的Action会有不同的Data封装,例如打电话的Intent会封装tel://格式的电话URI,而ACTION_VIEW的Intent中Data则会封装http:格式的URI。正确的Data封装对Intent匹配请求同样非常重要。

当匹配intent和能够处理intent所带的数据的组件时,知道数据类型(MIME类型)是很重要的。比如,一个展示图像的组件不应该被叫去播放一个音频。

很多情况下,从URI可以看出数据类型,比如content: URIs,表示数据是在设备上,但是是由content provider控制。

Intent.setData(Uri); //一个Uri,Scheme包含在其中
Intent.setType(String); //指定MimeType,比如'image/jpeg', 'audio/mpeg'等
Intent.setDataAndType(Uri, String); //上面二个方法的简便调用方式,一起搞进去

geo:latitude,longitude(?q=street+address),打开地图应用程序并显示指定的经纬度(地址)

http://,打开浏览器程序并显示指定的URL

tel:,打开电话应用程序并拨打指定的电话号码

voicemail:,打开电话应用程序并拨下指定语音邮箱的电话号码

mailto:邮件数据格式,后跟邮件收件人地址

smsto:短息数据格式,后跟短信接收号码

content://内容数据格式,后跟需要读取的内容。 

file://文件数据格式,后跟文件路径

market://search?q=搜索条件在Google Market里搜索应用。

market://details?id=app_id|packagename打开Google Market直接进入该程序的详细页面。

数据类型也可以显式指定,比如setData()方法指定数据为URI,setType() 指定为MIME type,setDataAndType() 指定它既为URI又为MIME type。读取的时候URI用getData(),MIME type用getType()

//显示网页
Uri uri = Uri.parse("http://www.oschina.net");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//显示地图(北纬39.9,东经116.3)
Uri uri = Uri.parse("geo:39.9,116.3");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//发送短信 记得加上<uses-permission android:name="android.permission.SEND_SMS"/>
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("smsto:0800000123"));
intent.putExtra("sms_body", "The SMS text");
startActivity(intent);
//传送彩信(相当于发送带附件的短信)
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra("sms_body", "some text");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://media/external/images/media/23"));
intent.setType("image/png");
startActivity(intent);
//播放多媒体
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, "song.mp3");  
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//打开Google Market寻找某个应用
Uri uri = Uri.parse("market://search?q=开源中国");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//显示某个应用的相关信息 
Uri uri = Uri.parse("market://details?id=com.oschina.test");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//卸载应用程序
Uri uri = Uri.fromParts("package", strPackageName, null);
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
startActivity(intent);
//安装应用程序
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/solitaire.apk");
intent.setDataAndType(uri,"application/vnd.android.package-archive");
startActivity(intent);
//或者
Uri uri = Uri.fromParts("package", strPackageName, null);
Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED, uri);
//打开联系人列表
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("vnd.android.cursor.item/phone");
startActivityForResult(intent, 0);
//或者 content://contacts/people(/+联系人ID)
Uri uri = Uri.parse("content://contacts/people");
Intent intent = new Intent(Intent.ACTION_PICK, uri);
startActivityForResult(intent, 0);
//打开另一程序
Intent intent = new Intent(Intent.ACTION_MAIN);
ComponentName cn = new ComponentName("com.example.testb","com.example.testb.MainActivity");
intent.setComponent(cn);
startActivityForResult(intent, 0);

常见的MIME类型:
超文本标记语言文本 .html,.html text/html 
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
GIF图形 .gif image/gif 
JPEG图形 .ipeg,.jpg image/jpeg 
au声音文件 .au audio/basic 
MIDI音乐文件 .mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg 
AVI文件 .avi video/x-msvideo 
GZIP文件 .gz application/x-gzip 
TAR文件 .tar application/x-tar 

(4)Category

Category是对目标组件类别信息的描述。同样作为一个字符串对象,一个Intent中可以包含多个Category。与Category相关的方法有三个,addCategory添加一个Category,removeCategory删除一个Category,而getCategories得到一个Category。Android系统同样定义了一组静态字符常量来表示Intent的不同类别,下面列出一些常见的Category常量。

CATEGORY_ALTERNATIVE:你将在这章的后面所看到的,一个Intent Filter的用途是使用动作来帮忙填入上下文菜单。ALTERNATIVE种类指定,在某种数据类型的项目上可以替代默认执行的动作。例如,一个联系人的默认动作时浏览它,替代的可能是去编辑或删除它。

CATEGORY_APP_BROWSER和ACTION_MAIN一起使用,用来启动浏览器应用程序。

CATEGORY_APP_CALCULATOR和ACTION_MAIN一起使用,用来启动计算器应用程序。

CATEGORY_APP_CALENDAR和ACTION_MAIN一起使用,用来启动日历应用程序。

CATEGORY_APP_CONTACTS和ACTION_MAIN一起使用,用来启动联系人应用程序。

CATEGORY_APP_EMAIL和ACTION_MAIN一起使用,用来启动邮件应用程序。

CATEGORY_APP_GALLERY和ACTION_MAIN一起使用,用来启动图库应用程序。

CATEGORY_APP_MAPS和ACTION_MAIN一起使用,用来启动地图应用程序。

CATEGORY_APP_MARKET这个activity允许用户浏览和下载新的应用程序。

CATEGORY_APP_MESSAGING和ACTION_MAIN一起使用,用来启动短信应用程序。

CATEGORY_APP_MUSIC和ACTION_MAIN一起使用,用来启动音乐应用程序

CATEGORY_SELECTED_ALTERNATIVE:设置这个activity是否可以被认为是用户当前选择的数据的一个可选择的action,与ALTERNATIVE类似,但ALTERNATIVE总是使用下面所述的Intent解析来指向单一的动作。SELECTED_ALTERNATIVE在需要一个可能性列表时使用。

CATEGORY_BROWSABLE设置该组件可以使用浏览器启动

CATEGORY_DEFAULTAndroid系统中默认的执行方式,按照普通Activity的执行方式执行

CATEGORY_GADGET设置该组件可以内嵌到另外的Activity

CATEGORY_HOME:HOME Activity是设备启动(登陆屏幕)时显示的第一个Activity。通过指定Intent Filter为HOME种类而不指定动作的话,你正在将其设为本地home画面的替代。

CATEGORY_LAUNCHER:使用这个种类来让一个Activity作为应用程序的启动项。

CATEGORY_MONKEY这个activity可能被monkey或者其他的自动测试工具执行。

CATEGORY_OPENABLE 用来指示一个GET_CONTENT意图只希望ContentResolver.openInputStream能够打开URI。

CATEGORY_TAB:表明目标Activity是TabActivity的一个标签下的Activity。

CATEGORY_PREFERNCE设置该组件为Preference

CATEGORY_EMBED:能够在上级(父)activity 中运行。

CATEGORY_DEVELOPMENT_PREFERENCE:说明 activity 是一个设置面板 (development preference panel)。

CATEGORY_SAMPLE_CODE 作为一个简单的代码示例使用(一般情况下不使用)。

CATEGORY_DESK_DOCK指定手机被插入桌面底座(硬件)时运行该Activity。

CATEGORY_CAR_DOCK指定手机被插入汽车底座(硬件)时运行该Activity。

CATEGORY_CAR_MODE设置该Activity可在车载环境下使用。

CATEGORY_INFO用于提供包信息。

CATEGORY_TEST该Activity是一个测试。

CATEGORY_UNIT_TEST联合测试使用。

一个Intent最多只能包含一个Action属性,但是一个Intent中可以包含多个Category属性。

//唤起MAIN和LAUNCHER的应用
Intent intent = new Intent(Intent.ACTION_MAIN,null); 
intent.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(intent);

你会发现唤起的应用里没有自己的应用,因为通过category筛选属于implicit intent的调用方式,不属于指定软件包名及类名的explicit intent的精确调用方式,对于implicit intent调用需要进行声明<category android:name="android.intent.category.DEFAULT"/>,如下:

<activity android:name=".MainActivity" android:label="@string/app_name" >
    <intent-filter> 
        <action android:name="android.intent.action.MAIN" /> 
        <category android:name="android.intent.category.LAUNCHER" /> 
        <category android:name="android.intent.category.DEFAULT"/> 
    </intent-filter> 
</activity>


(5)Extra

Extra中封装了一些额外的附加信息,这些信息是以键值对的形式存在的。Intent可以通过putExtras()与getExtras()方法来存储和获取Extra。在Android系统的Intent类中,同样对一些常用的Extra键值进行了定义,如下所示。

EXTRA_BCC:存放邮件密送人地址的字符串数组。 

EXTRA_CC:存放邮件抄送人地址的字符串数组。

EXTRA_EMAIL:存放邮件地址的字符串数组。 

EXTRA_SUBJECT:存放邮件主题字符串。 

EXTRA_TEXT:存放邮件内容。 

EXTRA_KEY_EVENT:以KeyEvent对象方式存放触发Intent的按键。  

EXTRA_PHONE_NUMBER:存放调用ACTION_CALL时的电话号码。

EXTRA_SHORTCUT_ICON使用ACTION_CREATE_SHORTCUT在HomeActivity创建快捷方式时,对快捷方式的描述信息。(其中ICON和ICON_RESOURCE描述的是快捷方式的图标,类型分别为Bitmap和ShortcutIconResource。INTENT描述的是快捷方式相对应的Intent对象。NAME描述的是快捷方式的名字。)   

EXTRA_SHORTCUT_ICON_RESOURCE  EXTRA_SHORTCUT_INTENT  EXTRA_SHORTCUT_NAME EXTRA_SUBJECT  描述信息主题的键 

EXTRA_TITLE  使用ACTION_CHOOSER动作时,描述对话框标题的键

EXTRA_UID  使用ACTION_UID_REMOVED动作时,描述删除的用户id的键

// 给someone@domain.com发邮件发送内容为“Hello”的邮件
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, "someone@domain.com");
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("text/plain");
startActivity(intent);

// 给多人发邮件
Intent intent=new Intent(Intent.ACTION_SEND);
String[] tos = {"1@abc.com", "2@abc.com"}; // 收件人
String[] ccs = {"3@abc.com", "4@abc.com"}; // 抄送
String[] bccs = {"5@abc.com", "6@abc.com"}; // 密送
intent.putExtra(Intent.EXTRA_EMAIL, tos);
intent.putExtra(Intent.EXTRA_CC, ccs);
intent.putExtra(Intent.EXTRA_BCC, bccs);
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("message/rfc822");
startActivity(intent);
//调用系统编辑添加联系人
Intent intent = newIntent(Intent.ACTION_INSERT_OR_EDIT);  
intent.setType(People.CONTENT_ITEM_TYPE);  
intent.putExtra(Contacts.Intents.Insert.NAME, "My Name");  
intent.putExtra(Contacts.Intents.Insert.PHONE, "+1234567890");  
intent.putExtra(Contacts.Intents.Insert.PHONE_TYPE,Contacts.PhonesColumns.TYPE_MOBILE);  
intent.putExtra(Contacts.Intents.Insert.EMAIL, "com@com.com");  
intent.putExtra(Contacts.Intents.Insert.EMAIL_TYPE,Contacts.ContactMethodsColumns.TYPE_WORK);
startActivity(intent);
//自定义一个chooser,不使用系统的chooser 该chooser可以有自己的标题(Title) 
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CHOOSER); 
intent.putExtra(Intent.EXTRA_TITLE, "my chooser");
intent.putExtra(Intent.EXTRA_INTENT,new Intent(Intent.ACTION_GET_CONTENT).setType("*/*").addCategory(Intent.CATEGORY_OPENABLE));
startActivityForResult(intent, 0);
//启动搜索,在以下示例代码中,"ANDROID"为要搜索的字符串,当执行这段代码后, 会在系统的Chooser中显示可以用于搜索的程序列表
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEARCH);     //启动搜索
intent.putExtra(SearchManager.QUERY, "ANDROID");
startActivity(intent);
//启动WEB搜索,在以下示例代码中,"ANDROID"为要搜索的字符串,当执行这段代码后, 会在系统的Chooser中显示可以用于搜索的程序列表,一般情况下系统中安装的浏览器都会显示出来
Intent intent = new Intent();
intent.setAction(Intent.ACTION_WEB_SEARCH);     //启动搜索
intent.putExtra(SearchManager.QUERY, "ANDROID");
startActivity(intent);

(6)Flag

首先简单介绍下Task和Activity的关系:

Task就像一个容器,而Activity就相当与填充这个容器的东西,第一个东西(Activity)则会处于最下面,最后添加的东西(Activity)则会在最上面。从Task中取出东西(Activity)是从最顶端取出,也就是说最先取出的是最后添加的东西(Activity),以此类推,最后取出的是第一次添加的Activity,而Activity在Task中的顺序是可以控制的,在Activity跳转时用到Intent Flag可以设置新建activity的创建方式。

(1) 前提: Activity A和Activity B在同一个应用中. 

     操作: Activity A启动开僻Task堆栈(堆栈状态: A), 在Activity A中启动Activity B(堆栈状态: AB), 按下BACK返回键(堆栈状态: A). 

(2) 前提: Activity A和Activity B在同一个应用中, 应用名称为"TaskOne应用".

     操作: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity B(TaskA堆栈状态: AB), 长按Home键, 返回Launcher, 启动其它应用(如:电子书),开僻一个新Task堆栈, 命名: TaskB, 长按Home健, 返回Launcher, 单击"TaskOne应用"图标, 此时TaskA堆栈返回前台,Activity B为栈顶应用, 供用户使用. 

(3) 前提: Activity A在名称为"TaskOne应用"的应用中, Activity C在名称为"TaskTwo应用"的应用中.

     操作: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C(TaskA堆栈状态: AC),长按Home键, 返回Launcher, 启动"TaskTwo应用"即Activity C,开僻新的Task堆栈, 命名为TaskB, 按BACK键返回Launcher, 单击"TaskOne应用"图标, 此时TaskA堆栈返回前台,Activity C为栈顶应用, 供用户使用. 

①FLAG_ACTIVITY_NEW_TASK: 设置此状态,记住以下原则,首先会查找是否存在和被启动的Activity具有相同的亲和性的任务栈(即taskAffinity,注意同一个应用程序中的activity的亲和性一样,所以下面的a情况会在同一个栈中,前面这句话有点拗口,请多读几遍),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的activity顺序不变,如果没有,则新建一个栈来存放被启动的activity。

a). 前提: Activity A和Activity B在同一个应用中。

操作: Activity A启动开僻Task堆栈(堆栈状态: A), 在Activity A中启动Activity B, 启动Activity B的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK, Activity B被压入Activity A所在堆栈(堆栈状态: AB)。

   原因: 默认情况下同一个应用中的所有Activity拥有相同的关系(taskAffinity).

b). 前提: Activity A在名称为"TaskOne应用"的应用中, Activity C和Activity D在名称为"TaskTwo应用"的应用中。

操作1: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C, 启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,Android系统会为Activity C开僻一个新的Task, 命名为TaskB(TaskB堆栈状态: C), 长按Home键, 选择TaskA,Activity A回到前台, 再次启动Activity C(两种情况1.从桌面启动;2.从Activity A启动,两种情况一样), 这时TaskB回到前台, Activity C显示, 供用户使用, 即:包含FLAG_ACTIVITY_NEW_TASK的Intent启动Activity的Task正在运行, 则不会为该Activity创建新的Task,而是将原有的Task返回到前台显示。

操作2: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C,启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,Android系统会为Activity C开僻一个新的Task, 命名为TaskB(TaskB堆栈状态: C),  在Activity C中启动Activity D(TaskB的状态: CD) 长按Home键, 选择TaskA, Activity A回到前台, 再次启动Activity C(从桌面或者ActivityA启动,也是一样的),这时TaskB回到前台, Activity D显示,供用户使用.说明了在此种情况下设置FLAG_ACTIVITY_NEW_TASK后,会先查找是不是有Activity C存在的栈,根据亲和性(taskAffinity),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的顺序不变。

②FLAG_ACTIVITY_CLEAR_TOP:

前提: Activity A, Activity B, Activity C和Activity D在同一个应用中.

操作: Activity A启动开僻Task堆栈(堆栈状态: A), 在Activity A中启动Activity B(堆栈状态: AB), 在Activity B中启动Activity C(堆栈状态: ABC), 在Activity C中启动Activity D(堆栈状态: ABCD), 在Activity D中启动Activity B,启动Activity B的Intent的Flag设置为FLAG_ACTIVITY_CLEAR_TOP, (堆栈状态: AB)。

③FLAG_ACTIVITY_BROUGHT_TO_FRONT:

前提: Activity A在名称为"TaskOne应用"的应用中, Activity C和Activity D在名称为"TaskTwo应用"的应用中.

操作: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C,启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,Android系统会为Activity C开僻一个新的Task, 命名为TaskB(TaskB堆栈状态: C), 在Activity C中启动Activity D(TaskB的堆栈状态: CD), 长按Home键, 选择TaskA, Activity A回到前台, 在Activity A中再次启动Activity C,在启动Activity C的Intent中设置Flag为FLAG_ACTIVITY_BROUGHT_TO_FRONT, TaskB回到前台,Activity C显示, (TaskB的堆栈状态: C)。

④FLAG_ACTIVITY_MULTIPLE_TASK:

与FLAG_ACTIVITY_NEW_TASK结合使用, 首先在Intent中设置FLAG_ACTIVITY_NEW_TASK, 打开Activity,则启动一个新Task, 接着在Intent中设置FLAG_ACTIVITY_MULTIPLE_TASK, 再次打开同一个Activity,则还会新启动一个Task。

⑤FLAG_ACTIVITY_SINGLE_TOP:

当前Task堆栈中存在ABCD四个Activity, A是栈顶Activity, D为栈底Activity, 存在打开A的Intent中设置了FLAG_ACTIVITY_SINGLE_TOP标志, 则会使用栈顶A, 而不会从新New A.

⑥FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:

一般与FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET结合使用,如果设置该属性,这个activity将在一个新的task中启动或者或者被带到一个已经存在的task的顶部,这时这个activity将会作为这个task的首个页面加载。将会导致与这个应用具有相同亲和力的task处于一个合适的状态(移动activity到这个task或者从中移出),或者简单的重置这个task到它的初始状态FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:在当前的Task堆栈中设置一个还原点,当带有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED的Intent请求启动这个堆栈时(典型的例子是用户从桌面再次启动这个应用),还原点之上包括这个应用将会被清除。应用场景:在email程序中预览图片时,会启动图片观览的actvity,当用户离开email处理其他事情,然后下次再次从home进入email时,我们呈现给用户的应该是上次email的会话,而不是图片观览,这样才不会给用户造成困惑。

例: 存在Activity A, Activity B, Activity C, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity B(TaskA堆栈状态: AB), 接着Activity B启动Activity C(TaskA堆栈状态: ABC),启动Activity C的Intent中设置FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET标题, 这样TaskA中有一个还原点,当有包含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED的Intent请求TaskA堆栈时(比如请求Activity A),系统就会将还原点以上的Activity清除, TaskA堆栈中只剩下了AB。

例子:

Intent intent = new Intent();
intent.setClassName("com.example.testb", "com.example.testb.MainActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);


2、IntentFilter的简介

IntentFilter实际上相当于Intent的过滤器,一个应用程序开发完成后,需要告诉Android系统自己能够处理哪些隐形的Intent请求,这就需要声明IntentFilter。IntentFilter的使用方法实际上非常简单,仅声明该应用程序接收什么样的Intent请求即可。

IntentFilter过滤Intent时,一般是通过Action、Data及Category三方面进行监测的。接下来分别对这三方面进行介绍。

(1)检查Action

一个Intent只能设置一种Action,但是一个IntentFilter却可以设置多个Action过滤。当IntentFilter设置了多个Action时,只需一个满足即可完成Action验证。当IntentFilter中没有说明任何一个Action时,那么任何的Action都不会与之匹配。而如果Intent中没有包含任何Action,那么只要IntentFilter中含有Action时,便会匹配成功。

String MY_ACTION = "com.view.my_action";//自定义
Intent intent = new Intent(MY_ACTION);
startActivity(intent);
<activity 
    android:name=".TestActivity" 
    android:launchMode="singleTop">
    <intent-filter>
        <action android:name="com.view.my_action"/>    
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>


(2)检查Data

数据的监测主要包含两部分,即数据的URI及数据类型,而数据URI又被分成三部分进行匹配(scheme、authority、path),只有这些全部匹配时,Data的验证才会成功。

如果你的组件能处理多个的话,你可以包含多个条件。你可以使用下面属性的任意组合来指定组件支持的数据:

android:host 指定一个有效的主机名(例如,com.google)。

android:mimetype 

允许你设定组件能处理的数据类型。例如,<type android:value=”vnd.android.cursor.dir/*”/>能匹配任何Android游标。

android:path 有效地URI路径值(例如,/transport/boats/)。

android:port 特定主机上的有效端口。

android:scheme 需要一个特殊的图示(例如,content或http)。

例如,下面的URI: 
content://com.example.project:200/folder/subfolder/etc 

scheme是content,host是"com.example.project",port是200,path是"folder/subfolder/etc"。host和port一起构成URI的凭据(authority),如果host没有指定,port也被忽略。 这四个属性都是可选的,但它们之间并不都是完全独立的。要让authority有意义,scheme必须也要指定。要让path有意义,scheme和authority也都必须要指定。

当比较intent对象和过滤器的URI时,仅仅比较过滤器中出现的URI属性。例如,如果一个过滤器仅指定了scheme,所有有此scheme的URIs都匹配过滤器;如果一个过滤器指定了scheme和authority,但没有指定path,所有匹配scheme和authority的URIs都通过检测,而不管它们的path;如果四个属性都指定了,要都匹配才能算是匹配。然而,过滤器中的path可以包含通配符来要求匹配path中的一部分。

<data>元素的type属性指定数据的MIME类型。Intent对象和过滤器都可以用"*"通配符匹配子类型字段,例如"text/*","audio/*"表示任何子类型。

数据检测既要检测URI,也要检测数据类型。规则如下:

① 一个Intent对象既不包含URI,也不包含数据类型:仅当过滤器也不指定任何URIs和数据类型时,才不能通过检测;否则都能通过。

② 一个Intent对象包含URI,但不包含数据类型:仅当过滤器也不指定数据类型,同时它们的URI匹配,才能通过检测。例如,mailto:tel:都不指定实际数据。

③ 一个Intent对象包含数据类型,但不包含URI:仅当过滤也只包含数据类型且与Intent相同,才通过检测。

④ 一个Intent对象既包含URI,也包含数据类型(或数据类型能够从URI推断出):数据类型部分,只有与过滤器中之一匹配才算通过;URI部分,它的URI要出现在过滤器中,或者它有content:file: URI,又或者过滤器没有指定URI。换句话说,如果它的过滤器仅列出了数据类型,组件假定支持content:file: 。

如果一个Intent能够通过不止一个活动或服务的过滤器,用户可能会被问那个组件被激活。如果没有目标找到,会产生一个异常。

Intent intent = new Intent("com.view.my_action");
intent.setData(Uri.parse("oschina://com.example.project"));
startActivity(intent);
<activity android:name=".MainActivity" android:label="@string/title_notes_list">
    <intent-filter>
        <action android:name="com.view.my_action"/>    
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="oschina" android:host="com.example.project"/>
    </intent-filter>
</activity>

(3)检查Category

IntentFilter同样可以设置多个Category,当Intent中的Category与IntentFilter中的一个Category完全匹配时,便会通过Category的检查,而其他的Category并不受影响。但是当IntentFilter没有设置Category时,只能与没有设置Category的Intent相匹配。


其它问题:

Intent在寻找目标组件时有两种方法:

第一种,显式调用(Explicit Intent),通过Component name直接指定;

第二种,隐式调用(implicit Intent),没有明确指定目标组件的名称,那么就要通过一定的条件过滤筛选。

Implicit Intent 到底发给哪个activity?

这需要进行三个匹配,一个是action,一个是category,一个是data。根据三个的匹配结果,找到应该启动的Activity。

Data匹配时的规则一共有四条:

a.如果Intent没有指定Data相关的字段,只能匹配上没有指定Data的IntentFilter。也就是说如果一个Intent没有指定任何的Data(Uri和Type),它只能匹配到没有指定任何Data(Scheme和Type)的IntentFilter。

b.如果一个Intent只指定了Uri但是没有Type(并且Type也不能够从Uri中分析出)只能匹配到仅指定了相应Scheme且没有指定Type的IntentFilter。实际的例子有如果一个Intent是想要发邮件,或是打电话,它们的Intent是类似这样的:"mailto:someone@sb.com"和"tel:1234567"。换句话说,这些Uri本身就是数据,而不再是一个指向数据的地址。比如:Phone中的Dialer就有如下的IntentFilter:

<intent-filter>
    <action android:name="android.intent.action.CALL" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="tel" />
</intent-filter>

再如,要处理SD状态变化的IntentFilter:

<intent-filter>
      <action android:name="android.intent.action.MEDIA_MOUNTED"/>
      <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
      <action android:name="android.intent.action.MEDIA_SHARED"/>
      <action android:name="android.intent.action.MEDIA_REMOVED"/>
      <action android:name="android.intent.action.MEDIA_EJECT"/>
      <category android:name="android.intent.category.DEFAULT" />       
      <data android:scheme="file" />
</intent-filter>

再如,要处理Package状态变化的IntentFilter:

<intent-filter>
      <action android:name="android.intent.action.PACKAGE_ADDED"/>
      <action android:name="android.intent.action.PACKAGE_REMOVED"/>
      <action android:name="android.intent.action.PACKAGE_DATA_CLEARED"/>
      <category android:name="android.intent.category.DEFAULT" />
      <data android:scheme="package" />
<intent-filter>

但是注意,对于想对数据进行操作的Intent,最好不要只指定Uri,而不指定类型。因为如果这样做通常会匹配到一大堆

c. 如果一个Intent只指定了Type,但是没有指定Uri,它只能匹配到只指定了相应Type且没有指定Scheme的IntentFitler

d. 如果一个Intent即有Uri又有Type,那么它会匹配上:1).Uri和Type都匹配的IntentFilter;2).首先Type要匹配,另外如果Intent的Uri是content:或file:,且IntentFilter没有指定Scheme的IntentFilter。因为对于Android来讲content和file这二种Scheme是系统最常见也是用的最多的,所以就当成缺省值来对待。

另外需要注意,Type,因为是MimeType,所以是允许使用通配符的,比如'image/*',能匹配上所有以'image'为开头的类型,也说是说能匹配上所有的图像。

根据Data匹配的例子

假如系统中有四个Activity,A的IntentFilter是这样子的:

    <activity ...>
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="content" android:mimeType="image/*" />
            </intent-filter>
   </activity>

这表明A可以发送一切图片类型,并且内容必须是由ContentProvider提供的,也就是Uri必须是以"content://"开头的
而另外一个Activity B是这样子声明的:

    <activity ...>
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" android:mimeType="image/*" />
            </intent-filter>
   </activity>

这表明B可以发送一切图片,但内容必须是单独的一个文件,也就是Uri必须是由"file://"开头的
还有一个C是这样子声明的:

    <activity ...>
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
   </activity>

这表明C只能接收那些没有指定任何Uri和Type的Action是SEND的Intent。
而D是这样子声明的:

    <activity ...>
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>
   </activity>

这表明D可以发送一切图片,无论是数据库内的(content),还是单独的文件(file)。
如果一个Intent是这样写的:

Intent share = new Intent(Intent.ACTION_SEND);
startActivity(share);

那么它只能匹配C,因为C没有指定数据和类型,Action是SEND,根据规则a,它只能匹配Activity A。但如果给Intent加上额外的条件

share.setDataAndType(uri,"image/jpeg");

那么如果uri是数据库内容,它会匹配到A,如果它是一个文件,会匹配到B。但无论是content还是file都会匹配到D,因为它能处理以任何形式存储的图片。但始终不会匹配到C,因为C没有声明Data字段,所以不会匹配上。
所以,通常想把组件作为系统公用接口时都是这样子来写:

<activity ...>
    <intent-filter>
        <!-- implement public actions such as View, Edit, Pick or Send -->
        <action android:name="android.intent.action.SEND" />
        <!-- never forget default category, otherwise your activity never receives intents -->
        <category android:name="android.intent.category.DEFAULT" />
        <!-- specify mimeType to constrain data type, receive data from both content provider and file -->
        <data android:mimeType="image/*" />
        <!-- specify scheme to constrain data source, if necessary -->
        <data android:shceme="http" />
    </intent-filter>
</activity>

Intent和IntentFilter对于组件Activity来讲注意事项比较多,但是对于Service和BroadcastReceiver来说就没有那么多的注意事项了,因为对于Service和BroadcastReceiver通常都不用设置Category和Data。但也有例外,比如前面所讲到的SD相关广播和应用程序安装相关广播。
另外要注意,如果使用Context.startActivity()或Context.startActivityForResult(),Context.bindService()和Context.startService(),如果系统没有为Intent匹配到目标Activity和Service那么会有RuntimeException(ActivityNotFoundException)抛出;如果有多个目标同时匹配,会以列表的方式来让用户选择使用哪个。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!