深刻android

好東西, 寫的挺幽默的,轉載 php

 

深刻Android

【一】 —— 序及開篇

對於Android,我也算是老人了,所謂,有文有真想。正因爲這段玩票經歷,使得我在畢業後,鬼使神差的成爲移動平臺的一名碼工,再次有機會放肆的擁抱Android。
2010開年,手上忽然有了一把閒散時間,有機會進一步總結和學習Android。因而想再一次爲Android寫一系列的東西,這些東西來自於一些開發經驗,對源碼的學習和對Android的淺薄認識,也算是鞭笞本身學習的一種手段。
其下全部內容,預計有十數篇,抑或更多。基本和技術相關,也許會配有一些其餘相關比較閒淡的話題。可能會有一些具象實例,但更多的多是本身的一些理解和認知。全部一切,源自於妄自挖掘,不免有疏漏或誤解,觀者淡定。
以此爲序,有心者,望共勉。


Android簡史
人生若只初識,何事秋風悲畫扇。 -- 《木蘭辭》

要說當今移動平臺的當紅辣子雞,Android說它是第二,也許沒有別家敢認這個第一(好吧,iPhone,有意見就說...)。瞭解Android開發平臺的過去和現狀,除了往下看,另有便捷的方式就是在WikiPedia中鍵入Android,在這裏,特此鳴謝GFW友情放生。

誕生

早在2005年7月,Google舞動着手中的美刀,收下了由Andy Rubin(傳說中的Android之父...)等人創立的一家小公司,他們當時作的就是基於Linux內核的手機操做系統,也就是小時候的Android。通過Google多年打磨,Android在07年底,正二八經的粉墨登場開門接客。
自打一出生,Android便被釘板在富二代的角色上,不只是由於老爹有錢的使人髮指,也是由於其後有一幫金光閃耀的叔伯們保駕護航。這個叔伯羣,即是響噹噹的開放手機聯盟OHA(Open Handset Alliance)。這個聯盟涵蓋了中國移動、T-Mobile、Sprint這樣的移動運營商,也包括HTC、Motolora、三星這些的手機制造商,同時還有以Google爲表明的手機軟件商,以Inter、Nvidia爲標誌的底層硬件廠商和Astonishing Tribe這樣的商業運做公司(這公司是啥我也不曉得...)。做爲後援團,他們理論上的任務,是各盡其長,全力捧紅Android,實際上的任務是齊心合力,借Android東風賺一個盆鉢滿響。
固然,Android自因此被萬衆矚目一炮走紅,不只僅是丫實在太有背景,同時,它也有這太多的新鮮的概念。Android是一個開源的平臺(恩,真正的全面開源,是在發佈後好久之後了...),它給那些捂着自家平臺源碼當寶的競爭對手們一記當頭棒喝。Android自行研發了一套Java虛擬機,當時僅提供Java API的支持(NDK是更久之後的事情了...),號稱爲專爲高端智能設計。Android開發環境支持全部主流操做系統平臺,包括Windows,Linux,Mac,即使到今天,在手機開發中也是極其罕見的。Android的帶頭人Google,自己是作網絡起家,Android內嵌大量Google網絡應用,聽上去就顯得很酷。這全部的一切,共同造就了Android那鶴立雞羣,不染風塵的少俠形象。

造勢

推出伊始,Google還有一個很震撼的推廣舉動,就是舉行所謂的Android程序挑戰賽(Android Developer Challenge, ADC)。整個比賽分紅兩場,第一場(ADC1)比賽,在沒有任何真機問世,SDK仍是個雛形的情況下,便鳴金開鑼了。
比賽套路是無差異的羣毆,基本概念是不管你來自何方(仍是要知足美國法律要求和避嫌要求的),不管你想作些什麼,也無論你是光桿司令仍是流氓團伙,只要提交一個能在模擬器上跑起來的程序,就可參賽。而比賽只是對你做品進行參觀評比,做品的全部權依然放在開發者的口袋中。
固然,這還不算什麼創新,NB的是無比豐厚的獎金,整個ADC的獎金高達1千萬美刀,每場各半,基本上首輪入圍獎(前50)已經超越了那時候通常程序比賽的頭名獎金,這對不少小公司和我的而言,無疑是具備很強吸引力的。因而,各路打醬油好手蜂擁而至,各論壇、博客、網站也七嘴八舌的討論開來,一時間,滿城風雨。
ADC1我也很厚顏無恥的參加了,結果固然能夠預想,一毛錢都沒摸上。回想整個過程,差距最大的並非在技術上,而是認知上,咱們玩的產品是人家幾年前玩剩下的,說創新只是一抹笑談。
當時覺着,Google太NB了,ADC這種車馬未動糧草先行的招太華麗了,就這動靜,不論比出來個啥結果,這1千萬刀也掏值了。但時過境遷,如今回頭來想,也許一切並非看上去那麼美。因爲沒有紮紮實實的真機擺出來,你們廣泛抱着一種玩票的心理,真的敢不顧一切舍下身家性命押寶在Android上的盡在少數,這就鍛造了Android平臺很長一段時間的只見雷不見雨的局面。而等喧囂事後,不少人熱情消退,Google真端出Android真機的時候,還須要從新熱場再來一次,也許,真的有些得不償失。

困境

全部的東西如今來講,都是過後諸葛亮,只能聽個響不能當個真。而真實的情況是ADC1很快進入囧境,因爲架構設計上的種種緣由,Google花了比預想多的多的時間作Android的優化工做,ADC1比賽被迫不斷延期,完全淪爲懶婆娘的裹腳布。各路曾熱捧Android的媒體,也不失時機的倒戈,親手在本身畫上的感嘆號後面,重重畫上了個大大的問號。
禍不單行,一樣是因爲Android的性能問題,雖然各路高手把Android移植到了不一樣的手機平臺上,但傳說中的GPhone一直難產中,使得人們難免有了胎死腹中的猜想。
與此同時,其餘對手可一點也沒閒着。iPhone很快宣佈開放SDK,以此來勾引純情的開發人員。Symbian被Nokia完全收購,成爲Nokia的自留地,開源計劃也很快浮出水面。全部景象,對Android而言都猶如夢魘。

破繭

全部一切困境,都在G1發佈後,漸漸消散了。2008年10月,第一款搭配Android平臺的真機,搭載着無限光榮與夢想的HTC Dream正式發售,這就是註定要載入史冊的G1。雖然比之當時絕代風華的iPhone,粗陋的G1猶如村姑趕上公主通常,但不管如何,G1讓人們真真切切的看到了Android。這就猶如你家買的跳票N久的期房,終於見着了個毛胚房,那種感受,除了踏實,找不到更適合的詞彙了。
好事固然也會成雙,G1它不是一我的在戰鬥。ADC1總算是落下帷幕,Android Market的也瓜熟蒂落的破繭而出,早期的應用,大都來自於ADC1的貢獻。
Android也結束了僞開源的歷史旅程,正式開發SDK的源碼實現,搭配的是Apache的License,這種坦誠相見的感受看上去很不錯。
忠心耿耿的HTC,更是再接再勵,在G1後,陸續發佈了Magic(G2)和Hero(G3)。尤爲是Hero的現身,惹得一陣小驚豔,HTC爲Hero搭配的是基於Android改造UI的Sense系統,以華麗的界面風格賺足了眼球,也創了改造Android的先河。
在HTC高歌猛進的同時,貓在螳螂後的一羣黃雀,也敵在動我也動了。摩托羅拉,三星,LG,華爲,戴爾,聯想等一干手機廠商紛紛跟進,各式各樣的Android蜂擁而至。與此同時,其餘嵌入式廠商也推陳出新,愛可視(Archos)發佈了基於Android的平板設備,明基的Android上網本也是箭在弦上,而基於Android的手持電子書閱讀設備也不斷的被推出,龐大的Android聯盟初現崢嶸。
爲了不同質化,各個廠商紛紛對Android進行的改造,摩托羅拉推出了Cliq,打得是SNS整合牌,三星的新系統也是被普遍期待,而中移動的OMS醜媳婦也要見婆娘了,打着整合移動服務牌@_@的OMS,以醜陋的外貌、低下的SDK版本和雷死人不償命的宣傳文案(絕口不提Android,只說本身作了大量很NB的工做,其實...,哎,咋就那麼小家子氣呢...)也算是招來大量眼球。
而仍是沒能耐住寂寞的Google,聯手HTC,一同推出了至今只爲止最重量級的Android手機:Nexus One。江湖有云:天下武功,無快不破。搭載了全新的Android 2.1,1G的CPU,史上最清晰的手機屏幕的Nexus One,快的是一塌糊塗迅雷不掩耳盜鈴小叮噹,在單機層面,第一次使得Android手機與iPhone掰手腕的能力(以前與iPhone的比較,都須要依靠集團力量,三英戰呂布...)。
在各家廠商努力的同時,Android自己也沒有閒着,版本從1.1,一步步進階到了2.1,SDK的升級,伴隨着大量性能、接口的改進,和功能的豐富,Android變得愈來愈快,愈來愈省電,愈來愈豐富,愈來愈多Google服務被嵌入@_@。因爲Android SDK是基於Java的,即使虛擬機作的非常NB,在某些狀況下,性能也是沒法與原生的C++代碼相提並論,因而,從1.5版起,除了SDK,Android還擁有了NDK(Native Develop Kit),它提供了一些C++的庫和編譯環境(庫是真的不多...),開發人員能夠基於C++寫底層庫,用Java寫上層邏輯,經過混編的方式,兼得魚和熊掌。
Android Market的發展也甚爲迅猛,雖然和其鼻祖App Store相比,應用的規模和盈利能力還顯得比較幼齒,但其漲勢兇猛,發展趨勢遠勝於前輩。國內一些比較著名的手機軟件,也紛紛擁有了Android版本的小弟,好比網易有道的出品的有道詞典Android版(好吧,這是在插播廣告,歡迎你們進行圍觀並提寶貴意見...)。

起飛

種種跡象代表,2010,也許就是姍姍來遲的Android元年。三星,moto,LG,HTC等多家手機制造廠商,都爲2010年將推出的半數以上的手機搭配了Android。在國內,移動的OPhone,醜媳婦要正式揭開蓋頭了,慘烈是慘烈了一點,但聊勝於無,除了水貨,2010畢竟至少多了條購買Android手機的道路。
軟件開發方面,你們也從抱着雙臂冷眼旁觀的狀態,進入到了一種乘機而動的戰略準備階段。前不久召開的moto開發者大會,驚現國內各領域的公司,試水開始,可見一斑。國內各個山寨的Market的,也愈來愈貨源充足,下載量穩步上升,升溫,就在當下。
而隨着G2爲首的Android水機價格火速下調,身邊路邊地鐵邊,能夠看到愈來愈多的人,把玩着各式各樣的Android手機,情況尤其喜人。

