android 中 任務、進程和線程的區別

https://blog.csdn.net/xujingcheng123/article/details/79925661

任務

      在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模式配置的方式,寫的非常清晰了。提供方對組件的配置,是通過配置文件(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。顧名思義不難看出,這說明,越和用戶操作緊密相連的,越是正與用戶交互的,優先級越高,越難被回收。-----------------------------

線程

       讀取數據,後臺處理,自然少不了線程的參與。在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方法,等待執行完畢後的回調,從而完成整個異步調用的流程,十分的簡約明瞭。                                                      ProcessRecord,是整個進程託管實現的核心,它存放有運行在這個進程上,所有組件的信息,根據這些信息,系統有一整套的算法來決議如何處置這個進程,如果對回收算法感興趣,可以從ActivityManagerService的trimApplications函數入手來看。
      對於開發者來說,去了解這部分實現,主要是可以幫助理解整個進程和任務的概念,如果覺得這塊理解的清晰了,就不用去碰ActivityManagerService這個龐然大物了。

 

轉 原文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章