前言:接上一篇廣播講后面的,intent
Intent
概述
Android中的Intent是一個類(就是一個封裝類許多方法的類,不要想的那么神秘),可以用來在一個組件中啟動App中的另一個組件或者是啟動另一個App的組件,這里所說的組件指的是Activity、Service以及Broadcast。(這些組件不知道的,自行百度哈)
Intent的作用(其實就是intent這個類的功能)
?啟動Activity:可以將Intent對象傳遞給startActivity()方法或startActivityForResult()方法以啟動一個Activity,該Intent對象包含了要啟動的Activity的信息及其他必要的數據。
回傳Activity的數據:startActivityForResult和onactivityresult的配合使用,可以得到上一個activity中傳回的數據(后面詳細介紹)
?啟動Service:可以將Intent對象傳遞給startService()方法或bindService()方法以啟動一個Service,該Intent對象包含了要啟動的Service的信息及其他必要的數據。
?發送廣播:廣播是一種所有App都可以接收的信息。android系統會發布各種類型的廣播,比如發布開機廣播或手機充電廣播等。我們也可以給其他的App發送廣播,可以將Intent對象傳遞給sendBroadcast()方法或sendOrderedBroadcast()方法或sendStickyBroadcast()方法以發送自定義廣播。(如果不知道廣播,自行看上一篇)
Intent的類型
顯式的Intent:
如果Intent中明確包含了要啟動的組件的完整類名(包名及類名),那么這個Intent就是explict的,即顯式的。使用顯式Intent最典型的情形是在你自己的App中啟動一個組件,因為你自己肯定知道自己的要啟動的組件的類名。比如,為了響應用戶操作通過顯式的Intent在你的App中啟動一個Activity或啟動一個Service下載文件。
當創建了一個顯式Intent去啟動Activity或Service的時候,系統會立即啟動Intent中所指定的組件。
隱式的Intent:
如果Intent沒有包含要啟動的組件的完整類名,那么這個Intent就是implict的,即隱式的。雖然隱式的Intent沒有指定要啟動的組件的類名,但是一般情況下,隱式的Intent都要指定需要執行的action。一般,隱式的Intent只用在當我們想在自己的App中通過Intent啟動另一個App的組件的時候,讓另一個App的組件接收并處理該Intent。例如,你想在地圖上給用戶顯示一個位置,但是你的App又不支持地圖展示,這時候你可以將位置信息放入到一個Intent中,然后給它指定相應的action,通過這樣隱式的Intent請求其他的地圖型的App(例如Google Map、百度地圖等)來在地圖中展示一個指定的位置。隱式的Intent也體現了Android的一種設計哲學:我自己的App無需包羅萬象所有功能,可以通過與其他App組合起來,給用戶提供很好的用戶體驗。而連接自己的App與其他App的紐帶就是隱式Intent。(這點很重要,要學會用別人的資源為自己服務)
當創建了一個隱式Intent去使用的時候,Android系統會將該隱式Intent所包含的信息與設備上其他所有App中manifest文件中注冊的組件的Intent Filters進行對比過濾,從中找出滿足能夠接收處理該隱式Intent的App和對應的組件。如果有多個App中的某個組件都符合條件,那么Android會彈出一個對話框讓用戶選擇需要啟動哪個App。(這里畫重點,有些同學可能還不知道吧)
Intent過濾器
Intent Filter,一個組件可以包含0個或多個Intent Filter。Intent Filter是寫在App的manifest文件中的,其通過設置action或uri數據類型等指明了組件能夠處理接收的Intent的類型。如果你給你的Activity設置了Intent Filter,那么這就使得其他的App有可能通過隱式Intent啟動你的這個Activity。反之,如果你的Activity不包含任何Intent Filter,那么該Activity只能通過顯式Intent啟動,由于我們一般不會暴露出我們組件的完整類名,所以這種情況下,其他的App基本就不可能通過Intent啟動我們的Activity了(因為他們不知道該Activity的完整類名),只能由我們自己的App通過顯式Intent啟動。
注意:為了確保應用的安全性,啟動service時,請始終使用顯式 Intent,且不要為服務聲明 Intent 過濾器。使用隱式 Intent 啟動服務存在安全隱患,因為您無法確定哪些服務將響應 Intent,且用戶無法看到哪些服務已啟動。從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 調用bindService(),系統會引發異常。
前面都是基本介紹,下面,我們來詳細聊聊intent的構建(當然,這個構建是比較完整的,這里是基本參考官方的)
Intent構建(包含組成)
intent對象攜帶了 Android 系統用來確定要啟動哪個組件的信息(例如,準確的組件名稱或應當接收該 Intent 的組件類別),以及收件人組件為了正確執行操作而使用的信息(例如,要采取的操作以及要處理的數據)。
intent中包含的主要信息如下:
組件名稱(Component Name)
要啟動的組件名稱。
這是可選項,但也是構建顯式Intent 的一項重要信息,這意味著 Intent 應當僅傳遞給由組件名稱定義的應用組件。 如果沒有組件名稱,則 Intent 是隱式的,且系統將根據其他 Intent 信息(例如,以下所述的操作、數據和類別)決定哪個組件應當接收 Intent。 因此,如需在應用中啟動特定的組件,則應指定該組件的名稱。
注:啟動Service時,您應始終指定組件名稱。 否則,您無法確定哪項服務會響應 Intent,且用戶無法看到哪項服務已啟動。
Intetn的這一字段是一個ComponentName對象,您可以使用目標組件的完全限定類名指定此對象,其中包括應用的軟件包名稱。 例如,com.example.ExampleActivity。您可以使用setComponent()、setClass()、setClassName()或Intent構造函數設置組件名稱。
操作(?Action?)
指定要執行的通用操作(例如,“查看”或“選取”)的字符串。
對于廣播 Intent,這是指已發生且正在報告的操作。操作在很大程度上決定了其余 Intent 的構成,特別是數據和 extra 中包含的內容。
您可以指定自己的操作,供 Intent 在您的應用內使用(或者供其他應用在您的應用中調用組件)。但是,您通常應該使用由Intent類或其他框架類定義的操作常量。以下是一些用于啟動 Activity 的常見操作:
ACTION_VIEW
如果您擁有一些某項 Activity 可向用戶顯示的信息(例如,要使用圖庫應用查看的照片;或者要使用地圖應用查看的地址),請使用 Intent 將此操作與startActivity()結合使用。
ACTIVITY_SEND
這也稱為“共享”Intent。如果您擁有一些用戶可通過其他應用(例如,電子郵件應用或社交共享應用)共享的數據,則應使用 Intent 將此操作與startActivity()結合使用。
有關更多定義通用操作的常量,請參閱類參考文檔。 其他操作在 Android 框架中的其他位置定義。例如,對于在系統的設置應用中打開特定屏幕的操作,將在Settings中定義。
您可以使用setAction或Intent構造函數為 Intent 指定操作。
如果定義自己的操作,請確保將應用的軟件包名稱作為前綴。 例如:
static final String ACTION_TIMETRAVEL="com.example.action.TIMETRAVEL";
注:提供一個intent? action大全參考地址 http://www.cnblogs.com/playing/archive/2010/09/15/1826918.html
數據(Data)
引用待操作數據和/或該數據 MIME 類型的 URI(Uri對象)。提供的數據類型通常由 Intent 的操作決定。例如,如果操作是ACTION_EDIT,則數據應包含待編輯文檔的 URI。
創建 Intent 時,除了指定 URI 以外,指定數據類型(其 MIME 類型)往往也很重要。例如,能夠顯示圖像的 Activity 可能無法播放音頻文件,即便 URI 格式十分類似時也是如此。因此,指定數據的 MIME 類型有助于 Android 系統找到接收 Intent 的最佳組件。但有時,MIME 類型可以從 URI 中推斷得出,特別當數據是content:URI 時尤其如此。這表明數據位于設備中,且由ContentProvider控制,這使得數據 MIME 類型對系統可見。
要僅設置數據 URI,請調用setData()。 要僅設置 MIME 類型,請調用setType()。如有必要,您可以使用setDataAndType()同時顯式設置二者。
注意:若要同時設置 URI 和 MIME 類型,請勿調用setData()和setType(),因為它們會互相抵消彼此的值。請始終使用setDataAndType()同時設置 URI 和 MIME 類型。
類別( Category )
一個包含應處理 Intent 組件類型的附加信息的字符串。 您可以將任意數量的類別描述放入一個 Intent 中,但大多數 Intent 均不需要類別。 以下是一些常見類別:
CATEGORY_BROWSABLE
目標 Activity 允許本身通過網絡瀏覽器啟動,以顯示鏈接引用的數據,如圖像或電子郵件。
CATEGORY_LAUNCHER
該 Activity 是任務的初始 Activity,在系統的應用啟動器中列出。
有關類別的完整列表,百度吧
您可以使用addCategory()指定類別。
以上列出的這些屬性(組件名稱、操作、數據和類別)表示 Intent 的既定特征。 通過讀取這些屬性,Android 系統能夠解析應當啟動哪個應用組件。
Extra( 筆記拿出來了,重點 ,用心看)
或許就這么看,不知道是什么但是,你是否記得 intent.putExtra(key,value)呢?沒錯,這個Extra就是我們常用的那個東西。
extras,顧名思義,就是額外的數據信息,Intent中有一個Bundle對象存儲著各種鍵值對,接收該Intent的組件可以從中讀取出所需要的信息以便完成相應的工作。有的Intent需要靠Uri攜帶數據,有的Intent是靠extras攜帶數據信息。
你可以通過調用Intent對象的各種重載的putExtra(key, value)方法向Intent中加入各種鍵值對形式的額外數據。你也可以直接創建一個Bundle對象,向該Bundle對象傳入很多鍵值對,然后通過調用Intent對象的putExtras(Bundle)方法將其一塊設置給Intent對象中去。
例如,你創建了一個action為ACTION_SEND的Intent對象,然后想用它啟動e-mail發送郵件,那么你需要給該Intent對象設置兩個extra的值:
用Intent.EXTRA_EMAIL作為key值設置收件方,用Intent.EXTRA_SUBJECT作為key值設置郵件標題。
Intent類里面也指定了很多預定義的EXTRA_*形式的extra,例如上面我們提到的(Intent.EXTRA_EMAIL和Intent.EXTRA_SUBJECT)。如果你想要聲明你自己自定義的extra,請確保將你的App的包名作為你的extra的前綴,例如:
static final String EXTRA_GIGAWATTS ="com.example.EXTRA_GIGAWATTS";
標志(Flags)
flag就是標記的意思,Intent類中定義的flag能夠起到作為Intent對象的元數據的作用。這些flag會告知Android系統如何啟動Activity(例如,新啟動的Activity屬于哪個task)以及在該Activity啟動后如何對待它。Flags大全,也請自行百度,就不寫了。
本來想寫一些例子的,不過想了一下,我的重點不在那上面就不寫了(想了解的同學,自行百度,隱式intent,顯式intent)
強制用戶使用App Chooser
如果有多個應用響應隱式 Intent,則用戶可以選擇要使用的應用,并將其設置為該操作的默認選項。 如果用戶可能希望今后一直使用相同的應用執行某項操作(例如,打開網頁時,用戶往往傾向于僅使用一種網絡瀏覽器),則這一點十分有用。
但是,如果多個應用可以響應 Intent,且用戶可能希望每次使用不同的應用,則應采用顯式方式顯示選擇器對話框。 選擇器對話框每次都會要求用戶選擇用于操作的應用(用戶無法為該操作選擇默認應用)。 例如,當應用使用ACTION_SEND操作執行“共享”時,用戶根據目前的狀況可能需要使用另一不同的應用,因此應當始終使用選擇器對話框。
要顯示選擇器,請使用createChooser()創建Intent,并將其傳遞給startActivity()。例如:
其實,本來還想聊聊自己app添加條件,接收外部app調用的東西,不過,后來發現,這一部分,除非自己做到像某Q或者某地圖那樣,不然,根本就沒人來調用你。所以,就不寫了,需要了解的,自行百度:接收intent調用自己app
順便總結一下啊 intent-filter其實,就是action category data的組合,這里就不深入了,畢竟這是基礎技術篇,屬于基礎部分。
下面我們再介紹,intent中比較特殊的一個東西,penddingIntent
penddingIntent(這部分,屬于額外部分,平時使用的頻率相對較少)
概述
在Android中,我們常常使用PendingIntent來表達一種“留待日后處理”的意思。從這個角度來說,PendingIntent可以被理解為一種特殊的異步處理機制。不過,單就命名而言,PendingIntent其實具有一定誤導性,因為它既不繼承于Intent,也不包含Intent,它的核心可以粗略地匯總成四個字——“異步激發”。
很明顯,這種異步激發常常是要跨進程執行的。比如說A進程作為發起端,它可以從系統“獲取”一個PendingIntent,然后A進程可以將PendingIntent對象通過binder機制“傳遞”給B進程,再由B進程在未來某個合適時機,“回調”PendingIntent對象的send()動作,完成激發。
在Android系統中,最適合做集中性管理的組件就是AMS(Activity Manager Service)啦,所以它義不容辭地承擔起管理所有PendingIntent的職責。這樣我們就可以畫出如下示意圖:
注意其中的第4步“遞送相應的intent”。這一步遞送的intent是從何而來的呢?簡單地說,當發起端獲取PendingIntent時,其實是需要同時提供若干intent的。這些intent和PendingIntent只是配套的關系,而不是聚合的關系,它們會被緩存在AMS中。日后,一旦處理端將PendingIntent的“激發”語義傳遞到AMS,AMS就會嘗試找到與這個PendingIntent對應的若干intent,并遞送出去。
當然,以上說的只是大概情況,實際的技術細節會更復雜一點兒。下面我們就來談談細節。
PendingIntent的技術細節
發起端獲取PendingIntent
我們先要理解,所謂的“發起端獲取PendingIntent”到底指的是什么。難道只是簡單new一個PendingIntent對象嗎?當然不是。此處的“獲取”動作其實還含有向AMS“注冊”intent的語義。
在PendingIntent.java文件中,我們可以看到有如下幾個比較常見的靜態函數:
public static PendingIntentgetActivity(Context context, int requestCode, Intent intent, int flags)
public static PendingIntentgetActivities(Context context, int requestCode, Intent[] intents, int flags)
public static PendingIntentgetActivities(Context context, int requestCode, Intent[] intents, int flags, Bundle options)
public static PendingIntentgetBroadcast(Context context, int requestCode, Intent intent, int flags)
public static PendingIntentgetService(Context context, int requestCode, Intent intent, int flags)
通過這些名字,大致猜猜想,就是從activity,broadcast,service中獲取的,這樣想是不是邏輯很通呀,但是,說實話,這幾個函數的命名可真不怎么樣,所以我們簡單解釋一下。上面的getActivity()的意思其實是,獲取一個PendingIntent對象,而且該對象日后激發時所做的事情是啟動一個新activity。也就是說,當它異步激發時,會執行類似Context.startActivity()那樣的動作。相應地,getBroadcast()和getService()所獲取的PendingIntent對象在激發時,會分別執行類似Context..sendBroadcast()和Context.startService()這樣的動作。至于最后兩個getActivities(),用得比較少,激發時可以啟動幾個activity。
怎么樣,是不是感覺,被坑了,也不知道誰取得名字,誤導性這么強。下面舉個簡單例子:
其中那句new PendingIntent(target)創建了PendingIntent對象,其重要性自不待言。然而,這個對象的內部核心其實是由上面那個getIntentSender()函數得來的。而這個IIntentSender核心才是我們真正需要關心的東西。
說穿了,此處的IIntentSender對象是個binder代理,它對應的binder實體是AMS中的PendingIntentRecord對象。PendingIntent對象構造之時,IIntentSender代理作為參數傳進來,并記錄在PendingIntent的mTarget域。日后,當PendingIntent執行異步激發時,其內部就是靠這個mTarget域向AMS傳遞語義的。(這里可能有點難懂,簡單說,就是獲取一個標識,以后,激發的時候,通過這個標識,去激發對應的事件)
我們前文說過,PendingIntent常常會經由binder機制,傳遞到另一個進程去。而binder機制可以保證,目標進程得到的PendingIntent的mTarget域也是合法的IIntentSender代理,而且和發起端的IIntentSender代理對應著同一個PendingIntentRecord實體。示意圖如下:
看到這個圖,你知道了mTarget,但是,這里面有一個PendingIntentRecord,這是什么鬼?別急,下面就講
AMS里的PendingIntentRecord
注意其中那個key域。這里的Key是個PendingIntentRecord的內嵌類
注意其中的allIntents[]數組域以及requestIntent域。前者記錄了當初獲取PendingIntent時,用戶所指定的所有intent(雖然一般情況下只會指定一個intent,但類似getActivities()這樣的函數還是可以指定多個intent的),而后者可以粗淺地理解為用戶所指定的那個intent數組中的最后一個intent。現在大家應該清楚異步激發時用到的intent都存在哪里了吧。
我們再來看看Key的構造函數,是怎樣吧
Key不光承擔著記錄信息的作用,它還承擔“鍵值”的作用。這是不是和我們平時理解的key不一樣。其實,不用多想,這就是一個類罷了
在AMS中,管理著系統中所有的PendingIntentRecord節點,所以需要把這些節點組織成一張表(嗯,可能有些同學不知道什么叫節點,也不明白為什么這里會事一張表,沒關系,黑盒思想,不用知道,因為這部分東西涉及偏底層了,先就這樣吧)
AMS中的PendingIntentRecord總表
這張哈希映射表的鍵值類型就是剛才所說的PendingIntentRecord.Key。
以后每當我們要獲取PendingIntent對象時,PendingIntent里的mTarget是這樣得到的:AMS會先查mIntentSenderRecords表,如果能找到符合的PendingIntentRecord節點,則返回之。如果找不到,就創建一個新的PendingIntentRecord節點。因為PendingIntentRecord是個binder實體,所以經過binder機制傳遞后,客戶進程拿到的就是個合法的binder代理。如此一來,前文的示意圖可以進一步修改成下圖:
AMS里的getIntentSender()函數
現在,我們回過頭繼續說前文的getActivity(),以及其調用的getIntentSender()。我們先列一遍getActivity()的原型:
context參數是調用方的上下文。
requestCode是個簡單的整數,起區分作用。
intent是異步激發時將發出的intent。
flags可以包含一些既有的標識,比如FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT等等。(不少同學對這個域不是很清楚,沒關系,黑盒思想)
options可以攜帶一些額外的數據。
getActivity()的代碼很簡單,其參數基本上都傳給了getIntentSender()。
getIntentSender()的原型大體是這樣的:
其參數比getActivity()要多一些,我們逐個說明
type參數表明PendingIntent的類型?getActivity()和getActivities()動作里指定的類型值是INTENT_SENDER_ACTIVITY,getBroadcast()和getService()和動作里指定的類型值分別是INTENT_SENDER_BROADCAST和INTENT_SENDER_SERVICE。另外,在Activity.java文件中,我們還看到一個createPendingResult()函數,這個函數表達了發起方的activity日后希望得到result回饋的意思,所以其內部調用getIntentSender()時指定的類型值為INTENT_SENDER_ACTIVITY_RESULT。
packageName參數表示發起端所屬的包名。
token參數是個指代回饋目標方的代理。
這是什么意思呢?我們常用的getActivity()、getBroadcast()和getService()中,只是把這個參數簡單地指定為null,表示這個PendingIntent激發時,是不需要發回什么回饋的。不過當我們希望獲取類型為INTENT_SENDER_ACTIVITY_RESULT的PendingIntent時,就需要指定token參數了。具體可參考createPendingResult()的代碼:
看到了嗎?傳入的token為Activity的mToken或者其mParent.mToken。說得簡單點兒,AMS內部可以根據這個token找到其對應的ActivityRecord,日后當PendingIntent激發時,AMS可以根據這個ActivityRecord確定出該向哪個目標進程的哪個Activity發出result語義。
resultWho參數和token參數息息相關,一般也是null啦。在createPendingResult()中,其值為Activity的mEmbeddedID字符串。
requestCode參數是個簡單的整數,可以在獲取PendingIntent時由用戶指定,它可以起區分的作用。
intents數組參數是異步激發時希望發出的intent。對于getActivity()、getBroadcast()和getService()來說,都只會指定一個intent而已。只有getActivities()會嘗試一次傳入若干intent。
resolvedTypes參數基本上和intent是相關的。一般是這樣得到的:
這個值常常和intent內部的mData URI有關系,比如最終的值可能是URI對應的MIME類型。
flags參數可以指定PendingIntent的一些行為特點。具體了解,百度吧。
還有一些底層實現,這里就不詳細說了,下面,就說說關于激發的事情。
PendingIntent的激發動作
下面我們來看PendingIntent的激發動作。在前文我們已經說過,當需要激發PendingIntent之時,主要是通過調用PendingIntent的send()函數來完成激發動作的。PendingIntent提供了多個形式的send()函數,然而這些函數的內部其實調用的是同一個send(),其函數原型如下:
該函數內部最關鍵的一句是:
我們前文已經介紹過這個mTarget域了,它對應著AMS中的某個PendingIntentRecord。
所以我們要看一下PendingIntentRecord一側的send()函數,其代碼如下:
其中sendInner()才是真正做激發動作的函數。
sendInner()完成的主要邏輯動作有:
1 如果當前PendingIntentRecord節點已經處于canceled域為true的狀態,那么說明這個節點已經被取消掉了,此時sendInner()不會做任何實質上的激發動作,只是簡單地return ActivityManager.START_CANCELED而已。
2 如果當初在創建這個節點時,使用者已經指定了FLAG_ONE_SHOT標志位的話,那么此時sendInner()會把這個PendingIntentRecord節點從AMS中的總表中摘除,并且把canceled域設為true。而后的操作和普通激發時的動作是一致的,也就是說也會走下面的第3)步。
3 關于普通激發時應執行的邏輯動作是,根據當初創建PendingIntentRecord節點時,用戶指定的type類型,進行不同的處理。這個type其實就是我們前文所說的INTENT_SENDER_ACTIVITY、INTENT_SENDER_BROADCAST、INTENT_SENDER_SERVICE等類型啦,大家如有興趣,可自己參考本文一開始所說的getActivity()、getBroadcast()、getService()等函數的實現代碼。
理論說一大堆,下面還是用一個例子說明一下吧:
這個例子是我們常見的一個例子,就是通知。點擊通知欄,然后,就會跳轉到對應activity:
這樣寫就可以了,然后,在通知欄那里點擊通知,ok,跳轉到對應地方,是不是非常簡單。當然,這里只是簡單舉個例子,不過,目前而言,我們用到的也不會太多。
總結一下,pendingintent,其實,就是不馬上執行罷了,相當于,一個觸發器,等什么時候,觸發了某個條件,就去執行。簡單來說,這玩意用法就是
1.包裝intent(意圖,目的)
2.pendingintent裝載前面的intent(當然要配置一些屬性,比如context呀之類的)
3.等待觸發(AMS做的事情,我們不管)
4.觸發(比如點擊事件之類的)
最后要提一個東西,曾經遇到的坑
沒錯就是這個方法,當接收數據的activity已經存在與棧中的時候,我們常規的getIntent,是得不到新過來的intent的,需要,用上面這個方法,才能獲取到新的intent。這只是一個小插曲,不過,這個問題,還是需要記住的。