因此,2010,若是你有心,就作好準備吧。
 html

【二】 —— 架構和學習

雖貴爲富二代,但Android要是沒任何可圈點的地方,開不過70邁,在玲琅滿目的手機平臺競爭中,充其量也就作幾個俯臥撐打一桶醬油,而後被落的遠遠的。說到底,出來混,靠的仍是技術。

架構



從SDK文檔中,偷來一幅Android平臺的架構圖,如上。在整個架構最底層紅彤彤的部分,是Linux Kernel在移動平臺的一個移植,它隱藏了硬件、網絡等相關的細節,爲上層提供了一個相對純潔的統一接口。除非要作的是Android到不一樣設備的移植工做,不然對於大部分普通開發者而言,基本上是遠觀而沒必要褻玩的。Google一直強調,Android的底層實現異常NB,可移植性超強,暫沒有功夫研讀,實屬遺憾。
靠上一層,是一些核心的和擴展的類庫,它們都是原生的C++實現。在這一層,你能夠看到不少熟悉的面孔,一如SQLite、WebKit、OpenGL,開源的力量與貢獻因而可知。若是,該層類庫須要被上層函數調用,就必需要經過JNI的導出相應的接口函數,不然就只能在層次內部自個把玩。
也是在這一層次上,還有爲上層Java程序服務的運行時。Dalvik虛擬機,是Android的Java虛擬機,之因此不採用J2ME的虛擬機,一方面是由於J2ME的設計是爲了低端機器而優化,而Dalvik則是爲了高端一些的機器進行優化,提供更好的性能。另外一方面,從商業角度來看,必須繞開J2ME虛擬機,Android才能完全解放,想怎麼開源就怎麼開源,再也不須要考慮License的問題。
再往上,終於有Java出沒了。首先是框架層,這裏包含全部開發所用的SDK類庫,另外還有一些未公開接口的類庫和實現,它們是整個Android平臺核心機制的體現。
而在最上面,就是應用層了,系統的一些應用和第三方開發的全部應用都是位於這個層次上,也許要糾結二者的差異,就是系統應用會用一些隱藏的類,而第三方的應用,老是基於SDK提供的東西來搞。
通常來講,Android開發,就是在SDK的基礎上,吭哧吭哧用Java寫應用。但自從有了NDK,一切有了寫小變化。NDK的出現意味着,最上面應用層的內容,能夠穿越Java部署的框架層,直接和底層暴露出來的,或者自行開發的C++庫直接對話,固然在這些庫中須要包含JNI的接口。
人說,這就不是Android也能夠用C++開發應用麼,但其實,這樣的說法不夠確切,純C++應用,是沒法被接受的。由於在Android中,大量的核心機制部署在框架層,它們都是用Java實現的,好比控件庫,Activity的調度之類的。所以,沒了界面,沒了調度,仍是隻用C++作類庫比較合適,不然一切都亂了套了。

特徵

基於這樣的架構,Android有不少的設計顯得頗有意思。縱覽整個SDK和核心機制的設計,工整漂亮,是Android給人的第一感受。爲了說明這一點,找一個反面教材是頗有必要的,Symbian同窗毫無懸念的擔當這個偉岸的角色。
寫Symbian程序,感受就像是在玩一個猜謎遊戲。哪怕你是一個Symbian老手,當須要用到Symbian中某塊陌生功能的時候,你可能依然一籌莫展。你每每須要猜並反覆找尋,在這裏我須要使用哪種奇巧淫技呢,是該臆想某些事件,仍是應該用一個神祕的UID尋找某個特定應用,諸如此類。
而作Android應用的時候,就像是作高考模擬試題,題看上去不同,解答模式摸清楚,就一通百通,一了百了。監聽某個系統事件,查一下SDK就好;訪問某個應用的數據,看看它有沒有提供Content Provider就能夠。全部的一切,都是按套路出牌,只要你瞭解了套路,再陌生的牌也能夠看得懂,出的順。人說武林高手,都應該是無招勝有招,而一個好的應用框架,也應該作到舉重若輕,可舉一反三。
而Android框架最文采飛揚的一點,就是引入了Mash-Up的思想。所謂Mash-Up,就是把寫應用搞成搭積木,要出效果的時候,東家一塊西家一塊現場拼起來就好。這裏面關鍵有兩點,一個是模塊化,另外一個就是動態性。所謂模塊化,就是一個應用的功能要明確的被封成一個個邊界清晰的功能點,每個功能點都像是一個黑盒,由預先定義的規則描述出其交互方式;而動態性,就是這些獨立的模塊可以在運行的時候,按照需求描述,鏈接在一塊兒,共同完成某項更大的功能。在這兩點上,Android都作得很是出色。
站在可Mash-Up構造應用這一點去看其餘的一些Android中的核心功能設計,就顯得頗有章可循了。好比爲何要把文件私有化,爲何要讓進程被託管,等等(固然也能夠站在別的角度看出不一樣的效果,視角不一樣,視野天然不一樣...)。
在UI機制方面,Android也有很不錯的表現。它採起xml格式的資源文件,描述全部界面相關的內容。資源文件不是什麼新東西了,xml格式也是老調重彈,但難得的是Android作的更爲的豐富和完全,基本把界面相關的邏輯,所有從代碼中剝離到了資源文件中,和Symbian那四不像的資源文件相比,真是強大了不知多少倍了。

Android學習入門
不積跬步,無以致千里。 -- 《勸學》
說,萬事開頭難。想開始Android的開發,最重要的應該是先把馬步扎穩,套路摸清楚,後面的事情就順當多了。打開懷抱,擁抱Android,也許能夠先作下面這些事。

開發環境

辣手摧花成性的GFW,無情的把Android開發者官網關在了牆外。不過不要緊,猛擊這裏,一樣能夠異曲同工(Shit,一直在用代理,剛試了下,發現居然也被盾了,若是不行,那就只能FQ了...)。
若是旅途順利,你能夠在路徑sdk/index.html下找到安裝說明,成功配置好Android的開發環境(【注】:在之後,若是要給開發者頁面上的連接,都會給一個像sdk/index.html這樣的相對路徑,你能夠在前面加上官網地址,或者本地SDK的doc地址拼湊成完整的路徑,在一個盾牌橫行的朝代,只能用這樣委屈求全的方法保證能更好的使用...)。
在2.0以前,每一次版本更新,你都要本身去下個全新的SDK,而後按照說明,當心翼翼的一步步修改eclipse的設置,甚是麻煩。在2.0後,這個模式有所改善,你會先下到一個相似於下載器的插件,經過它能夠來管理和升級SDK,不只簡化了整個升級模式,還使得你能夠更好的在各個不一樣的SDK版本間遊走,利國利民。
Eclipse + ADT(Android Development Tool),是正牌的Android開發環境。你能夠在Windows,Linux,Mac下作開發,甚爲自由。比之Symbian的開發環境,ADT顯得尤其強大,它對SDK提供的一堆優秀的命令行工具進行了UI上的封裝,提供了圖形界面(命令行控固然一樣幸福,具體參見:guide/developing/tools/index.html)。經過ADT,你能夠用運行和管理模擬器,使用調試器進行調試,過濾和查看Log,瀏覽模擬器上的文件信息,模擬撥號、短信等手機纔有的事件,等等。

文檔

我知道,有不少人在學習一個新平臺開發的時候,都習慣去買一些《xxx 21天精通》之類的書籍。但其實,最好的入門學習資料,就是SDK文檔。由於只有作平臺的本身,才能最瞭解平臺中的各個玄機,各方面的輕重緩急,從而可以更好的對症下藥藥到病除。
在Android的SDK中,guide/index.html是由淺入深的教學文檔,reference/packages.html是標準的API文檔。對於教學文檔,個人意見是,一字不拉的通讀一遍甚至多遍,至少作到能對Android摸着頭腦,而且碰到問題的時候,可以快速想起在哪裏能夠找到,回來深入閱讀。
而API文檔方面,Android作的算是還不錯了。基本上每一個類,每一個接口,都有標準而詳盡的說明,在一些尤其重要的類中,還具備大量的學習性的內容,不和Symbian似的,有太多的太監類,只有光禿禿的一個函數,一行文檔說明都沒有。整個文檔結構是按照Java包來組織的,自己Java包命名的結構性和可讀性很強,找起來也頗爲方便。
不少人對SDK文檔有抵觸情緒,我想,有兩方面的緣由。一則是SDK文檔廣泛缺乏文學性,麻木不仁的八股文,難如下嚥。Android在這方面作得算是乏善可陳,雖然算不上文采華麗,但仍是挺適合閱讀的。另外一則,就會是語言方面的緣由了。SDK文檔多爲英語,偶爾像MSDN這樣有中文的,也停留在機器翻譯的水平上,閱讀起來頗爲難受。特意在網上搜了下,找到一些翻譯SDK的中文文檔,好比這裏。雖然是基於1.5 r1版本SDK所著,稍顯過期,但翻譯的仍是有小用心的,做爲輔助,也不失爲一份好資料,特表明廣大看官向這些爲人民福利着想的同志致敬。

Tutorials

光說不練假把式,除了讀,在入門階段,寫也是一項不能少的運動。一樣是在SDK中,Android提供了一組Tutorials和一些列的Samples,詳見:resources/index.html。
Tutorials很簡單,Hello World只是在教你如何在eclipse中,在ADT的幫助下,建立一個Android項目。相比之下,Hello Views複雜了些,它集中展現了幾種標準的Android Layout 樣式是如何構建的,不少時候,你都是在這些樣式下擴展所需的UI。
Hello Localization,是教你如何使用資源的,作完這個,就能夠了解Android的資源有多殺~。最後收官的是一個更爲完整的Notepad Tutorial,它展現了不少Android的核心機制,好比基於Intent的Activity整合,Activity的生命週期等。邁過這個Tutorial,歡迎你,進入Android的大門。
固然,作完Tutorials,對於Android而言,只是管中窺豹略見一斑。在SDK中,還提供了一系列的Samples。能夠根據本身的需求,挑選合適的Sample編譯運行和學習。但其中,有一個是不論你作什麼,都須要必看必讀必熟悉的,就是API Demos。在這個Sample中,集中展現了Android重點功能的API使用,把這個Sample用熟悉,須要作什麼的時候過去找一下就能夠很快的入手了。

源碼

