Activity Intent詳解

一、Intent簡介

Intent表明了Android應用的啓動「意圖」,Android應用將會根據Intent來啓動指定組件,至於到底啓動哪一個組件,則取決於Intent的各個屬性。下面就來介紹一下Intent的各類發展,以及Android如何根據不一樣屬性來啓動相應的組件。Intent是由Component、Action、Data、Category、Extra及Flag六部分組成的,接下來將分別對其進行詳細介紹。html

(1)Component namejava

組件名稱實際上就是一個ComponentName對象,用於標識惟一的應用程序組件,即指明瞭指望的Intent組件,這種對象的名稱是由目標組件的類名與目標組件的包名組合而成的。須要注意的是Component name是一個可選項,若是被設置,那麼Intent對象就顯式指定了要轉向的組件,若是沒有被設置,則Intent對象須要根據其餘信息進行篩選查找。android

組件名稱經過 setComponent(),setClass(),setClassName()設置,經過getComponent()獲取。git

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)Actionweb

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);


二、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)拋出;若是有多個目標同時匹配,會以列表的方式來讓用戶選擇使用哪一個。

相關文章
相關標籤/搜索