天天看點

Android應用程式模型:應用程式,任務,程序,線程

大多數作業系統,在應用程式所寄存的可執行程式映像(如Windows系統裡的.exe)、它所運作的程序以及和使用者互動的圖示和應用之間有一種嚴格的1對1關系。在Android系統裡,這些關聯要松散得多。并且重要的是要了解各種概念怎麼樣組成整體。

由于Android應用固有的靈活性,當實作這些不同方面的時候有一些基本術語需要加以了解:

一個Android包 (.apk)檔案,其中包含一個應用程式的代碼和資源。這是應用程式分發和下載下傳的檔案,使用者用來安裝該應用程式在他們的裝置上。

一個任務一般而言是指使用者視為的一個可啟動應用程式:通常任務在桌面(home screen)有一個可通路的圖示,且可以被切換到前台。

一個程序是一個運作着應用程式代碼的底層核心過程。通常所有.apk裡的代碼運作在一個專有的程序裡。不過,程序标記也可以用來限定代碼運作位置,或者為整個.apk或者為個别的活動activity,接收者receiver,服務或提供者provider,元件。

任務

這裡的一個關鍵點是:當使用者看到一個“應用”時,他們實際上在和任務打交道。如果您剛剛建立一個包含若幹活動的.apk,其中之一是頂層入口點(通過動作android.intent.action.MAIN的意圖過濾器intent-filter和類别android.intent.category.LAUNCHER),那麼這事實上将為您的.apk建立一個任務,并且您從那兒起動的任何活動都将作為那個任務的一部分運作。

一個任務,那麼,從使用者的角度來看是您的應用程式;而從應用程式開發者的角度來看,它是一個或多個使用者在那個任務中已經經曆過且未關閉的活動,或者說是一個活動棧。一個新的任務通過以Intent.FLAG_ACTIVITY_NEW_TASK标志起動一個活動意圖來建立;這一意圖将被用來作為任務的根意圖,定義任務是什麼。任何不以這個标志起動的活動将和起動它的活動在相同的任務中運作(除非該活動已請求特别啟動模式,稍後會讨論)。任務可以被重新安排:如果您使用FLAG_ACTIVITY_NEW_TASK标志但已經有一個任務以這個意圖運作,則目前任務的活動棧将被切換到前台而不是開始一個新的任務。

FLAG_ACTIVITY_NEW_TASK必 須謹慎使用:使用它意味着,在使用者看來,一個新的應用程式由此起動。如果這不是你所期望的行為,你就不該去建立一個新的任務。另外,僅在使用者可以從桌面返 回到他原來的地方和以一個新任務啟動相同意圖的情況下,你才應該使用新的任務标記。否則,如果使用者在你已經啟動的任務裡按桌面(HOME)鍵,而不是傳回(BACK)鍵,你的任務及其活動将被放置到桌面後面,沒有辦法再切換回去。

任務共用性Affinity

在某些情況下,Android需要知道一個活動屬于哪個任務即使它沒有被啟動到一個具體的任務裡。這是通過任務共用性(Affinities)完成的。任務共用性(Affinities)為這個運作一個或多個活動的任務提供了一個獨特的靜态名稱,預設的一個活動的任務共用性(Affinity)是實作了該活動的.apk包的名字。這提供了預期的标準特性,即所有在一個特定的.apk包裡的活動是單個使用者應用程式的一部分。

當開始一個沒有Intent.FLAG_ACTIVITY_NEW_TASK标志的活動時,任務共用性affinities不會影響将會運作該新活動的任務:它總是運作在啟動它的任務裡。但是,如果使用了NEW_TASK标志,那麼共用性(affinity)将被用來判斷是否已經存在一個有相同共用性(affinity)的任務。如果是這樣,這項任務将被切換到前面而新的活動會啟動于這個任務的頂層。

這種特性在您必須使用NEW_TASK标志的情況下最有用,尤其是從狀态欄通知或桌面快捷方式啟動活動時。結果是,當使用者用這種方式啟動您的應用程式時,它的目前任務将被切換到前台,而且想要檢視的活動被放在最上面。

你可以在程式清單(Manifest)檔案的應用程式application标簽中為.apk包中所有的活動配置設定你自己的任務共用性Affinites,或者在活動标記中為各個活動進行配置設定。一些說明其如何使用的例子如下:

如果您的.apk包含多個使用者可以啟動的高層應用程式,那麼您可能需要對使用者看到的每個活動指定不同的affinities。一個不錯的命名慣例是以附加一個以冒号分隔的字元串來擴充您的.apk包名。例如,“ com.android.contacts ”.apk可以有affinities:“com.android.contacts:Dialer”和“ com.android.contacts:ContactsList”。

如果您正在替換一個通知,快捷方式,或其他可以從外部發起的應用程式的“内部”活動,你可能需要明确設定您替代活動的taskAffinity和您準備替代的應用程式一樣。例如,如果您想替換contacts詳細資訊視圖(使用者可以建立并調用快捷方式),你得把taskAffinity設定成“com.android.contacts”。

啟動模式和啟動标志