到這裏,不少看官必定很不屑,前面所謂的學習入門介紹,只不過是圍着SDK打轉。其實,事實也是如此,SDK中包含的內容是真的很是重要,我只是指望經過一些簡短的介紹,激起一些初學者的重視,如是而已。
固然,SDK每個平臺都有,沒什麼稀罕的。但Android有另外一個很是稀罕而值錢的看家法寶,就是源代碼。從Android Source的主站上: http://source.android.com/,你能夠得到整個平臺的源碼以及相關介紹。很是苦口婆心的指望你們都去down一份源碼放在機器上,哪怕你不須要進行修改編譯,放在機器上當百科全書也是遠勝於任何一本Android教學書籍的。本系列文章後續不少內容,都是從源碼中學習到的一些淺薄見識。
對於大部分開發者都有學習價值的源碼,主要在源碼的frameworks和packages目錄下。前者包含的是平臺核心的一些實現,好比你須要自定義一個控件,也許你就能夠翻到一個系統控件的實現中去,看看它是怎麼來作的。後者包含一些系統的應用實現,好比你想作個播放器,也許你能夠先去參考參考系統自帶的是具體怎麼作的。這樣的實現,即使不算是最華美,至少也是最標準,其價值不容小視。
另外,你也能夠把它當個代碼庫來使,不會使用某個類,grep一把,也許就能得到一份最漂亮的Sample。固然,若是你有時候對某些系統機制表示費解,抑或有一些bug不知道源頭在哪,均可以跟着源碼順藤摸瓜的搞清楚。這樣的好東西,可不是每一個平臺都可以享用的。

其餘

論壇,其實對於開發和學習都是很重要的資源。畢竟,全部的資料都是死的,只有人是活得,可以最大限度的因地制宜解決問題。
只不過,標準的官方論壇,放在Google Group上,已經惆悵的被盾了。中文論壇方面,沒有特別優秀和活躍的,這一方面是因爲Android的發展示狀還不算很磅礴,另外一方面是因爲Android的開發相對於Symbian而言,奇技淫巧少了不少,沒有那麼多好問的。也許你能夠去去csdn這樣的傳統論壇,或者eoe這樣專門的論壇。有的時候,仍是多少能得到一些幫助的。
書籍方面,真沒有什麼推薦,豆瓣上搜索一下,你能夠看到,目前的書籍,基本上仍是集中在SDK使用層面上,不多有解析的很透徹,作的很深刻的。而SDK的使用,看SDK的文檔就足夠了,若是實在對e文不感冒,買一兩本評價不太差的中文書籍,放着翻翻也仍是挺好。
更進一步,也許能夠讀讀一些經驗性的文檔,去Google Code上搜索一些代碼回來看看。好比,SDK文檔中,有個經驗性文檔的集合:resources/articles/index.html,就能夠翻看一下。

最後,更多的一切還須要本身在工程和思考中,慢慢總結。相信,好的代碼,會垂青一個勤於動手和思考的人。

 




【三】 —— 組件入門
組件(Component),在談及所謂架構和重用的時候,是一個重要的事情。不少時候都會說基於組件的軟件架構,指的是指望把程序作樂高似的,有一堆接口標準封裝完整的組件放在哪裏,想用的時候取上幾個一搭配,整個程序就構建完成了。
在開篇的時候就在說,Android是一個爲組件化而搭建的平臺,它引入所謂Mash-Up的概念,這使得你在應用的最上層,想作的不組件化都是很困難的一件事情(底層邏輯,好吧,管不了...)。具體說來,Android有四大組件四喜丸子:Activity、Service、Broadcast Receiver、Content Provider。

Activity

作一個完整的Android程序,不想用到Activity,真的是比較困難的一件事情,除非是想作綠葉想瘋了。由於Activity是Android程序與用戶交互的窗口,在我看來,從這個層面的視角來看,Android的Activity特像網站的頁面。
首先,一個網站,若是一張頁面都沒有,那...,真是一顆奇葩。而一張頁面每每都有個獨立的主題和功能點,好比登陸頁面,註冊頁面,管理頁面,如是。
在每一個頁面裏面,會放一些連接,已實現功能點的串聯,有的連接點了,刷,跑到同一站點的另外一個頁面去了;有的連接點了,啾,可能跳到其餘網站的頁面去;還有的連接點了,恩...,此次沒跑,但當前頁面的樣子可能有所變化了。這些模式,和Activity給人的感受很像,只不過實現策略不一樣罷了,畢竟Android這套架構的核心思想,自己就來自源於Web的Mash-Up概念,視爲頁面的客戶端化,也何嘗不可。
Activity,在四大組件中,無疑是最複雜的,這年頭,同樣東西和界面掛上了勾,都簡化不了,想想,獨立作一個應用有多少時間淪落在了界面上,就能琢磨清楚了。從視覺效果來看,一個Activity佔據當前的窗口,響應全部窗口事件,具有有控件,菜單等界面元素。從內部邏輯來看,Activity須要爲了保持各個界面狀態,須要作不少持久化的事情,還須要妥善管理生命週期,和一些轉跳邏輯。對於開發者而言,就須要派生一個Activity的子類,而後埋頭苦幹上述事情。對於Activity的更多細節,先能夠參見:reference/android/app/Activity.html。後續,會獻上更爲詳盡的剖析。

Service

服務,從最直白的視角來看,就是剝離了界面的Activity,它們在不少Android的概念方面比較接近,都是封裝有一個完整的功能邏輯實現,只不過Service不拋頭露臉,只是默默無聲的作堅實的後盾。
但其實,換個角度來看,Android中的服務,和咱們一般說的Windows服務,Web的後臺服務又有一些相近,它們一般都是後臺長時間運行,接受上層指令,完成相關事務的模塊。用運行模式來看,Activity是跳,從一個跳到一個,呃...,這有點像模態對話框(或者還像web頁面好了...),給一個輸入(抑或沒有...),而後無論不顧的讓它運行,離開時返回輸出(同抑或沒有...)。
而Service不是,它是等,等着上層鏈接上它,而後產生一段持久而纏綿的通訊,這就像一個用了Ajax頁面,看着沒啥變化,偷偷摸摸的和Service不知眉來眼去多少回了。
但和通常的Service仍是有所不一樣,Android的Service和全部四大組件同樣,其進程模型都是能夠配置的,調用方和發佈方均可以有權利來選擇是把這個組件運行在同一個進程下,仍是不一樣的進程下。這句話,能夠拿把指甲刀刻進腦海中去,它凸顯了Android的運行特徵。若是一個Service,是有指望運行在於調用方不一樣進程的時候,就須要利用Android提供的RPC機制,爲其部署一套進程間通訊的策略。



Android的RPC實現,如上圖所示(好吧,也是從SDK中拿來主義的...),無甚稀奇,基於代理模式的一個實現,在調用端和服務端都去生成一個代理類,作一些序列化和反序列化的事情,使得調用端和服務器端均可以像調用一個本地接口同樣使用RPC接口。
Android中用來作數據序列化的類是Parcel,參見:/reference/android/os/Parcel.html,封裝了序列化的細節,向外提供了足夠對象化的訪問接口,Android號稱實現很是高效。
還有就是AIDL (Android Interface Definition Language) ,一種接口定義的語言,服務的RPC接口,能夠用AIDL來描述,這樣,ADT就能夠幫助你自動生成一整套的代理模式須要用到的類,都是想起來很乏力寫起來很苦力的那種。更多內容,能夠再看看:guide/developing/tools/aidl.html,若是有興致,能夠找些其餘PRC實現的資料lou幾眼。
關於Service的實現,還強推參看API Demos這個Sample裏面的RemoteService實現。它完整的展現了實現一個Service須要作的事情:那就是定義好須要接受的Intent,提供同步或異步的接口,在上層綁定了它後,經過這些接口(不少時候都是RPC的...)進行通訊。在RPC接口中使用的數據、回調接口對象,若是不是標準的系統實現(系統可序列化的),則須要自定義aidl,全部一切,在這個Sample裏都有表達,強薦。
Service從實現角度看,最特別的就是這些RPC的實現了,其餘內容,都會接近於Activity的一些實現,也許再也不會詳述了。

Broadcast Receiver

在實際應用中,咱們常須要等,等待系統抑或其餘應用發出一道指令,爲本身的應用擦亮明燈指明方向。而這種等待,在不少的平臺上,都會須要付出不小的代價。
好比,在Symbian中,你要等待一個來電消息,顯示歸屬地之類的,必須讓本身的應用忍辱負重偷偷摸摸的開機啓動,消隱圖標隱藏任務項,潛伏在後臺,監控着相關事件,等待轉瞬即逝的出手機會。這是一件很髮指的事情,不但白白耗費了系統資源,還留了個流氓軟件的罵名,這真是賣力不討好的正面典型。
在Android中,充分考慮了普遍的這類需求,因而就有了Broadcast Receiver這樣的一個組件。每一個Broadcast Receiver均可以接收一種或若干種Intent做爲觸發事件(有不知道Intent的麼,後面會知道了...),當發生這樣事件的時候,系統會負責喚醒或傳遞消息到該Broadcast Receiver,任其處置。在此以前和這之後,Broadcast Receiver是否在運行都變得不重要了,及其綠色環保。
這個實現機制,顯然是基於一種註冊方式的,Broadcast Receiver將其特徵描述並註冊在系統中,根據註冊時機,能夠分爲兩類,被我冠名爲冷熱插拔。所謂冷插拔,就是Broadcast Receiver的相關信息寫在配置文件中(求配置文件詳情?稍安,後續奉上...),系統會負責在相關事件發生的時候及時通知到該Broadcast Receiver,這種模式適合於這樣的場景。某事件方式 -> 通知Broadcast -> 啓動相關處理應用。好比,監聽來電、郵件、短信之類的,都隸屬於這種模式。而熱插拔,顧名思義,插拔這樣的事情,都是由應用本身來處理的,一般是在OnResume事件中經過registerReceiver進行註冊,在OnPause等事件中反註冊,經過這種方式使其可以在運行期間保持對相關事件的關注。好比,一款優秀的詞典軟件(好比,有道詞典...),可能會有在運行期間關注網絡情況變化的需求,使其能夠在有廉價網絡的時候優先使用網絡查詢詞彙,在其餘狀況下,首先經過本地詞庫來查詞,從而兼顧腰包和體驗,一箭雙鵰一石二鳥一舉兩得(注,真實在有道詞典中有這樣的能力,但不是經過Broadcast Receiver實現的,僅覺得例...)。而這樣的監聽,只須要在其工做狀態下保持就好,不運行的時候,管你是天大的網路變化,與我何干。其模式能夠歸結爲:啓動應用 -> 監聽事件 -> 發生時進行處理。
除了接受消息的一方有多種模式,發送者也有很重要的選擇權。一般,發送這有兩類,一個就是系統自己,咱們稱之爲系統Broadcast消息,在reference/android/content/Intent.html的Standard Broadcast Actions,能夠求到相關消息的詳情。除了系統,自定義的應用能夠放出Broadcast消息,經過的接口能夠是Context.sendBroadcast,抑或是Context.sendOrderedBroadcast。前者發出的稱爲Normal broadcast,全部關注該消息的Receiver,都有機會得到並進行處理;後者放出的稱做Ordered broadcasts,顧名思義,接受者須要按資排輩,排在後面的只能吃前面吃剩下的,前面的心情很差私吞了,後面的只能喝西北風了。
當Broadcast Receiver接收到相關的消息,它們一般作一些簡單的處理,而後轉化稱爲一條Notification,一次振鈴,一次震動,抑或是啓動一個Activity進行進一步的交互和處理。因此,雖然Broadcast整個邏輯不復雜,倒是足夠有用和好用,它統一了Android的事件廣播模型,讓不少平臺都相形見絀了。更多Broadcast Receiver相關內容,參見:/reference/android/content/BroadcastReceiver.html。

