Intent解析
基於組件的架構體系,除了有定義良好的組件,如何把這些組件組裝在一起,也是一門藝術。在Android中,
Intent(貌似通常譯作:
意圖...),就是連接各組件的橋梁。
前段時間看同事們做Symbian平台的
網易掌上郵(真的是做的用心,NB的一米,熱情歡迎所有163郵箱的S60v3用戶,猛點擊之...),有個功能是為郵件添加附件,比如你想要通過郵件發送一副圖片泡 mm,可能需要有個很直觀的方式從本地選一副珍藏美圖,抑或是拿相機來個完美自拍。在Symbian中,這樣的功能,都需要你用底層的API,自己一點點寫。為了讓選圖片體驗更好,可能需要做一個類似於圖片浏覽器之類的東西,為了把拍照做的更為順暢,甚至需要實現從聚焦到調節亮度之類一整套的相機功能。
而其實呢,用戶的手機中可能本身就裝了其他的專業圖片浏覽器、相機等應用,這些應用已經非常出色好用,而用戶也已然能很純屬使用它們,如果能進行調用,對郵箱的開發者和用戶而言,都會是個更好的選擇。但在Symbian這樣殘敗的系統裡,應用和應用之間的結合能力奇弱無比,想復用,基本比登天還難,作為開發者,只能忍住一次又一次的惡心,為了用戶,做這些重復造輪子吃力不討好的附加工作。
還好還好,在Android中,一切變得美好多了,它將開發者從接口和對象的細節中解救出來,讓我們有更多精力投入到核心功能的開發中去。在Android 中,如果你需要選個圖拍個片,只需要構造一個描述你此項意願的Intent,發送出去,系統會幫你選擇一個能夠處理該項業務的組件來滿足你的需求,而不再需要糾結在具體的接口和實現上,Perfect World,便應如此。
Intent構成
Intent被譯作意圖,其實還是很能傳神的,Intent期望做到的,就是把實現者和調用者完全解耦,調用者專心將以意圖描述清晰,發送出去,就可以夢想成真,達到目的。
當然,這麼說太虛了,庖丁解牛,什麼東西切開來看看,也許就清晰了。
Intent(
reference/android/content/Intent.html),在Android中表現成一個類,發起一個意圖,你需要構造這樣一個對象,並為下列幾項中的一些進行賦值:
- Action。當日常生活中,描述一個意願或願望的時候,總是有一個動詞在其中。比如:我想做三個俯臥撐;我要看一部x片;我要寫一部血淚史,之類雲雲。在Intent中,Action就是描述看、做、寫等動作的,當你指明了一個Action,執行者就會依照這個動作的指示,接受相關輸入,表現對應行為,產生符合的輸出。在Intent類中,定義了一批量的動作,比如ACTION_VIEW,ACTION_PICK,之類的,基本涵蓋了常用動作,整一個降龍十八掌全集。當然,你也可以與時俱進,創造新的動作,比如lou這樣的。與系統預定義的相比,這些自定義動作的流通范圍很是有限,除非做了非常NB的應用,大家都需要follow你,否則通常都是應用內部流通。
- Data。當然,光有動作還是不夠的,還需要有更確切的對象信息。比如,同樣是泡這個動作,但泡咖啡,和泡妞,就差之千裡了。Data的描述,在Android中,表現成為一個URI。用在內部通信中,可能描述是Content Provider用的形如content://xxxx這樣的東東,抑或是外部的一個形如tel://xxxx這樣的鏈接。總而言之,是能夠清楚准確的描述一個數據地址的uri。
- Type。說了Data,就必須要提Type,很多時候,會有人誤解,覺著Data和Type的差別,就猶如泡妞和泡馬子之間的差別一樣,微乎其微。但其實不然,Type信息,是用MIME來表示的,比如text/plain,這樣的東西。說到這裡,兩者差別就很清晰了,Data就是門牌號,指明了具體的位置,具體問題具體分析,而type,則是強調物以類聚,解決一批量的問題。實際的例子是這樣的,比如,從某個應用撥打一個電話,會發起的是action為ACTION_DIAL且data為tel:xxx這樣的 Intent,對應的人類語言就是撥打xxx的電話,很具象。而如果使用type,就寬泛了許多,比如浏覽器收到一個未知的MIME類型的數據(比如一個視頻...),就會放出這樣的Intent,求系統的其他應用來幫助,表達成自然語言應該就是:查看pdf類文檔,這樣的。
- Category。通過Action,配合Data或Type,很多時候可以准確的表達出一個完整的意圖了,但也會有些時候,還需要加一些約束在裡面才能夠更精准。比如,如果你雖然很喜歡做俯臥撐,但一次做三個還只是在特殊的時候才會發生,那麼你可能表達說:每次吃撐了的時候,我都想做三個俯臥撐。吃撐了,這就對應著Intent的Category的范疇,它給所發生的意圖附加一個約束。在Android中,一個實例是,所有應用主Activity(就是單獨啟動時候,第一個運行的那個Activity...),都需要能夠接受一個Category為 CATEGORY_LAUNCHER,Action為ACTION_Main的意圖。
- Component。在此之前,我們企圖用Action,Data/Type,Category去描述一個意圖,這是Android推薦,並期望大家在大多數時候使用的,這樣模式在Android中稱做Implicit Intents,通過這種模式,提供一種靈活可擴展的模式,給用戶和第三方應用一個選擇權。比如,還是一個郵箱軟件,他大部分功能都好,就是選擇圖片的功能做的很土,怎麼辦?如果它采用的是Implicit Intents,那麼它就是一個開放的體系了,手機中沒有其他圖片選擇程序的話,可以繼續使用郵箱默認的,如果有,你可以任意選擇來替代原有模塊完整這功能,一切都自然而然。但這種模式,也不是沒有成本,需要付出的是一些性能上的開銷,因為畢竟有一個檢索過程。於是,Android提供了另一種模式,叫做Explicit Intents,就需要Component的幫助了。Component就是類名,完整的,形如com.xxxxx.xxxx,一旦指明了,一切都清晰了,找的到這個類(當然會是一個特定的子類...),成功,反之,失敗。這個好處,自然是速度,適合在你明確知道這就是一個內部模塊的時候,使用它。
- Extras。通過上面的這些項,識別問題,基本完美解決了,剩下一個重要的問題,就是傳參。Extras是用來做這個事情的,它是一個Bundle類的對象,有一組可序列化的key/value對組成。每一個Action,都會有與之對應的key和value類型約定,發起Intent的時候,需要按照要求把Data不能表示的額外參數放入Extras中(當然,如果不需要額外附加參數,就算了...),否則執行者拿到的時候會抓狂的。
- Flags。能識別,有輸入,整個Intent基本就完整了,但還有一些附件的指令,需要放在Flags中帶過去。顧名思義,Flags是一個整形數,有一些列的標志位構成,這些標志,是用來指明運行模式的。比如,你期望這個意圖的執行者,和你運行在兩個完全不同的任務中(或說進程也無妨吧...),就需要設置FLAG_ACTIVITY_NEW_TASK的標志位。
有了上述這些,一個Intent的形象就躍然紙上了,如此豐富的內容,決定了它比傳統的模式,都來得強大。
Intent匹配
上次在moto dev上,聽人做Android的講座,下面有很多聽客都對Intent這個概念表示出了強烈的興趣,拿出自己熟悉領域的各類概念進行類比,比如事件、消息之類。當時我在想,Intent作為組件間的通信協定,與一般的簡單的通信方式不同,首先,從前面部分可以看到,它的描述是針對需求而不是實現者來進行的。其次,它的解析是依托第三方而不是兩方直接進行。
這個概念和設計模式中的中介模式(Mediator Pattern)是一脈的,即所有的外圍組件,都只和系統的核心模塊發生聯系,通過它進行中轉,組件之間不直接勾搭。
如上圖所示,要想跑通整個流程,另一個很重要的東西,就是
Intent Filters,它是用來描述一個Activity或Serveice等組件,期望能夠響應怎麼樣的Intent。如果一個組件,只希望別的組件通過Explicit Intents(就是指明Component...)的方式來找到它,那麼就不需要添加Intent Filters,反之,一定需要一個或若干個Intent Filters。Intent Filter的各個項,猶如Intent照鏡子過來的效果,包括Action,Catagory,Data,Type等。
Intent Filters可以寫到配置文件中,和那些組件的配置一起(不記得什麼是配置文件了,可以看這裡...),若干的實例可以在Intent介紹頁面上找到(
reference/android/content/Intent.html)。同樣,Intent Filters可以在代碼中,動態插拔,這個是和動態插拔的
Broadcast Receiver是配套使用的。
系統核心的模塊,會負責收集這些Intent Filters,和它們對應的組件信息。當請求者需要一個組件幫忙,並構造了描述它需求的Intent發送到系統核心,系統核心會將其與已知的各個 Intent Filters進行匹配,挑選一個符合需求的組件返回。如果有多個符合的,會嘗試看看有沒有默認執行的,如果沒有默認的,就會構造UI,讓用戶幫助抉擇,如是,整個流程就跑通了。
Intent實現
上圖,是請求一個Activity組件的簡單實現流程圖,算是用的最多的Intent解析實例。流程從調用
Context.startActivity(Intent)開始,調用者傳入構造好的Intent對象,然後流程會讓實際的執行者,是
Instrumentation對象來完成。它是整個應用激活的Activity管理者,集中負責該應用內所有Activity的起承轉合生離死別。它有一個隱藏的方法
execStartActivity方法,就是負責根據Intent啟動Activity的。去掉一些細節,它做得最重要的事情,就是將此調用,通過RPC的方式,傳遞到
ActivityManagerService。
前面一直再說,
系統核心層,其實這裡所謂的系統核心層,就是負責Android一些關鍵事務的
一組服務。它們同樣運行在虛擬機上,和普通的Service實現機理是一致的,只不過它們不拋頭露臉只是默默的在下層服務,故謂之核心嘛。AcitivityManagerService,是負責Activity調度的服務,也許日後提及調度細節的時候還會有涉及。
在這裡,AcitivityManagerService會分兩個步驟完成相關操作,首先把Intent遞交給另一個服務
PackageManagerService,此服務掌握整個軟件包及其各組件的信息,它會將傳遞過來的Intent,與已知的所有Intent Filters進行匹配(如果帶有Component信息,就不用比了...),找到了,就把相關Component的信息通知回 AcitivityManagerService,在這裡,會完成啟動Activity這個很多細節的事情。
由此可知,啟動Activity,要經過多個服務的處理,並不是非常輕量的過程,在Android隨機文檔介紹性能的一節中,對此有一個評估。但這樣的操作不是會放在循環裡反復折磨的那種,因此整體效果與其付出的性能代價相比,覺得是物超所值的。