您控制活動和任務互動的主要途徑是通過活動的launchMode 屬性和意圖相關的标志flags。這兩個參數可以以各種方式合作來控制活動啟動的結果,正如它們相關文檔中描述的那樣。在這裡,我們将看看一些常見的用例和參數組合。

你将使用的最常見的啟動模式(除了預設的standard模式)是singleTop。這并不影響任務;它隻是避免多次在一個堆棧頂部起動同一活動。

singleTask啟動模式對任務有重大的影響:它使活動始終是開始于一項新的任務(或其現有的任務被帶到前台) 。使用這種模式需要謹慎對待你如何與系統其他部分進行互動,因為這影響到這個活動中的每一個路徑。它應當僅在活動處于應用程式前台時使用(也就是支援MAIN動作和LAUNCHER類别)。

singleInstance啟動模式更是專業,并應僅用于整個就是被實作為一個活動的應用程式中。

有一種你會經常遇到的情況是當另一個實體(如SearchManager 或NotificationManager)開始您的一個活動。在這種情況下,必須使用Intent.FLAG_ACTIVITY_NEW_TASK 标簽,因為該項活動是在任務之外起動的(而且應用/任務可能根本不存在)。正如前面所述,這種情況下的标準行為是把比對新活動affinity的任務帶到前台和在此之上起動新的活動。不過,也有其他您可以實施的行為類型。

一種通常的辦法是和NEW_TASK聯合起來使用Intent.FLAG_ACTIVITY_CLEAR_TOP标志。這樣,如果您的任務已經運作,那麼它将會被帶到前台,除根活動外其它所有堆棧中的活動都被清除,而且這個根活動的方法onNewIntent(Intent)會在該意圖起動時被調用。注意這個活動使用這個方法時經常使用singleTop或者singleTask起動模式,這樣目前執行個體被賦予新的意圖而不是需要銷毀它然後重新起動一個新的執行個體。

您能采取的另外的方法是設定通知活動的任務affinity為空字元串“”(表示沒有affinity),并設定finishOnBackground屬性。這種方法是有用的如果你希望這個通知把使用者帶到一個單獨的描述它的活動中,而不是傳回到應用程式的任務。通過指定這個屬性,該活動将被結束不管使用者通過BACK還是HOME離開它;如果這個屬性沒有指定,按首頁将導緻這個活動及其任務仍保留在系統裡,且可能沒有辦法傳回它。

請務必閱讀關于launchMode屬性和Intent标志的文檔以擷取這些選項的詳細說明。

程序

在Android裡,程序完全是應用的實作細節,而不是使用者通常了解的那樣。其主要用途就是:

通過安置不受信任的或不穩定的代碼到另一個程序來提高穩定性或安全性。

通過在同一程序裡運作多個.apks的代碼來減少開銷。

通過把重量級代碼放在單獨的程序中來幫助系統管理資源,該程序可以在不影響應用程式其他部分的情況下被終止。

正如前面所述,這個程序屬性用來控制運作着特定應用程式元件的程序,注意,此屬性不能用于違反系統安全性:如果有兩個不共享相同使用者ID的.apks嘗試運作在同一程序中,這将不會被允許,相反會為它們每一個建立不同的程序。

參見安全 文檔以擷取更多關于安全限制方面的資訊。

線程

每個程序包含一個或多個線程。多數情況下,Android避免在程序裡建立額外的線程,以保持應用程式單線程,除非它建立自己的線程。一個重要的結果就是所有對活動Activity,廣播接收器BroadcastReceiver以及服務Service執行個體的調用都是由這個程序的主線程建立的。

注意新的線程并不會為每個活動,廣播接收器,服務或者内容提供器(ContentProvider) 執行個體而建立:這些應用程式的元件在程序裡被執行個體化(除非另有說明,都在同一個程序處理),實際上是程序的主線程。這說明當被系統調用時沒有哪個元件(包括 服務)會進行遠端或者阻塞操作(就像網絡調用或者計算循環),因為這将阻止程序中的所有其他元件。你可以使用标準的線程類Thread或者Android的HandlerThread便捷類去對其它線程執行遠端操作。

這裡有一些關于這個線程規則的重要的例外:

 ・ 對IBinder或者IBinder實作的接口的調用由調用線程或本地程序的線程池(如果該呼叫來自其他程序)分發,而不是它們的程序的主線程。特殊情況下,一個服務的IBinder可以這樣調用。(盡管調用服務裡的方法已經在主線程裡完成。)這意味着IBinder接口的實作必須要有一種線程安全的方法,這樣任意線程才能同時通路它。

  ・ 對ContentProvider主要方法的調用由調用線程或者主線程分發,如同IBinder一樣。被指定的方法在内容提供器的類裡有記錄。這意味着實作這些方法必須要有一種線程安全的模式,這樣任意其它線程可以同時通路它。

  ・ 視圖及其子類中的調用由正在運作着視圖的線程産生。通常情況下,這會被作為程序的主線程,如果你建立一個線程并顯示一個視窗,那麼繼承的視窗視圖将從那個線程裡啟動。