Content Provider

Content Provider,聽着就和數據相關,沒錯,這就是Android提供的第三方應用數據的訪問方案。在Android中,對數據的保護是很嚴密的,除了放在SD卡中的數據,一個應用所持有的數據庫、文件、等等內容,都是不容許其餘直接訪問的,但有時候,溝通是必要的,不只對第三方很重要,對應用本身也很重要。
好比,一個聯繫人管理的應用。若是不容許第三方的應用對其聯繫人數據庫進行增刪該查,整個應用就失去了可擴展力,必將被其餘應用拋棄,而後另立門戶,自個玩自個的去了。
Andorid固然不會真的把每一個應用都作成一座孤島,它爲全部應用都準備了一扇窗,這就是Content Provider。應用想對外提供的數據,能夠經過派生ContentProvider類,封裝成一枚Content Provider,每一個Content Provider都用一個uri做爲獨立的標識,形如:content://com.xxxxx。全部東西看着像REST的樣子,但實際上,它比REST更爲靈活。和REST相似,uri也能夠有兩種類型,一種是帶id的,另外一種是列表的,但實現者不須要按照這個模式來作,給你id的uri你也能夠返回列表類型的數據,只要調用者明白,就無妨,不用苛求所謂的REST。
另外,Content Provider不和REST同樣只有uri可用,還能夠接受Projection,Selection,OrderBy等參數,這樣,就能夠像數據庫那樣進行投影,選擇和排序。查詢到的結果,以Cursor(參見:reference/android/database/Cursor.html )的形式進行返回,調用者能夠移動Cursor來訪問各列的數據。
Content Provider屏蔽了內部數據的存儲細節,向外提供了上述統一的接口模型,這樣的抽象層次,大大簡化了上層應用的書寫,也對數據的整合提供了更方便的途徑。Content Provider內部,經常使用數據庫來實現,Android提供了強大的Sqlite支持,但不少時候,你也能夠封裝文件或其餘混合的數據。
在Android中,ContentResolver是用來發起Content Provider的定位和訪問的。不過它僅提供了同步訪問的Content Provider的接口。但一般,Content Provider須要訪問的多是數據庫等大數據源,效率上不足夠快,會致使調用線程的擁塞。所以Android提供了一個AsyncQueryHandler(參見:reference/android/content/AsyncQueryHandler.html),幫助進行異步訪問Content Provider。
在各大組件中,Service和Content Provider都是那種須要持續訪問的。Service若是是一個耗時的場景,每每會提供異步訪問的接口,而Content Provider不論效率如何,都提供的是約定的同步訪問接口。我想這遵循的就是場景導向設計的原則,由於Content Provider僅是提供數據訪問的,它不能確信具體的使用場景如何,會怎樣使用它的數據;而相比之下,Service包含的邏輯更復雜更完整,能夠抉擇大部分時候使用某接口的場景,從而肯定最貼切的接口是同步仍是異步,簡化了上層調用的邏輯。

配置

四大組件說完了,四大組件幕後的英雄也該出場了,那就是每一個應用都會有一份的配置文件,名稱是AndroidManifest.xml,在工程的根目錄下。在這個配置文件中,不只會描述一些應用相關的信息,很重要的,會包含一個應用中全部組件的信息。若是你派生Activity或者Service實現了一個相關的類,這只是把它組件化的第一步,你須要把這個類的相關信息寫到配置文件中,它纔會做爲一個組件被應用到,不然只能默默無聞的黯淡度過餘生。



擺了一幅圖出來,此次不是偷來的,是敝帚自珍原創,因此沒有意外的畫的很醜,但基本仍是能夠體現出一些意思。在In Others的部分,這裏是通常平臺應用之間通訊和交互的模型,每一個應用都有很強烈的應用邊界(每每表現爲進程邊界...),App 1的仍是App 2的,分得非常清楚。每一個應用內部,都有本身的邏輯去切分功能組件,這樣的切分一般沒有什麼標準,率性而爲。應用間的交互邏輯也比較零散,App 1與App 2交互,每每須要明確知道對方應用的具體信息,好比進程ID,進程名稱之類的,這樣使得應用和應用之間的聯繫,變得很生硬。而上層應用和系統應用的通訊,每每有不少特定的模式,這種模式,極可能是沒法直接應用在普通應用之間的,換而言之,系統應用是有必定特殊性的。
重點,在圖的下半部,描述的是Android的應用情形。在Android中,應用的邊界,在組件這個層面,是極度模糊,什麼進程、什麼應用,均可以沒必要感知到。舉個例子,App 1,實現了A和B兩個組件,App 2,實現了C這個組件。A和C,都想使用B這個組件,那麼它們的使用方式是徹底一致的,都須要經過系統核心的組件識別和通訊機制,找到和使用組件B。A,雖然說和B是一個孃胎裏蹦出來的,很很差意思,沒有任何特殊的後面和捷徑,仍是要跑規矩的途徑才能用到,一片和諧社會的景象油然而生。
在Android中,全部組件的識別和消息傳遞邏輯都必須依賴底層核心來進行(通訊能夠沒有底層核心的參與,好比一旦Service找到了,就能夠和它產生持久的通訊...),沒有底層核心的牽線搭橋,任何兩個組件都沒法產生聯繫。好比一個Activity,跳到另外一個Activity,必需要向底層核心發起一個Intent,有底層解析並承認後,會找到另外一個Activity,把相關消息和數據傳給它。一個Activity想使用Content Provider中的數據,必須經過底層核心解析相關的uri,定位到這個Content Provider,把參數傳遞給它,而後返回Activity須要的Cursor。這樣的設計,保證了底層核心對全部組件的絕對掌控權和認知權,使得搭積木似的開發變成可能。
爲了,使得核心系統可以完整的掌握每一個組件的信息,這就須要配置文件了。配置文件,就是將組件插到底層核心上的這個插頭。只有經過這個插頭插在底層核心的插座上(不要亂想,非十八禁...),組件纔可以發光發熱,閃耀光芒。
組件的配置信息在我看來主要包含兩個方面,一部分是描述如何認知。好比,Activity、Service、Broadcast Receiver都會有名字信息,和但願可以把握的Intent信息(姑且當作消息好了...),Content Provider會有一個描述其身份的uri。當其餘組件經過這樣的名字或者Intent,就能夠找到它。
另外一部分是運行相關的信息。這個組件,指望怎麼來運行,放在單獨的進程,仍是和調用者一個進程,仍是找相關的其餘組件擠在同一個進程裏面,這些內容,均可以在配置的時候來決定(調用者在這個約束範圍內,有進一步的選擇權...)。更多配置項,請參見:guide/topics/manifest/manifest-intro.html。

經過前續內容,也許能夠幫助你們對Android組件有個初略的瞭解。但這些瞭解都還停留在靜態層面,程序是個動態的概念,關於各個組件具體是怎麼聯繫在一塊兒的,如何手拉手運行起來完成一項功能的,這即是後話了。

 

 

 




【四】 —— 組件調用

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隨機文檔介紹性能的一節中,對此有一個評估。但這樣的操做不是會放在循環裏反覆折磨的那種,所以總體效果與其付出的性能代價相比,以爲是物超所值的。

 

 

 




【五】 —— 任務和進程
關於Android中的組件和應用,以前涉及,大都是靜態的概念。而當一個應用運行起來,就不免會須要關心進程、線程這樣的概念。在Android中,組件的動態運行,有一個最不同凡響的概念,就是Task,翻譯成任務,應該仍是比較瓜熟蒂落的。
Task的介入,最主要的做用,是將組件之間的鏈接,從進程概念的細節中剝離出來,能夠以一種不一樣模型的東西進行配置,在不少時候,可以簡化上層開發人員的理解難度,幫助你們更好的進行開發和配置。

任務

