任務、進程和線程
關於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棧html
點開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這個龐然大物了。程序員