在SDK中關於Task(guide/topics/fundamentals.html#acttask),有一個很好的比方,說,Task就至關於應用(application)的概念。在開發人員眼中,開發一個Android程序,是作一個個獨門獨戶的組件,但對於通常用戶而言,它們感知到的,只是一個運行起來的總體應用,這個總體背後,就是Task。
Task,簡單的說,就是一組以棧的模式彙集在一塊兒的Activity組件集合。它們有潛在的先後驅關聯,新加入的Activity組件,位於棧頂,並僅有在棧頂的Activity,纔會有機會與用戶進行交互。而當棧頂的Activity完成使命退出的時候,Task會將其退棧,並讓下一個將跑到棧頂的Activity來於用戶面對面,直至棧中再無更多Activity,Task結束。

事件        Task棧(粗體爲棧頂組件)
點開Email應用,進入收件箱(Activity A)        A
選中一封郵件,點擊查看詳情(Activity B)        AB
點擊回覆,開始寫新郵件(Activity C)        ABC
寫了幾行字,點擊選擇聯繫人,進入選擇聯繫人界面(Activity D)        ABCD
選擇好了聯繫人,繼續寫郵件        ABC
寫好郵件,發送完成,回到原始郵件        AB
點擊返回,回到收件箱        A
退出Email程序        null

如上表所示,是一個實例。從用戶從進入郵箱開始,到回覆完成,退出應用整個過程的Task棧變化。這是一個標準的棧模式,對於大部分的情況,這樣的Task模型,足以應付,可是,涉及到實際的性能、開銷等問題,就會變得殘酷許多。好比,啓動一個瀏覽器,在Android中是一個比較沉重的過程,它須要作不少初始化的工做,而且會有不小的內存開銷。但與此同時,用瀏覽器打開一些內容,又是通常應用都會有的一個需求。設想一下,若是同時有十個運行着的應用(就會對應着是多個Task),都須要啓動瀏覽器,這將是一個多麼殘酷的場面,十個Task棧都堆積着很雷同的瀏覽器Activity,是多麼華麗的一種浪費啊。因而你會有這樣一種設想,瀏覽器Activity,可不能夠做爲一個單獨的Task而存在,無論是來自那個Task的請求,瀏覽器的Task,都不會歸併過去。這樣,雖然瀏覽器Activity自己須要維繫的狀態更多了,但總體的開銷將大大的減小,這種舍小家爲你們的行爲,仍是很值得歌頌的。
如此值得歌頌的行爲,Android固然會舉雙手支持的。在Android中,每個Activity的Task模式,都是能夠由Activity提供方(經過配置文件...)和Activity使用方(經過Intent中的flag信息...)進行配置和選擇。固然,使用方對Activity的控制力,是限定在提供方容許的範疇內進行,提供方明令禁止的模式,使用方是不可以越界使用的。
在SDK中(guide/topics/fundamentals.html#acttask),將二者實現Task模式配置的方式,寫的很是清晰了,我再很絮叨挑選一些來解釋一下(完整可配置項,必定要看SDK,下面只是其中經常使用的若干項...)。提供方對組件的配置,是經過配置文件(Manifest)<activity>項來進行的,而調用方,則是經過Intent對象的flag進行抉擇的。相對於標準的Task棧的模式,配置的主要方向有兩個:一則是破壞已有棧的進出規則,或樣式;另外一則是開闢新Task棧完成本應在同一Task棧中完成的任務。
對於應用開發人員而言,<activity>中的launchMode屬性,是須要常常打交道的。它有四種模式:"standard", "singleTop", "singleTask", "singleInstance"。
standard模式, 是默認的也是標準的Task模式,在沒有其餘因素的影響下,使用此模式的Activity,會構造一個Activity的實例,加入到調用者的Task棧中去,對於使用頻度通常開銷通常什麼都通常的Activity而言,standard模式無疑是最合適的,由於它邏輯簡單條理清晰,因此是默認的選擇。
而singleTop模式,基本上於standard一致,僅在請求的Activity正好位於棧頂時,有所區別。此時,配置成singleTop的Activity,再也不會構造新的實例加入到Task棧中,而是將新來的Intent發送到棧頂Activity中,棧頂的Activity能夠經過重載onNewIntent來處理新的Intent(固然,也能夠無視...)。這個模式,下降了位於棧頂時的一些重複開銷,更避免了一些奇異的行爲(想象一下,若是在棧頂連續幾個都是一樣的Activity,再一級級退出的時候,這是怎麼樣的用戶體驗...),很適合一些會有更新的列表Activity展現。一個活生生的實例是,在Android默認提供的應用中,瀏覽器(Browser)的書籤Activity(BrowserBookmarkPage),就用的是singleTop。
singleTop模式,雖然破壞了原有棧的邏輯(複用了棧頂,而沒有構造新元素進棧...),但並未開闢專屬的Task。而singleTask,和singleInstance,則都採起的另闢Task的蹊徑。標誌爲singleTask的Activity,最多僅有一個實例存在,而且,位於以它爲根的Task中。全部對該Activity的請求,都會跳到該Activity的Task中展開進行。singleTask,很象概念中的單件模式,全部的修改都是基於一個實例,這一般用在構形成本很大,但切換成本較小的Activity中。在Android源碼提供的應用中,該模式被普遍的採用,最典型的例子,仍是瀏覽器應用的主Activity(名爲Browser...),它是展現當前tab,當前頁面內容的窗口。它的構形成本大,但頁面的切換仍是較快的,於singleTask相配,仍是挺天做之合的。
相比之下,singleInstance顯得更爲極端一些。在大部分時候singleInstance與singleTask徹底一致,惟一的不一樣在於,singleInstance的Activity,是它所在棧中僅有的一個Activity,若是涉及到的其餘Activity,都移交到其餘Task中進行。這使得singleInstance的Activity,像一座孤島,完全的黑盒,它不關注請求來自何方,也不計較後續由誰執行。在Android默認的各個應用中,不多有這樣的Activity,在我我的的工程實踐中,曾嘗試在有道詞典的快速取詞Activity中採用過,是由於我以爲快速取詞入口足夠方便(從notification中點選進入),而且會在各個場合使用,應該作得徹底獨立。
除了launchMode能夠用來調配Task,<activity>的另外一屬性taskAffinity,也是經常被使用。taskAffinity,是一種物以類聚的思想,它傾向於將taskAffinity屬性相同的Activity,扔進同一個Task中。不過,它的約束力,較之launchMode而言,弱了許多。只有當<activity>中的allowTaskReparen ting設置爲true,抑或是調用方將Intent的flag添加FLAG_ACTIVITY_NEW_TASK屬性時纔會生效。若是有機會用到Android的Notification機制就可以知道,每個由notification進行觸發的Activity,都必須是一個設成FLAG_ACTIVITY_NEW_TASK的Intent來調用。這時候,開發者極可能須要妥善配置taskAffinity屬性,使得調用起來的Activity,可以找到組織,在同一taskAffinity的Task中進行運行。

進程

在大多數其餘平臺的開發中,每一個開發人員對本身應用的進程模型都有很是清晰的瞭解。好比,一個控制檯程序,你能夠想見它從main函數開始啓動一個進程,到main函數結束,進程執行完成退出;在UI程序中,每每是有一個消息循環在跑,當接受到Exit消息後,退出消息循環結束進程。在該程序運行過程當中,啓動了什麼進程,和第三方進程進行通訊等等操做,每一個開發者都是心如明鏡一本賬算得清清楚楚。進程邊界,在這裏,猶如國界通常,每一次穿越都會留下深深的印跡。
在Android程序中,開發人員能夠直接感知的,每每是Task而已。倍感清晰的,是組件邊界,而進程邊界變得難以琢磨,甚至有了進程託管一說。Android中不但剝奪了手工鍛造內存權力,連手工處置進程的權責,也絕不猶豫的獨佔了。
固然,Android隱藏進程細節,並非刻意爲之,而是天然而然水到渠成的。若是,咱們把傳統的應用稱爲面向進程的開發,那麼,在Android中,咱們作得就是面向組件的開發。從前面的內容能夠知道,Android組件間的跳轉和通訊,都是在第三方介入的前提下進行,正因爲這種介入,使得兩個組件通常不會直接發生聯繫(於Service的通訊,是不須要第三方介入的,所以Android把它所有假設成爲穿越進程邊界,統一基於RPC來通訊,這樣,也是爲了掩蓋進程細節...),其中是否穿越進程邊界也就變得不重要。所以,若是這時候,還須要開發者關注進程,就會變得很奇怪,很費解,乾脆,Android將全部的進程一併託管去了,上層無須知道進程的生死和通訊細節。
在Android的底層,進程構造了底部的一個運行池,不只僅是Task中的各個Activity組件,其餘三大組件Service、Content Provider、Broadcast Receiver,都是寄宿在底層某個進程中,進行運轉。在這裏,進程更像一個資源池(概念形如線程池,上層要用的時候取一個出來就好,而不關注具體取了哪個...),只是爲了承載各個組件的運行,而各個組件直接的邏輯關係,它們並不關心。但咱們能夠想象,爲了保證總體性,在默認狀況下,Android確定傾向於將同一Task、同一應用的各個組件扔進同一個進程內,可是固然,出於效率考慮,Android也是容許開發者進行配置。
在Android中,總體的<application>(將影響其中各個組件...)和底下各個組件,均可以設置<process>屬性,相同<process>屬性的組件將扔到同一個進程中運行。最多見的使用場景,是經過配置<application>的process屬性,將不一樣的相關應用,塞進一個進程,使得它們能夠同生共死。還有就是將常常和某個Service組件進行通訊的組件,放入同一個進程,由於與Service通訊是個密集操做,走的是RPC,開銷不小,經過配置,能夠變成進程內的直接引用,消耗頗小。
除了經過<process>屬性,不一樣的組件還有一些特殊的配置項,以Content Provider爲例(經過<provider>項進行配置...)。<provider>項有一個mutiprocess的屬性,默認值爲false,這意味着Content Provider,僅會在提供該組件的應用所在進程構造一個實例,第三方想使用就須要經由RPC傳輸數據。這種模式,對於構造開銷大,數據傳輸開銷小的場合是很是適用的,而且可能提升緩存的效果。可是,若是是數據傳輸很大,抑或是但願在此提升傳輸的效率,就須要將mutiprocess設置成true,這樣,Content Provider就會在每個調用它的進程中構造一個實例,避免進程通訊的開銷。
既然,是Android系統幫助開發人員託管了進程,那麼就須要有一整套紛繁的算法去執行回收邏輯。Android中各個進程的生死,和運行在其中的各個組件有着密切的聯繫,進程們依照其上組件的特色,被排入一個優先級體系,在須要回收時,從低優先級到高優先級回收。Android進程共分爲五類優先級,分別是:Foreground Process, Visible Process, Service Process, Background Process, Empty Process。顧名思義不難看出,這說明,越和用戶操做緊密相連的,越是正與用戶交互的,優先級越高,越難被回收。具體詳情,參見:guide/topics/fundamentals.html#proclife。
有了優先級,還須要有良好的回收時機。回收太早,緩存命中機率低可能引發不斷的創造進程銷燬進程,池的優點蕩然無存;回收的太晚,總體開銷大,系統運行效率下降,好端端的法拉利可能被糟蹋成一枚QQ老爺車。Android的進程回收,最重要的是考量內存開銷,以及電量等其餘資源情況,此外每一個進程承載的組件數量、單個應用開闢的進程數量等數量指標,也是做爲衡量的一個重要標識。另外,一些運行時的時間開銷,也被嚴格監控,啓動慢的進程會很被強行kill掉。Android會定時檢查上述參數,也會在一些極可能發生進程回收的時間點,好比某個組件執行完成後,來作回收的嘗試。
從用戶體驗角度來看,Android的進程機制,會有很可喜的一面,有的程序啓動速度很慢,可是在資源充沛的前提下,你反覆的退出再使用,則啓動變得極其快速(進程沒死,只是從後臺弄到了前臺),這就是拜進程託管所賜的。固然,可喜的另外一面就是可悲了,Android的託管算法,還時不時的展示其幼稚的一面,明明用戶已經明顯感受到操做系統運行速度降低了,打開任務管理器一看,一票應用還生龍活虎的跳躍着,必需要手動幫助它們終結生命找到墳墓,這使得任務管理器基本成爲Android的裝機必備軟件。
從開發角度上來看,Android這套進程機制,解放了開發者的手腳。開發人員不須要處心積慮的構造一個後臺進程偷偷默默監聽某個時間,並嘗試用各類各樣的守護手段,把本身的進程鍛造的猶如不死鳥一輝通常,進程生死的問題,已經原理了普通開發人員須要管理的範疇內。但同時,於GC和人肉內存管理的爭議同樣,全部開發人員都不相信算法能比本身作得效率更高更出色。但我一直堅信一點,全部效率的優點都會隨着算法的不斷改良硬件的不斷提高而消失殆盡,只有開發模式的簡潔不會隨時間而有任何變化。

組件生命週期

任何架構上的變化,都會引發上層開發模式的變化,Android的進程模型,雖然使開發者再也不須要密切關注進程的建立和銷燬的時機,但仍然須要關注這些時間點對組件的影響。好比,你可能須要在進程銷燬以前,將寫到內存上的內容,持久化到硬盤上,這就須要關注進程退出前發生的一些事件。
在Android中,把握這些時間點,就必須瞭解組件生命週期(Components Lifecycles)。所謂組件的生命在週期,就是在組件在先後臺切換、被用戶建立退出、被系統回收等等事件發生的時候,會有一些事件通知到對應組件上,開發人員能夠選擇性的處理這些事件在對應的時間點上來完成一些附加工做。
除Content Provider,其餘組件都會有生命週期的概念,都須要依照這個模型定時定點處理一些情況,所有內容參見:guide/topics/fundamentals.html#lcycles。在這裏,擒賊先擒王,仍是拿Activity出來做楷模。



繼續偷圖,來自SDK。一個天然的Activity生命旅途,從onCreate開始,到onDestroy消亡。但月有陰晴圓缺組件有禍福旦夕,在系統須要的時候且組件位於後臺時,所在的進程隨時可能爲國捐軀被回收,這就使得知道切入後臺這個事情也變得很重要。
當組件進入棧頂,與用戶開始交互,會調用onResume函數,相似,當退出棧頂,會有onPause函數被呼喚。onResume和onPause能夠處理不少事情,最常規的,就是作一些文件或設置項的讀寫工做。由於,在該組件再也不前臺運行的時候,可能別的組件會須要讀寫一樣一份文件和設置,若是再也不onResume作刷新工做,用的可能就是一份髒數據了(固然,具體狀況,還須要具體分析,若是文件不會被多頭讀寫,能夠放到onCreate裏面去作讀工做)。
除了前述切入後臺會被其餘組件騷擾的問題,另外,死無定因也是件很可怕的事情。在Android中,組件都有兩種常見的死法,一種是天然消亡,好比,棧元素ABC,變成AB了,C組件就天然消亡了。這種死發輕如鴻毛,不須要額外關心。但另外一種狀況,就是被系統回收,那是死的重如泰山,爲國捐軀嘛。
但這種捐軀的死法,對用戶來講,比較費解。想象一下,一款遊戲,不能存盤,你一直玩啊玩,三天三夜沒閤眼,這時候你mm打來電話鼓勵一下,你精神抖擻的準備再接再礪,卻發現你的遊戲進程,在切入後臺以後,被系統回收了,一晚上回到解放前三天努力成爲一場泡影,你會不會想殺作遊戲的人,會不會會不會會不會,必定會嘛。這時候,若是沒有Activity生命週期這碼事,遊戲程序員必定是被冤死的,成了Android的替罪羊。可是,Android的組件是有生命週期的,若是真的發生這樣狀況,不要猶豫,去殺開發的程序員吧。
爲了逃生,程序員們有一塊免死金牌,那就是Android的state機制。所謂state,就是開發人員將一些當前運行的狀態信息存放在一個Bundle對象裏面,這是一個可序列化鍵值對集合。若是該Activity組件所處的進程須要回收,Android核心會將其上Activity組件的Bundle對象持久化到磁盤上,當用戶回到該Activity時候,系統會從新構造該組件,並將持久化到磁盤上的Bundle對象恢復。有了這樣的持久化的狀態信息,開發人員能夠很好的區分具體死法,並有機會的使得死而復生的Activity恢復到死前狀態。開發者應該作的,是經過onSaveInstanceState函數把須要維繫的狀態信息(在默認的狀態下,系統控件都會本身保存相關的狀態信息,好比TextView,會保存當前的Text信息,這都不須要開發人員擔憂...),寫入到Bundle對象,而後在onRestoreInstanceState函數中讀取並恢復相關信息(onCreate,onStart,也均可以處理...)。

線程

讀取數據,後臺處理,這些猥瑣的夥計,天然少不了線程的參與。在Android核心的調度層面,是不屑於考量線程的,它關注的只有進程,每個組件的構造和處理,都是在進程的主線程上作的,這樣能夠保證邏輯的足夠簡單。多線程,每每都是開發人員須要作的。
Android的線程,也是經過派生Java的Thread對象,實現Run方法來實現的。但當用戶須要跑一個具備消息循環的線程的時候,Android有更好的支持,來自於Handler和Looper。Handler作的是消息的傳送和分發,派生其handleMessage函數,能夠處理各類收到的消息,和win開發無異。Looper的任務,則是構造循環,等候退出或其餘消息的來臨。在Looper的SDK頁面,有一個消息循環線程實現的標準範例,固然,更爲標準的方式也許是構造一個HandlerThread線程,將它的Looper傳遞給Handler。
在Android中,Content Provider的使用,每每和線程掛鉤,誰讓它和數據相關呢。在前面提到過,Content Provider爲了保持更多的靈活性,自己只提供了同步調用的接口,而因爲異步對Content Provider進行增刪改查是一個常作操做,Android經過AsyncQueryHandler對象,提供了異步接口。這是一個Handler的子類,開發人員能夠調用startXXX方法發起操做,經過派生onXXXComplete方法,等待執行完畢後的回調,從而完成整個異步調用的流程,十分的簡約明瞭。

實現

整個任務、進程管理的核心實現,盡在ActivityManagerService中。上一篇說到,Intent解析,就是這個ActivityManagerService來負責的,其實,它是一個很名存實亡的類,由於雖然名爲Activity的Manager Service,但它管轄的範圍,不僅是Activity,還有其餘三類組件,和它們所在的進程。
在ActivityManagerService中,有兩類數據結構最爲醒目,一個是ArrayList,另外一個是HashMap。ActivityManagerService有大量的ArrayList,每個組件,會有多個ArrayList來分狀態存放。調度工做,每每就是從一個ArrayList裏面拿出來,找個方法調一調,而後扔到另外一個ArrayList裏面去,當這個組件沒對應的ArrayList放着的時候,說明它離死不遠了。HashMap,是由於有組件是須要用名字或Intent信息作定位的,好比Content Provider,它的查找,都是依據Uri,有了HashMap,一切都瓜熟蒂落了。
ActivityManagerService用一些名曰xxxRecord的數據結構,來表達各個存活的組件。因而就有了,HistoryRecord(保存Activity信息的,之因此叫History,是相對Task棧而言的...),ServiceRecord,BroadcastRecord,ContentProviderRecord,TaskRecord,ProcessRecord,等等。
值得注意的,是TaskRecord,咱們一直再說,Task棧這樣的概念,其實,真實的底層,並不會在TaskRecord中,維繫一個Activity的棧。在ActivityManagerService中,各個任務的Activity,都以HistoryRecord的形式,集中存放在一個ArrayList中,每一個HistoryRecord,會存放它所在TaskRecord的引用。當有一個Activity,執行完成,從概念上的Task棧中退出,Android是經過從當前HistoryRecord位置往前掃描同一個TaskRecord的HistoryRecord來完成的。這個設計,使得上層不少看上去邏輯很複雜的Task體系,在實現變得很統一而簡明,值得稱道。
ProcessRecord,是整個進程託管實現的核心,它存放有運行在這個進程上,全部組件的信息,根據這些信息,系統有一整套的算法來決議如何處置這個進程,若是對回收算法感興趣,能夠從ActivityManagerService的trimApplications函數入手來看。
對於開發者來講,去了解這部分實現,主要是能夠幫助理解整個進程和任務的概念,若是以爲這塊理解的清晰了,就不用去碰ActivityManagerService這個龐然大物了。

 

 

 




【六】 —— 界面構造
UI界面,對於每一個應用而言,是它與用戶進行交互的門臉。好的門臉,不僅是是要亮麗可人,最好還能秀色可餐過目不忘,甚至還應該有涵養有氣質,彬彬有理溫柔耐心。
對於開發者來講,鍛造這樣的面容,不但須要高超的技藝,也須要有稱手的工具和對得起黨的料子。俗話說,朽木不可雕也,芙蓉不是一日煉成的,不是什麼平臺都能叫特能書。有套好用的UI框架,對於開發者而言,真有如沙漠中的甘露,而要是撞見了杯具的UI套件,整個界面開發就有如夢魘了。
Android的UI框架,最核心的,是資源和Layout體系,而後,經過完善的控件庫,簡明的接口設計,進一步幫助開發者,可以最快的搭建本身須要界面(聽到這裏,Symbian同窗開始鑽土...)。

UI控件

作UI,有時候就像搭積木,在Android中,這個最原子的積木塊,就是View。全部其餘的UI元素,都是派生於此類的子孫類們。



又從SDK中偷來張圖,用來描述Android的UI控件結構,在每個window下,這都是一個標準而完整的樹結構。View有一個子類ViewGroup,它至關於一個容器類或者是複合控件,全部派生與ViewGroup的子類在這顆UI樹中均可以承擔着父節點的職責,而另外一些繞過ViewGroup從View直通下來的,就只能蜷局在葉節點的範疇內了。
之全部說這是一個很標準的控件樹,是由於父控件對子控件有絕對的掌控權,每一個子控件的佔地面積和位置,都是基於父控件來分配的,它可以接受和處理的事件,也是父控件派發下去的。這樣的結構,被不少平臺和框架普遍的承認,和傳統的win開發和杯具的Symbian相比,雖然由於事件傳播途徑變長了,不少操做的效率變低了,但整個結構更有層次性,每一個控件只須要多其父控件負責指揮子控件就好,職責明確,邏輯簡單,利於開發和設計。
談及任何平臺的控件,都有一些不可避免的主題,好比,每一個控件如何標識,如何設定大小和位置,如何接受和處理事件,如何繪製,諸如此類。
標識

在Android中,你能夠爲每一個控件選擇設定一個id,這個id的全局的惟一性不須要保證,但在某個局部的範圍內具備可識別性,這樣就能夠經過這個id找到這個控件(若是不須要查找,就別設置了...)。
可是,在父控件中逐級的find比較,找到id匹配的控件,而後再作轉型,是一個比較重量的操做,因而Android又爲控件憋出另外一個屬性,tag。它接受任意object類型的數據,你能夠把和這個控件對象相關的內容堆在裏面。好比,在list中,咱們經常將和每一個list item相關的全部控件元素封裝成一個object,扔到tag中,就不須要每次都去比較id進行尋找,更加高效快捷。
尺寸

在Android中,控件最重要的大小屬性,就是width/height,開發者能夠明確的指明控件的大小,能夠設定成爲fill_parent和wrap_content,這樣的概念性的大小。丈量並設定控件的位置,是經過兩步來進行的。
第一步是measure。它傳入此控件的width/height信息,控件會根據本身的參數,計算出真實須要的width/height,而後調用setMeasuredDimension方法,緩存成成員變量,留做後用。
在計算出大小以後,會進行另外一個步驟,layout。在這個過程當中,父控件會計算其上各個子控件的位置,從而完成整個大小和位置的肯定流程。整個measure和layout的流程,都是自上到下,從樹頂往葉子來推動的。
當開發人員須要自定義控件的時候,可能須要關注這些內容,經過重載onMeasure和onLayout方法,能夠定義本身控件的丈量方式。
事件

在Android中,全部的按鍵,觸屏等事件,都是從頂至下進行分發的。每一個ViewGroup的對象,會維繫一個focused變量,它表示在這個父控件中具有focus的控件,當有按鍵時間發生的時候,會找到這個focused子控件,並傳遞給它。同理,觸屏事件的分發也是相似,只不過和focus無關,父控件會遍歷全部子控件,看看誰處於觸碰位置,從而傳遞給誰。
另外還有一些事件,邏輯上並非從頂至下發起的。好比,當你修改某個子控件的內容,使得該子控件的大小和內容都發生了變化,就須要進行控件的重排和重繪,這些操做不只是子控件本身的事情,須要整個控件樹上的全部控件都須要配合。在Android中,處理這類事情的實現策略是子控件維繫一個ViewParent對象,該對象象徵着整個控件樹的管理者,子控件產生影響整個控件樹的事件時,會通知到ViewParent,ViewParent會將其轉換成一個自頂向下的事件,分發下去。
Android的事件處理邏輯,採用的是觀察者模式。Android的控件提供了一些列的add/set Listener的接口,使得外部觀察者,有機會處理控件事件。好比,你須要在某個button被點擊時作一些事情,你就須要派生一個View.OnClickListener對象做爲觀察者,調用該控件的setOnClickListener接口註冊進去,當button被點擊,就能夠得到處理點擊事件的機會了。固然,有的時候,你須要處理的邏輯更爲複雜,光是站在外面圍觀叫好不能解決問題,可能就須要派生某個控件,去重載onXXXX之類的事件處理函數,進行更完整的控制。
焦點

對於一個非觸屏的機器,焦點的維繫是一個極其重要的事情,而在有觸屏的年代,焦點的地位雖有所降低,但依然仍是須要妥善保護的。
Android中,是以控件樹爲單位,來管理焦點的。每一個控件,能夠設置上下左右四向的focus轉移對象。當在一個控件上發生焦點轉移事件,Android會如前述,自頂向下根據設定好的焦點轉移邏輯,跳轉到正確的控件上。和Symbian相比,真是,真是。。。
Layout

Layout是一類特殊的ViewGroup控件,它們自己沒有任何可顯示內容,形如透明的玻璃盒子,存活的惟一理由,就是其中的內部結構,可以更好的擺放它的子控件們。
好比線性的Layout,LinearLayout。放入這個Layout的子控件,會按水平或垂直方向,排排坐,一個挨着一個按順序排列下去。TableLayout,能夠將子控件按照表格的形式,一枚枚放置好。而RelativeLayout則更靈活,能夠設定各個控件之間的對齊和排列關係,適合定製複雜的界面。
有了Layout的存在,控件和控件之間再也不割裂的存在,而是更有機的結合在了一塊兒,設定起來也更爲方便。比Symbian那樣人肉維繫各個控件的關係,輕鬆自在多了。
更多

這些問題的完整答案,參見SDK中View的頁面:/reference/android/view/View.html。

實現

有了這些對Android的UI控件的認知,能夠看更總體性的實現細節,那就是Activity的UI實現。



如上圖所示,假設你作了個如同虛線框中結構的一個界面,經過Activity的setContentView方法,塞進了Activity中,就會造成圖示的一個邏輯關係。每個Activity,都包含一個Window對象,它表示的是一個頂級的一整屏幕上面的界面邏輯。在Android源碼中,其實現是MidWindow,它包含了一個FrameLayout對象,呈現出來就是那種帶着一個title的界面樣子。自定義的一堆控件,會插進Window的界面部分,在Activity中,全部事件的處理邏輯,是Window先享用,沒消費掉在交由這堆控件吃剩的。
在整個控件樹的最頂端,是一個邏輯的樹頂,ViewParent,在源碼中的實現是ViewRoot。它是整個控件樹和WindowManager之間的事件信息的翻譯者。WindowManager是Android中一個重要的服務。它將用戶的操做,翻譯成爲指令,發送給呈如今界面上的各個Window。Activity,會將頂級的控件註冊到WindowManager中,當用戶真是觸碰屏幕或鍵盤的時候,WindowManager就會通知到,而當控件有一些請求產生,也會經由ViewParent送回到WindowManager中。從而完成整個通訊流程。

 

 

 




【七】 —— 資源文件
做爲一枚coder,作界面,不少時候都是一場夢魘。不少時候,咱們會感受對於底層邏輯實現的頗有把握性,哪怕需求一直在變,也能夠經過不斷的重構一直跟進,一切盡在掌握。但遭遇界面,每每就再也不如此,它的好壞老是和審美、體驗之類的詞彙扯在一塊兒,在鳳姐芙蓉出沒的年頭,談審美成爲一件恐怖的事情。你可能會被要求不停的改代碼,就爲了移動一個像素,調整一枚按鈕,瑣碎而無聊。
爲了改變這樣的情況,挽救coder們於水生活熱之中,不少開發平臺,都採用了相似於資源文件的解決方案。此類方案的基本思想是,將界面的實現與底層邏輯的實現徹底剝離開來,用資源文件這樣的東西來描述界面。資源文件的描述語言,每每是結構化很強,好比Html,Xml(及其變形體)之類的。於開發語言相比,此類語言邏輯性較弱但結構更好可讀性更強更容易理解,並對自動化工具很是友好,能夠於界面的拖拽配置結合的更加完美。這樣的剝離,能夠是的底層邏輯和上層界面獨立變化,甚至不一樣的人員開發(這一點在web開發上表現的應該很明顯...),二者之間的耦合性很是的小,coder們的負擔,陡然減小(好吧,一個很挫的資源架構也會額外增長開發人員的負擔,Symbian同窗,請不要對號入座...)。

結構和格式

Android的資源文件,是由目錄結構,Xml格式的文件,和純數據文件構成。從格式上來看,無疑,學習門檻很是低。Xml做爲coder們的瑞士軍刀,哪怕使不習慣,弄得清楚並會用至少是沒有問題。從配套的工具來看,Android的ADT,提供了一套可視化的配置工具,說不上特別好用,但至少是差強人意能湊合着用,比不上iPhone的,調戲Symbian仍是沒有問題的[強檔廣告首播:有道詞典 for iPhone新版火熱上線,增長了超強單詞本功能,特有的觸電式顫抖單詞切換功能,讓你欲罷不能,持有相關設備的童鞋不要猶豫,蜂擁而上吧...]。
Android的資源文件,覆蓋面超級廣,只要是和界面相關的,均可以用資源文件表示,好比:UI的樣式,菜單,配置文件,各類描述性字符串,圖片,音頻視頻文件,動畫,顏色,尺寸,風格和樣式,等等等。全部的資源文件(不考慮asset,它和討論暫無關聯...),都放在res目錄下,不一樣類別的資源,須要放置在不一樣的特定名稱的子文件夾中,或者是寫在特定文件名的文件中(或者ms不是必須的,但,不用在這裏特立獨行,尋章辦事也挺好...)。好比,全部做爲UI背景之類的圖片,都須要扔在drawable這類的文件夾中,全部字符串相關的,都會放到values目錄下形如strings.xml這樣的文件中(以下圖所示,是一個資源文件目錄結構的截圖...)。


每一個xml文件,都有必定的約定。好比一個字符串,會放在<string></string>這樣的xml element中(以下圖所示...),你能夠經過eclipse的ADT插件提供的但是界面去填而不關注具體規範,也能夠直接人肉打造,前者對於新手來講更爲直觀,後者對於老鳥而言更爲迅捷。



可配置性

程序邏輯老是不變應萬變的,但界面每每是須要可以72變。首先一種變化因素,就是狀態。想象一下,咱們每每會有這樣相似的需求,一個按鈕,咱們須要沒有按下去的時候是一種背景,按的過程當中刷的變成另外一副模樣,當它可用的時候須要鮮鮮亮的一個樣子,不可用的時候最好是灰不溜秋沒人願點的慫樣,諸如此類。傳統編程模型下(Symbian,哥叫你出來當模特...),咱們老是須要不厭其煩的用代碼控制這樣的事情。監聽不一樣的事件,見縫插針的切換背景,並祈禱上天,千萬別讓哥調整,不然哥和你沒完。



在Android中,作這個事情,變得簡單許多,經過預設的一些Xml屬性,可以輕鬆的搞定。如上圖所示,是Radio Button的背景。經過搭配不一樣的屬性,就能夠自動轉換背景。好比第一個<item>,說的是當Radio Button被選中,而且具備焦點的時候,顯示btn_radio_on這幅圖片,而最後一個<item>,說的是前述條件都不知足,而且處於選中狀態,那麼顯示btn_radio_on這幅圖片。
另一個更易變的因素,就是手機硬件/軟件環境了,畢竟,不是家家都是蘋果,一個平臺搭一款手機,手機款形多樣化,幾乎是避免不了的問題。沒有人但願本身作的軟件在大屏幕手機上閃亮光鮮,換個小屏幕就慘不忍睹,豎屏看像那麼回事橫屏看就擠作一團。還有就是語言環境了,作爲一個有國際眼光的coder,做面向世界的NB軟件是咱的夢想,但咱們不能由於本身的夢想逼迫你們都去學中文,作一款軟件能夠根據手機的語言環境選擇最合適展現的語言,不少時候,是一個須要具有的功能點。
在Android中,實現這些,都是舉手之勞。方法就是將和環境相關的資源,放入特定名稱的文件夾中。好比,表示簡體中文字符信息的資源,能夠放到values-zh-rCN中去,當系統語言環境爲簡體中文時,就會呈現出中文的字符信息。在Android中,不少相關配置項,均可以按照這樣的方式參與到資源自適應的活動中來,包括屏幕大小,屏幕朝向,屏幕分辨率,語言環境,觸屏類型,SDK版本等等。系統會給全部配置項一個優先級(或者說權重,次序之類的),當用戶提供了多份資源的時候,系統會根據優先級從高到底淘汰備選資源,若是淘汰僅剩了一個,那就是最符合當前系統軟硬件語言環境的資源項,若是一個不剩,擇啓用默認項(最是形如values這樣沒有任何尾巴目錄中的資源...)。所以,默認的資源是很是重要的,它必須是其餘全部可選資源項的超集,不然在資源選擇失敗的狀況下,應用會淒涼的崩潰。
關於資源配置,以及選擇的詳情,參見SDK中的:guide/topics/resources/resources-i18n.html部分。

R類

在使用資源後,界面邏輯與底層邏輯的耦合被下降了,但這不意味着,二者沒有關聯了。好比,須要爲某個按鈕增長一個點擊事件,就須要定位到所需的那個按鈕;再好比,你須要使用某個字符串資源,通知用戶某件事情,就須要能定位到資源中放置的該字串。
最顯而易見的一種方式,就是經過字符串比較,用名字信息在資源的xml描述文件中定位到所需的內容,加載並使用。這種方式,解決了查找的問題,但反覆的字符串比較,勢必帶來嚴重的效率隱患。所以,在Android中,相似於Symbian的方法,引入了一個R類。
它的基本思想是,經過增長一個額外的編譯器,爲全部的資源項,都賦予一個32位的整形數來表示,同一個資源像的不一樣配置,都使用同一個id。這個整形數,就至關於這個資源項的門牌號碼,可以幫助定位到對應的資源項。全部的這些整形數,都以常量的方式,整合到一個Java類中,這個類就是R類。這樣,在程序中,就能夠經過使用這個R類,來查找所需的資源,這就將字符串比較,簡化成了一個整形數的比較,大大的節約了開銷。
不得不說,這整套邏輯和Symbian中的資源文件預編譯一致。但二者很不一樣的點在於Symbian中的整形數,表明的是一個二進制流的偏移量,資源中的內容在編譯時決定了。而Android中的整形數,是一個有邏輯意義的數值,它表達了這個資源所處的資源包,類別,和腳標,它的具體內容在運行時才肯定,這使得它的靈活性大大加強,付出的則是必定的效率代價。
實現

按照慣例,仍是要說實現的,以一個查找流程爲示例。當在Activity中須要使用字符串的,會調用它的getString方法,傳入R.stirng.xxx的一個整形數,換取一個符合當前機器環境配置的字符串。
getString,追根溯源,來到AssetManager類中。Asset類,實際上是一個空殼,它僅僅是提供了一些便利的接口,而將請求,經過JNI的接口,傳入到了底層C++實現的類庫中。
在底層的實現,主要是在C++實現的,AssetManager,ResourceTypes等等之中。其中:
JNI文件在:framework/base/core/jni
頭文件在:framework/base/include/utils
CPP文件在:framework/base/libs/utils

具體實現,和前述的算法邏輯是一致的。每個資源的id,32位,高8位表示資源包,低16位用於描述腳標,中間8位,用來講明類別。全部資源中的文件,都被預處理了,放入到了一系列的隊列和表中,經過id,能夠查到具體的位置。而後根據緩存的環境設置對象,跑一次淘汰算法,得到匹配的資源對象的對應文件和偏移量。而後將值讀取出來,經過JNI接口,拷貝回去。
以上這些描述,並不能幫助瞭解真實的實現細節,主要是爲了促使你們對讀取資源的效率有一個比較直觀的認知。整個資源讀取的流程比較長,可是實如今C++中,能夠預想,效率比Java高一些,開發人員,應該可以根據本身的需求,決定是否將內容寫入資源文件中(仍是寫在代碼中...),是否是須要本身稍微緩存一下,諸如此類。

 

 

 




【八】 —— Activity間數據傳輸
當對Android有一些瞭解後,不難發現,Android程序UI框架接近於Web頁面的概念。每個用於呈現頁面的組件,Activity,都是彼此獨立的,它們經過系統核心來調度整合,彼此之間的經過Intent機制來串聯。
每一種架構都會有其利弊,Android固然也不能超然脫俗。因爲Activity之間的鬆耦合關係,使得其複用能力特別的出色,Mash-Up方式能夠有效的提升開發效率。但另外一方面,因爲Activity過於的獨立,它們之間的數據共享,成爲一個麻煩的事情。
基於消息的傳輸

最標準的Activity之間的數據傳輸,就是經過Intent的Extra對象。好比,你在A這個Activity上拿到一坨用戶輸入的文本信息,興高采烈的想把它放到B這個Activity上展現併發送,一個很可行的方式,是經過Intent的putExtra接口,把用戶輸入的那些字符信息,按照key/value的形式放進Intent,傳輸到B這個Activity上。



如上圖示,從A到B的傳輸,看上去是一個直連,但其實,Intent都是要經由系統核心層去分析調度的,這個操做,跨越了進程邊界,天然而然,其中的數據,就是須要序列化和反序列化的,而不能夠僅經過一個指針就倒騰過去了。
基於這樣類消息的傳輸模式,好處很少說,直接談問題:
首先,對於大數據,就是一場杯具,不可能一坨上M的數據,也來來回回的傳來傳去,慢死了誰來負責;
再則,Activity之間,維繫的是一種線性關係,當我想把一份數據,從隊尾一級級傳到隊頭的話,本身歷經磨難不提,會把中間全部的Activity都搭上,他們明明本身可能不須要這份數據,也得拿着擱着,爲他人作嫁衣裳,不惆悵都不行;
此外,基於消息的傳輸,會把同一份數據生成若干個副本,有時候,這樣很好,沒有反作用,你們本身玩本身的不須要看別人臉色,但還有些時候,你就上杆子須要把這些數據都修改一下,同步起來那就太慘烈了;
最後,寫序列化代碼實在是太無聊了,稍微複雜點的代碼,就要本身寫個序列化接口,整個吃力不討好的活計。
基於外部存儲的傳輸

既然獸獸手手相傳太幸苦,天然而然的想法就是找個地方,A把數據擱在那裏,把地址信息告訴B,B須要的話,按圖索驥,自取就好。這個擱東西的地方,能夠考慮選擇外部存儲。
在Android中,預設了一些快捷便利類和模塊,更好的支持不一樣類別數據的存取。若是,須要存儲的是一些小數據量的配置信息,能夠選擇Preference,它等同於傳統意義上的設置文件。Preference提供了一些基於key/value的存取接口,能夠放置一些簡單的基本數據或者派生了Parcelable接口的對象。一個很好的應用場景是Cookie的存放。你在登陸界面得到了一份Cookie,你能夠把它扔進Preference,誰想要誰去拿,不再要來來回回的折騰了。
Preference適合於小數據、設置信息,若是大數據,你能夠考慮使用數據庫。在Android中,使用的是Sqlite,相關的類,堆放在android.database名字空間下,自查,無需贅述。
在Android裏,數據庫是私有的,若是想分享給第三方組件使用,就須要用ContentProvider來封裝了。好比你用系統的錄音機組件即時搞一段音頻信息,它不是返回可能大到恐怖的錄音數據,而是會返回給你一個Uri,它標明瞭這份數據在ContentProvider的地址信息,拿着這個Uri,領取數據就好。
固然固然,若是你足夠淡定,也能夠用赤果果的File來存儲。若是這個文件存在手機私有目錄下,那就內部使用,放在SD卡上,那就能夠全部應用,一切分享。



基於這樣外部存儲的數據傳輸,優缺點顯而易見,它解決了困擾Intent的傳輸路徑複雜,不利於傳輸大批量數據的問題,但同時,它有留下了效率隱患,複雜了編程模型。由於面對外部存儲,開發者必需要考慮效率問題,不少時候,多線程就會被提上議程,這樣,想不麻煩,都不行鳥。
基於Service的傳輸

既然存在外部太慢,那麼仍是在內存級別解決問題好了,這時候,你可能就須要請出Android四大組件之一的Service了。Service設計的本意,就是提供一些後臺的服務,數據存取,也能夠歸於其職責的一部分。
Service是提供了直連機制,調用的Activity,能夠經過bindService方法,與目標Service創建一條數據通路,拿到IBinder。這樣,經過Android提供的IPC模型,就能夠進行遠程方法的調用和數據的傳輸了。 



如上,經過這種模式,能夠解決必定問題,可是對於Service來講,實在是太大才小用了,Service的專長,不是在數據,仍是在邏輯。對於傳數據而言,Service仍是重量了一點,不可是有鏈接耗精力,傳輸經由IPC,寫起來也夠費勁。並且做爲組件,Service隨時可能死掉,你仍是要費勁心機的處理數據的持久化,得不償失。
利用Application傳輸

好吧,若是你須要在不一樣頁面之間共有某個內存對象,很合適的一種方式是把它們扔到Application裏面。Application是Context的一個子類,它會在整個應用任何一個組件起來以前,先起來噓噓。它的生命週期會貫穿整個應用全部組件的生命旅途,所以,放在其中的對象,不會被處理掉。
在Activity中,能夠經過getApplication接口,隨時得到Application對象的引用,用於實現一些全局對象的存儲,和處理,真是最合適不過的地方了。



固然,好東西也不要使用過分,能夠想象,因爲Application存活週期長,其上引用的對象一直缺乏被釋放的機會,若是你把它當成垃圾場,什麼東西都往裏扔,污染環境,混亂邏輯不提,單就是濫用內存資源這一項,就夠罪孽深重一把了。
所以,若是數據不是真的須要全局使用,不要擱在其中,若是數據太大,不要所有load出來,合理使用數據庫等外存儲設備,仍是必需要的。
結語

還有一些特殊狀況,能夠考慮用一些特殊的方式。好比子Activity之間,能夠經過調用getParent得到父Activity的引用,來訪問期間的對象,云云。小衆狀況,姑且不提。
以上這些概念,我相信全部的coder都瞭如指掌,如何處理這樣的數據,都心如明鏡。我只是給它們套上了一件Android的外衣,讓初入Android的coder們,能迅速找到心儀的兵器,劈山砍石,攻城拔寨。

 

 

 


 

 

 
  Drawing.png  (9.33 KB) 
  
 
 
2010-9-15 22:38
 
  Drawing (1).png  (13.17 KB) 
  
 
 
2010-9-15 22:38
 
  Drawing (2).png  (6.05 KB) 
  
 
 
2010-9-15 22:38
 
  Drawing (3).png  (7.72 KB) 
  

 

yeaxzz 上傳了這個附件:
2010-9-15 22:37
 
  ddwgxw9r_1019g8tqs8g4_b.png  (14.85 KB) 
  
 
 
2010-9-15 22:37
 
  ddwgxw9r_1020db7d45dh_b.png  (59.49 KB) 
  

 
2010-9-15 22:37
 
  ddwgxw9r_1021dpjfp3cb_b.png  (75.08 KB) 
  
 
 

 

yeaxzz 上傳了這個附件:
2010-9-15 22:35
 
  ddwgxw9r_1008gxrqmxfz_b.png  (13.91 KB) 
  
 
 
2010-9-15 22:35
 
  sIVImeFxoNuobDB_V7TQmMg.png  (15.76 KB) 
  

 

yeaxzz 上傳了這個附件:
2010-9-15 22:34
 
  activity_lifecycle.png  (64.6 KB) 
  

 

yeaxzz 上傳了這個附件:
2010-9-15 22:33
 
  2 (1).png  (6.37 KB) 
  
 
 
2010-9-15 22:33
 
  3 (1).png  (9.96 KB) 
  
 
 

 

yeaxzz 上傳了這個附件:
2010-9-15 22:32
 
  1 (1).png  (14.52 KB) 
  
 
 
2010-9-15 22:32
 
  ddwgxw9r_980hc5x4cgf_b.png  (30.02 KB) 
  
 

 

yeaxzz 上傳了這個附件:
2010-9-15 22:31
 
  ddwgxw9r_869c6pstfgr_b.jpg  (194.02 KB) 
  
  
相關文章
相關標籤/搜索