1. 程式人生 > 實用技巧 >Android應用開發基礎 學習筆記(一)

Android應用開發基礎 學習筆記(一)

Android開發基礎學習筆記(一)

Android 基本架構

Android的系統架構分為5層,從高到低分別是應用程式層(Applications)、應用程式框架層(Application Framework)、系統執行庫層(Libralies)、執行環境層(Android Runtime)和Linux核心層(Linux Kernel)。
下面這張圖展示了Android系統的主要組成部分:

1、Linux 核心

Android的核心繫統服務基於Linux 2.6核心,例如:安全、記憶體管理、程序管理、網路堆疊、驅動模型。並且改過Linux核心,針對手機進行了專門的優化,主要是電源管理和程序排程方面。Linux核心層也作為硬體和軟體之間的抽象層,它隱藏具體硬體細節而為上層提供統一的服務,所以只開發應用程式不需要過多瞭解這一層。

2、執行環境

Android包含一個核心庫的集合(Core Libralies),提供大部分在Java程式語言核心類庫中可用的功能。
android虛擬機器Dailvk是基於暫存器的,所有的類都經由Java編譯器編譯成.class檔案格式再由Android SDK中的dx工具進行二次編譯成適合小記憶體使用的.dex檔案格式再由虛擬機器執行,提高了虛擬機器的執行效率。
Dalvik虛擬機器依賴於Linux 核心提供基本功能,如執行緒和底層記憶體管理。

3、系統執行庫

核心類庫的來源主要有兩種,一種是系統類庫,android為了提高框架層的執行效率,使用c/c++來實現一些關鍵模組,基礎演算法等等。一種是第三方類庫,大部分是優秀開源專案的移植,如android的多媒體處理,sqlite。webkit。android會為所有移植而來的第三方類庫封裝一層JNI介面,以供框架層呼叫。

4、應用程式框架

框架層是android的核心框架層主要是系統服務組成的,框架層對於android的重要性,相當於大腦對人。這些服務包括元件管理服務,視窗管理服務,電源管理服務,通話管理服務,地理資訊服務中。所有服務都寄存在系統核心程序中。在執行時,每個服務都佔據一個獨立的執行緒,彼此通過程序間的通訊機制傳送訊息和傳輸資料。
應用層中的應用,時刻都在與這些服務打交道,瞭解裝置資訊等操作,都是在各個系統服務的支援下實現的。
android應用的最大特徵是基於元件的設計方式。每個應用都郵若干個元件構成,元件和元件之間並不會建立通訊通道,而是通過框架層的系統,集中的排程和傳遞訊息,這樣的設計方式相當於一箇中間層,該層瞭解所有元件的狀況,可以更智慧的進行協調,從而提升了整個系統的靈活性。

5、應用程式

應用層主要是用於和使用者進行互動的,包括系統的應用,例如電話,瀏覽器,聯絡人。也包括後續安裝到裝置上的第三方應用,這些統稱為android應用層。

Android 元件

Android的一個主要特點是,一個應用程式可以利用其他應用程式的元素(假設這些應用程式允許的話)。例如,如果你的應用程式需要顯示一個影象的滾動列表,且其他應用程式已經開發了一個合適的滾動條並可以提供給別的應用程式用,你可以呼叫這個滾動條來工作,而不用自己開發一個。你的應用程式不用併入其他應用程式的程式碼或連結到它。相反,當需求產生時它只是啟動其他應用程式塊。

對於這個工作,當應用程式的任何部分被請求時,系統必須能夠啟動一個應用程式的程序,並例項化該部分的Java物件。因此,不像其他大多數系統的應用程式,Android應用程式沒有一個單一的入口點(例如,沒有main()函式)。相反,系統能夠例項化和執行需要幾個必要的元件。有四種類型的元件:

  1. 活動(Activities)
  2. 服務(Services)
  3. 廣播接收者(Broadcast receivers)
  4. 內容提供者(Content providers)

然而,並不是所有的應用程式都必須包含上面的四個部分,你的應用程式可以由上面的一個或幾個來組建。當你決定使用以上哪些元件來構建Android應用程式時,你應該將它們列在AndroidManifest.xml檔案中,在這個檔案中你可以宣告應用程式元件以及它們的特性和要求。

1、活動(Activities)

一個活動表示一個視覺化的使用者介面,關注一個使用者從事的事件。例如,一個活動可能表示一個使用者可選擇的選單項列表,或者可能顯示照片連同它的標題。一個文字簡訊應用程式可能有一個活動,顯示聯絡人的名單傳送資訊;第二個活動,寫資訊給選定的聯絡人;其他活動,重新檢視舊資訊或更改設定。雖然他們一起工作形成一個整體的使用者介面,但是每個活動是獨立於其他活動的。每一個都是作為Activity基類的一個子類的實現。

android.app.Activity類:因為幾乎所有的活動(activities)都是與使用者互動的,所以Activity類關注建立視窗,你可以用方法setContentView(View)將自己的UI放到裡面。然而活動通常以全屏的方式展示給使用者,也可以以浮動視窗或嵌入在另外一個活動中。有兩個方法是幾乎所有的Activity子類都實現的:

  1. onCreate(Bundle):初始化你的活動(Activity),比如完成一些圖形的繪製。最重要的是,在這個方法裡你通常將用佈局資源(layout resource)呼叫``setContentView(int)方法定義你的UI,和用`findViewById(int)在你的UI中`檢索你需要程式設計地互動的小部件(widgets)。`setContentView`指定由哪個檔案指定佈局(main.xml),可以將這個介面顯示出來,然後我們進行相關操作,我們的操作會被包裝成為一個意圖(Intent),然後這個意圖對應有相關的activity進行處理。``
  2. onPause():處理當離開你的活動時要做的事情。最重要的是,使用者做的所有改變應該在這裡提交(通常ContentProvider儲存資料)。

一個應用程式可能只包含一個活動,或者像剛才提到的簡訊應用,它可能包含幾個活動。這些活動是什麼,以及有多少,當然這取決於它的應用和設計。一般來講,當應用程式被啟動時,被標記為第一個的活動應該展示給使用者。從一個活動移動到另一個活動由當前的活動完成開始下一個。

每一個活動都有一個預設的視窗。一般來講,視窗會填滿整個螢幕,但是它可能比螢幕小或浮在其他視窗上。一個活動還可以使用額外的視窗——例如彈出式對話方塊,或當一使用者選擇螢幕上一個特定的項時一個視窗顯示給使用者重要的資訊。

視窗的可視內容是由繼承自View基類的一個分層的檢視—物件提供。每個檢視控制元件是視窗內的一個特定的矩形空間。父檢視包含和組織子女檢視的佈局。葉子檢視(在分層的底層)繪製的矩形直接控制和響應使用者的操作。因此,一個檢視是活動與使用者交互發生的地方。例如,一個檢視可能顯示一個小的圖片和當用戶點選圖片時發起一個行為。Android有一些現成的檢視你可以使用,包括按鈕(buttons)、文字域(text fields)、滾動條(scroll bars)、選單項(menu items)、複選框(check boxes)等等。

通過Activity.setContentView() 方法放置一個檢視層次在一個活動視窗中。內容檢視(content view)是層次結構的根檢視物件。層次結構如下圖所示:

Activity.setContentView() 方法:
public void setContentView (int layoutResID):根據佈局資源設定活動的介面。 資源將被誇大,添加布局資原始檔中所有的最高層的檢視( top-level views )到活動.

2、 服務(Services)

一個服務沒有一個視覺化使用者介面,而是在後臺無期限地執行。例如一個服務可能是播放背景音樂而使用者做其他一些事情,或者它可能從網路獲取資料,或計算一些東西並提供結果給需要的活動(activities)。每個服務都繼承自Service基類。

每個服務類在AndroidManifest.xml中有相應的<service>宣告。服務可以通過Context.startService()和Context.bindService()啟動。

一個典型的例子是一個媒體播放器播放一個播放列表中的歌曲。該播放器應用程式將可能有一個或多個活動(activities),允許使用者選擇歌曲和開始播放。然而,音樂播放本身不會被一個活動處理,因為使用者希望保持音樂繼續播放,當用戶離開播放器去做其他事情時。為了保持音樂繼續播放,媒體播放器活動可以啟動一個服務執行在後臺。系統將保持音樂播放服務執行,甚至媒體播放器離開螢幕時。

可以連線到(繫結到)一個持續執行的服務(並啟動服務,如果它尚未執行)。連線之後,你可以通過服務暴露的介面與服務交流。對於音樂服務,這個介面可以允許使用者暫停、倒帶、停止和重新播放。

像活動(activities)和其他元件一樣,服務(services)執行在應用程式程序中的主執行緒中。因此,他們將不會阻止其他元件或使用者介面,他們往往產生其他一些耗時的任務(如音樂播放)。

3、廣播接收者(Broadcast receivers)

一個廣播接收者是這樣一個元件,它不做什麼事,僅是接受廣播公告並作出相應的反應。許多廣播源自於系統程式碼,例如公告時區的改變、電池電量低、已採取圖片、使用者改變了語言偏好。應用程式也可以發起廣播,例如為了他其他程式知道某些資料已經下載到裝置且他們可以使用這些資料。

一個應用程式可以有任意數量的廣播接收者去反應任何它認為重要的公告。所有的接受者繼承自BroadcastReceiver基類。

BroadcastReceiver類:

是接受sendBroadcast()傳送的意圖(intents)的基類。可以用Context.registerReceiver()動態地註冊這個類的例項,或者通過AndroidManifest.xml中<receiver>標籤靜態釋出。注意:如果你在Activity.onResume() 註冊一個接受者,你應該在Activity.onPause()登出它。因為當暫停時你不會收到意圖,登出它將削減不必要的系統開銷。不要在Activity.onSaveInstanceState()中登出它,因為它將不會被呼叫,如果使用者移動到先前的堆疊。

有兩種主要的可接受廣播型別:

  1. 正常廣播(由Context.sendBroadcast傳送)是完全非同步的。所有的廣播接收者以無序方式執行,往往在同一時間接收。這樣效率較高,但是意味著接受者不能使用結果或終止廣播資料傳播。
  2. 有序廣播(由Context.sendOrderedBroadcast傳送)一次傳遞給一個接收者。由於每個接收者依次執行,因此它可以傳播到下一個接收器,也可以完全終止傳播以便他不會傳遞給其他接收者。接收者的執行順序可由匹配的意圖過濾器(intent-filter)的android:priority屬性控制。

廣播接收者不顯示一個使用者介面。然而,它們啟動一個活動去響應收到的資訊,或者他們可能使用NotificationManager去通知使用者。通知可以使用多種方式獲得使用者的注意——閃爍的背光、振動裝置、播放聲音等等。典型的是放在一個持久的圖示在狀態列,使用者可以開啟獲取資訊。

4、內容提供者(Content providers)

內容提供者(content provider)使一個應用程式的指定資料集提供給其他應用程式。這些資料可以儲存在檔案系統中、在一個SQLite資料庫、或以任何其他合理的方式。內容提供者繼承自ContentProvider 基類並實現了一個標準的方法集,使得其他應用程式可以檢索和儲存資料。然而,應用程式並不直接呼叫這些方法。相反,替代的是它們使用一個ContentResolver物件並呼叫它的方法。ContentResolver能與任何內容提供者通訊,它與提供者合作來管理參與進來的程序間的通訊。

內容提供者是Android應用程式的主要組成部分之一,提供內容給應用程式。他們封裝資料且通過單個ContentResolver介面提供給應用程式。只有需要在多個應用程式間共享資料是才需要內容提供者。例如,通訊錄資料被多個應用程式使用,且必須儲存在一個內容提供者中。如果你不需要在多個應用程式間共享資料,你可以直接使用SQLiteDataBase。

當ContentResolver發出一個請求時,系統檢查給定的URI的許可權並傳遞請求給內容提供者註冊。內容提供者能理解URI想要的東西。UriMatcher 類用於幫組解析URIs。

需要實現的方法主要如下:

  • query(Uri, String[], String, String[], String) 返回資料給呼叫者
  • insert(Uri, ContentValues) 插入資料到內容提供者
  • update(Uri, ContentValues, String, String[]) 更新內容提供者已存在的資料
  • delete(Uri, String, String[]) 從內容提供者中刪除資料
  • getType(Uri) 返回內容提供者中的MIME 型別資料

元件的啟用和關閉

1、啟用元件:意圖(Intents)

當接收到ContentResolver發出的請求後,內容提供者被啟用。而其它三種元件——活動、服務和廣播接收者,被一種叫做意圖(intent)的非同步訊息啟用。意圖是一個儲存著訊息內容的Intent物件。對於活動和服務來說,Intent物件指明瞭請求的操作名稱以及作為操作物件的資料的URI和其它一些資訊。例如,它可以傳遞對活動的一個請求,讓它為使用者顯示一張圖片,或者讓使用者編輯一些文字。而對於廣播接收者而言,Intent物件指明瞭廣播的行為。例如當照相按鈕被按下,它可以對所有感興趣的物件廣播。

對於每種元件來說,啟用的方法是不同的。下面將分別介紹活動、服務、廣播接收者元件的啟用方法。

1.1、活動(Activity)元件的啟用

通過傳遞一個Intent物件至Context.startActivity()或Activity.startActivityForResult()以載入(或指定新工作給)一個活動。相應的活動可以看到初始的意圖,這個意圖通過getIntent() 方法來檢視啟用活動。Android呼叫活動的onNewIntent()方法傳遞任何後續的意圖。

一個活動經常啟動了下一個。如果它期望它所啟動的那個活動返回一個結果,它會呼叫startActivityForResult()而不是startActivity()。例如,如果它啟動了一個活動讓使用者挑選一張照片,它可能會返回被選中的照片。結果以一個Intent物件傳遞呼叫活動的onActivityResult() 方法。

1.2、服務(Service)元件的啟用

通過傳遞一個Intent物件至Context.startService()以啟動一個服務(或給予正在執行的服務以一個新的指令)。Android呼叫服務的onStart()方法並將Intent物件傳遞給它。

與此類似,一個Intent可以傳遞給Context.bindService()以在呼叫的元件和目標服務之間建立持續的連線。這個服務會在呼叫onBind() 方法中接受這個Intent物件(如果服務尚未啟動,bindService()會先啟動它)。例如,一個活動可以連線至前面講到的音樂播放服務,並提供給使用者一個可操作的(使用者介面)以對播放進行控制。這個活動可以呼叫bindService()來建立連線,然後呼叫服務中定義的物件來控制播放。

1.3、廣播接收者(Broadcast receiver)元件的啟用

應用程式可以通過將Intent物件傳遞給

  • Context.sendBroadcast()
  • Context.sendOrderedBroadcast()
  • Context.sendStickyBroadcast()

及其它類似方法來產生一個廣播。Android會通過onReceive()方法將intent傳遞給所有對此廣播有興趣的廣播接收者。

2、關閉元件

內容提供者僅在響應ContentResolver提出請求的時候啟用。而一個廣播接收者僅在響應廣播資訊的時候啟用。所以,沒有必要去顯式的關閉這些元件。

而活動則不同,它提供了使用者介面。與使用者進行會話,所以只要會話依然持續,哪怕對話程序空閒,它都會一直保持啟用狀態。與此相似,服務也會在很長一段時間內保持執行。所以Android提供方法有序地關閉活動和服務。

  • 可以通過呼叫它的finish()方法來關閉一個活動。一個活動也可以通過呼叫finishActivity()方法來關閉另外一個活動(它用startActivityForResult() 啟動的)。
  • 服務可以通過呼叫它的stopSelf()方法來停止,或者呼叫 Context.stopService()。

當元件不再被使用的時候或者Android必須要為更多活躍的元件回收記憶體時,元件也可能會被系統關閉。

3、清單(manifest)檔案

當Android啟動一個應用程式元件之前,它必須知道那個元件是存在的。所以,應用程式會在一個清單(manifest)檔案中宣告它的元件,這個檔案會被打包到Android包中。這個.apk檔案還將包括應用程式的程式碼、檔案以及其它資源。

這個清單檔案是XML結構的檔案,且所有的Android應用程式都把它叫做AndroidManifest.xml。為宣告一個應用程式元件,它還會做很多額外工作,比如指明應用程式所需連結到的庫的名稱(除了預設的Android庫之外)以及宣告應用程式期望獲得的各種許可權。

但清單檔案的主要功能仍然是向Android宣告應用程式的元件。舉例說明,一個活動可以如下宣告:

AndroidManifest.xml

<activity>元素的name屬性指定了實現了這個活動的Activity類的子類,icon和label屬性指向了包含展示給使用者的此活動的圖示和標籤的資原始檔。

其它元件也以類似的方法宣告——<service> 元素用於宣告服務,<receiver> 元素用於宣告廣播接收者,而<provider>元素用於宣告內容提供者。清單檔案中未進行宣告的活動、服務以及內容提供者將不為系統所見,從而也就不會被執行。然而,廣播接收者既可以在清單檔案中宣告,也可以在程式碼中動態的建立(作為BroadcastReceiver物件)且呼叫Context.registerReceiver()方式註冊到系統。

4、Intent過濾器

Intent物件可以顯式地指定目標元件。如果進行了這種指定,Android會找到這個元件(依據清單檔案中的宣告)並激活它。但如果Intent沒有進行顯式的指定,Android就必須為它找到對於intent來說最合適的元件。這個過程是通過比較Intent物件和所有可能物件的intent過濾器完成的。元件的intent過濾器會告知Android它所能處理的intent型別。如同其它關於元件的必要資訊一樣,它們在清單檔案中進行宣告的。這裡是上面示例的一個擴充套件,其中加入了針對活動的兩個intent過濾器宣告:

AndroidManifest.xml

示例中的第一個過濾器——action:“android.intent.action.MAIN”和category:“android.intent.category.LAUNCHER”的組合,是常見的。它標記這個活動顯示在應用程式啟動器中,使用者在裝置上看到的可啟動的應用程式列表。換句話說,這個活動是應用程式的入口,是使用者選擇執行這個應用程式後所見到的第一個活動。第二個過濾器聲明瞭這個活動針對特定型別的資料。

一個元件可以擁有任意數量的intent過濾器,每個宣告一系列不同的能力。如果它沒有包含任何過濾器,它將只能被顯式聲明瞭目標元件名稱的意圖啟用。

對於廣播接收者,它在程式碼中建立並註冊intent過濾器,直接作為IntentFilter的物件例項化。其它過濾器則在清單檔案中設定。

活動與任務

1、活動與任務概述

如前所述,一個活動(activity)能啟動另一個活動,包括定義在別的應用程式中的活動。再次舉例說明,假設你想讓使用者顯示某地的街道地圖。而且已經有了一個活動能做這個事情(假設這個活動叫做地圖檢視器),因此你的活動要做的就是將請求資訊放進一個Intent物件,然後將它傳給startActivity()。地圖檢視器就啟動並顯示出地圖。當用戶點選返回按鈕之後,你的活動就會重新出現在螢幕上。

對使用者來說,這個地圖檢視器就好像是你的應用程式的活動一樣,雖然它定義在其他的應用程式中且執行在那個應用程式的程序中。Android將這些活動保持在同一個任務task)中以維持使用者的體驗。簡單地講,任務是使用者體驗上的一個“應用程式”,是排成堆疊的一組相關活動。棧底的活動(根活動)是起始活動——一般來講,它是使用者在應用程式啟動器(也稱應用程式列表,下同)中選擇的一個活動。棧頂的活動是正在執行的活動——它關注使用者的行為(操作)。當一個活動啟動另一個,新的活動被壓入棧頂,變為正在執行的活動。前面那個活動儲存在棧中。當用戶點選返回按鈕時,當前活動從棧頂中彈出,且前面那個活動恢復成為正在執行的活動。

棧中包含物件,因此如果一個活動啟動了多個例項——例如多個地圖檢視器,則棧對每個例項有一個獨立的入口。(可以這樣理解:假設有四個活動以這樣的順序排在棧中——A-B-C-D,現在又有一個C的例項,則棧變成A-B-C-D-C,這兩個C的例項是獨立的。)棧中的活動從不會被重新排列,只會被壓入、彈出。這點很好理解,因為活動的呼叫順序是固定的。

任務是一棧的活動,而不是清單檔案中宣告的某個類或元素,因此無法獨立於它的活動為任務賦值。整個任務的值是在棧底活動(根活動)設定的。例如,下節將討論的“任務親和度”,親和度資訊就是從任務的根活動中獲取的。

一個任務的所有活動作為一個整體執行。整個任務(整個活動棧)可置於前臺或傳送到後臺。例如,假設當前任務有四個活動在棧中——三個活動在當前活動下面。使用者按下HOME鍵,切換到程式啟動器,並選擇一個新的應用程式(實際上是一個新的任務)。當前任務進入後臺,新任務的根活動將顯示。接著,過了一會,使用者回到主螢幕並再次選擇之前的應用程式(之前的任務)。那個任務棧中的所有四個活動都變為前臺執行。當用戶按下返回鍵時,不是離開當前任務回到之前任務的根活動。相反,棧頂的活動被移除且棧中的下一個活動將顯示。

上面所描述的是活動和任務的預設行為,但是有方法來改變所有這些行為。活動與任務之間的聯絡及任務中活動的行為,是由啟動活動的Intent物件的標誌(flags)和清單檔案中活動<activity>元素的屬性共同決定的。

在這方面,主要的Intent標誌有:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  • FLAG_ACTIVITY_SINGLE_TOP

主要的<activity>屬性有:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

接下來的小節將討論這些標誌和屬性的作用,他們怎麼互動,及使用的注意事項。

2、親和度和新任務(Affinities and new tasks)

預設情況下,一個應用程式的所有活動互相之間都有一個親和度(affinity——也就是說,他們屬於同一個任務的偏好(preference)。然而,也可以通過<activity>元素的taskAffinity屬性為每個活動設定個體親和度。定義在不同應用程式中的活動能夠共享親和度,同一個應用程式中的活動可以分配不一樣的親和度。親和度發揮作用的兩種情況:1)啟動活動的Intent物件包含FLAG_ACTIVITY_NEW_TASK標誌時;2)一個活動的allowTaskReparenting屬性為"true"時。

  • FLAG_ACTIVITY_NEW_TASK標誌

如前所述,預設情況下,一個新的活動被啟動到呼叫startActivity()方法的活動所在的任務。它被壓入呼叫它的活動的棧中。但是,如果傳遞給方法的Intent物件包含FLAG_ACTIVITY_NEW_TASK標誌,系統找一個不同的任務容納活動。通常,顧名思義它表示一個新任務。但是,他並非一定如此。如果已經存在一個任務與新活動親和度一樣,該活動將啟動到該任務。如果不是,則啟動一個新任務。

  • allowTaskReparenting屬性

如果一個活動的allowTaskReparenting屬性為"true",它可以從啟動它的任務轉移到與它有親和度並轉到前臺執行的任務中。例如,假設一個天氣預報的活動,但選擇城市是一個旅遊應用程式的一部分。它與同一個應用程式中的其他活動具有相同的親和度,且允許重新選擇父活動(reparenting)。你的一個活動啟動天氣預報活動,因此他初始是跟你的活動屬於同一個任務。但是,當旅遊應用程式切換到前臺執行時,天氣預報活動將被重新分配和顯示到該任務。

如果一個.apk檔案,從使用者的角度看包含不止一個“應用程式”,你可能要為與他們有關的些活動指定不一樣的親和度。

3、啟動模式(Launch modes)

有四種不同的啟動模式可以分配到<activity>元素的launchMody屬性:

  • "standard"(預設模式)
  • "singleTop "
  • "singleTask"
  • "singleInstance"

這些模式的在以下四方面不同:

  • 哪個任務將持有響應意圖(intent)的活動。對"standard"和"singleTop "模式,是產生意圖的任務(呼叫startActivity()方法)——除非Intent物件包含FLAG_ACTIVITY_NEW_TASK標誌。在那種情況下,像上一節親和度和新任務(Affinities and new tasks)所描述的那樣選擇一個不同的任務。
    相反,"singleTask"和"singleInstance"模式,總是將活動標記為一個任務的根活動。他們定義一個任務,而從不啟動到其他任務。
  • 活動是否可以例項化多次。"standard"或"singleTop "活動可以例項化多次。這些例項可以屬於多個任務,且一個給定任務可以包含同一個活動的多個例項。
    相反,"singleTask"和"singleInstance"活動僅可以被例項化一次。因為這些活動是一個任務的根,這個限制意味著裝置上一個時間只有不多於一個任務的例項。
  • 是否允許例項所在任務有其他活動。"singleInstance"活動所在任務只有它一個活動。如果他啟動別的活動,那些活動將啟動到不同的任務中,無論它的模式如何——就好像Intent物件包含FLAG_ACTIVITY_NEW_TASK標誌。在所有其他方面,"singleInstance"模式等同於"singleTask"模式。
    其它三種模式允許多個活動屬於一個任務。"singleTask"活動總是任務的根活動,但是它能啟動其他活動到它的任務。"standard"或"singleTop "活動的例項可以出現的棧中的任何位置。
  • 響應一個意圖時是否需要生成類的新例項。對於預設的"standard"模式,建立新的例項去響應每一個新的意圖。每個例項僅處理一個意圖。對於"singleTop "模式,一個類已存在的例項可以重新用了處理新的意圖,如果它位於目標任務的活動棧的棧頂。如果不是在棧頂,就不可以重用。相反,將建立一個新的例項並壓入棧頂。
    例如,一個任務的活動棧由根活動A、B、C和D組成,順序為A-B-C-D。當一個意圖到達請求型別D時,如果D是預設的"standard"模式,將產生D類的新例項且棧變為A-B-C-D-D。然而,如果D的啟動模式是"singleTop ",已存在的D例項將去處理新的意圖(因為它在棧頂)且棧仍然是A-B-C-D。
    如果,另一方面,到達的意圖是請求型別B時,一個B的新例項將啟動而不管B的模式是"standard"還是"singleTop "(因為B不是在棧頂),因此棧的結構為A-B-C-D-B。
    如前所述,"singleTask"和"singleInstance"活動僅可以被例項化一次,因此他們的例項將處理所有的新意圖。一個"singleInstance"活動總是在棧頂(因為僅有一個活動在任務中),因此它總是在可以處理意圖的位置。然而,一個"singleTask"活動在棧中可能有或可能沒有其他活動在它上面。如果有,即它不在處理意圖的位置,意圖會被丟棄(即使意圖被丟棄了,它的到來使任務轉到並保持在前臺執行)

當一個已存在的活動被請求處理一個新的意圖,Intent物件將通過onNewIntent()呼叫傳到活動。(產生啟動活動的意圖物件可以由getIntent()獲取。)

注意到當一個活動的新例項被建立去處理新意圖時,使用者總是可以按返回鍵返回到之前的狀態(之前的活動)。但是當一個已存在的活動例項去處理新意圖是,使用者不可以按返回鍵返回到意圖到達之前的狀態。

4、清除棧(Clearing the stack)

如果使用者離開一個任務很長時間,系統將會清除根活動之外的活動。當用戶再次返回到這個任務時,像使用者離開時一樣,僅顯示初始的活動。這個想法是,一段時間後,使用者可能已經放棄之前做的東西,及返回任務做新的事情。這是預設情況,有些活動屬性可以用來控制和改變這個行為。

  • alwaysRetainTaskState屬性
    如果在任務的根活動中這個屬性被設定為"true",剛才描述的預設行為將不會發生。任務將保留所有的活動在它的棧中,甚至是離開很長一段時間。
  • clearTaskOnLaunch屬性
    如果在任務的根活動中這個屬性被設定為"true",只有使用者離開就清除根活動之外的活動。換句話說,它與alwaysRetainTaskState截然相反。使用者總是返回到任務的初始狀態,甚至是隻離開一會。
  • finishOnTaskLaunch屬性
    這個屬性類似於clearTaskOnLaunch,但是它作用於單個活動,而不是整個任務。而且它能移除任何活動,包括根活動。當它被設定為"true",任務本次會話的活動的部分還存在,如果使用者離開並返回到任務時,它將不再存在。

有其他的方法強制從棧中移除活動。如果Intent物件包含FLAG_ACTIVITY_CLEAR_TOP標誌,目標任務已經有一個指定型別的活動例項,棧中該例項上面的其它活動將被移除而使它置於棧頂響應意圖。如果指定的活動的啟動型別是"standard",它自己也將被移除出棧,且一個新的例項將被啟動去處理到來的意圖。這是因為當模式是"standard"時,總是建立一個新的例項去處理新的意圖。

FLAG_ACTIVITY_CLEAR_TOP標誌經常與FLAG_ACTIVITY_NEW_TASK一起使用。當一起使用時,這些標誌的方式是定位到另一個任務中的已存在的活動並把它放到可以處理意圖的位置。

5、啟動任務(Starting tasks)

通過給定活動一個意圖過濾器"android.intent.action.MAIN"作為指定行為(action)和"android.intent.category.LAUNCHER"指定種類(category),活動就被設定為任務的入口點了。

它的第二個功能非常重要:使用者可以離開任務且之後可以返回到它。基於這個原因,兩個啟動模式"singleTask"和"singleInstance"標記活動總是初始化一個任務來響應意圖,僅可以使用在有MAIN和LAUNCHER過濾器的活動中。想象一下,如果沒有這個過濾器將會發生什麼:一個意圖啟動一個"singleTask"活動,開始一個新任務,使用者在任務中做一些操作。然後使用者按下HOME鍵,任務現在退到後臺執行且被主螢幕遮蔽住。而且,由於活動不在應用程式啟動器中顯示,使用者無法再返回。

類似的困難也出現在FLAG_ACTIVITY_NEW_TASK標誌。如果這個標誌導致一個活動開始一個新的任務且使用者按HOME鍵離開它,就必須要有某種方法是使用者能夠導航回來。一些實體(如通知管理器)總是在外部任務啟動活動,從不作為他們自己的一部分,因此他們總是將帶FLAG_ACTIVITY_NEW_TASK標誌的意圖傳到startActivity()方法啟動活動。如果你有活動能呼叫外部實體,可以使用此標誌,注意使用者有一個獨立的方式返回到開始的任務。

如果你希望使用者離開活動後就不能再回到這個活動,可以將<activity>元素的finishOnTaskLaunch屬性設定為"true"。可以參見清除棧那節。

程序與執行緒

1、程序

元件運行於哪個程序中由清單檔案控制。元件元素——<activity><service><receiver><provider>,都有一個process屬性可以指定元件執行在哪個程序中。這個屬性可以設定為每個元件執行在自己的程序中,或者某些元件共享一個程序而其他的不共享。他們還可以設定為不同應用程式的元件執行在同一個程序中——假設這些應用程式共享同一個Linux使用者ID且被分配了同樣的許可權。<application>元素也有process屬性,為所有的元件設定一個預設值。

所有的元件都在特定程序的主執行緒中例項化,且系統呼叫元件是由主執行緒派遣。不會為每個例項建立單獨的執行緒,因此,對應這些呼叫的方法——諸如View.onKeyDown()報告用使用者的行為和生命週期通知,總是執行在程序的主執行緒中。這意味著,沒有元件當被系統呼叫時應該執行很長時間或阻塞操作(如網路操作或迴圈計算),因為這將阻塞程序中的其它元件。你可以為長操作衍生獨立的執行緒。

public boolean onKeyDown(int keyCode,KeyEvent event):預設實現KeyEvent.Callback.onKeyMultiple(),當按下檢視的KEYCODE_DPAD_CENTER或KEYCODE_ENTER然後釋放時執行,如果檢視可用且可點選。

引數

keyCode-表示按鈕被按下的鍵碼,來自KeyEvent
event-定義了按鈕動作的KeyEvent物件

返回值

如果你處理事件,返回true;如果你想下一個接收者處理事件,返回false。

當記憶體剩餘較小且其它程序請求較大記憶體並需要立即分配,Android要回收某些程序,程序中的應用程式元件會被銷燬。當他們再次執行時,會重新開始一個程序。

當決定終結哪個程序時,Android會權衡他們對使用者重要性的相對權值。例如,與執行在螢幕可見的活動程序相比(前臺程序),它更容易關閉一個程序,它的活動在螢幕是不可見(後臺程序)。決定是否終結程序,取決於執行在程序中的元件狀態。

2、執行緒

雖然你可能會將你的應用程式限制在一個程序中,但有時候你會需要衍生一個執行緒做一些後臺工作。因為使用者介面必須很快地響應使用者的操作,所以活動寄宿的執行緒不應該做一些耗時的操作如網路下載。任何不可能在短時間完成的操作應該分配到別的執行緒。

執行緒在程式碼中是用標準的Java執行緒物件建立的,Android提供了一些方便的類來管理執行緒——Looper用於線上程中執行訊息迴圈、Handler使用者處理訊息、HandlerThread使用者設定一個訊息迴圈的執行緒。

Looper類

該類使用者線上程中執行訊息迴圈。執行緒預設沒有訊息迴圈,可以線上程中呼叫prepare()建立一個執行迴圈;然後呼叫loop()處理訊息直到迴圈結束。大部分訊息迴圈互動是通過Handler類。下面是一個典型的執行一個Looper執行緒的例子,分別使用prepare()和loop()建立一個初始的Handler與Looper互動:

class LooperThread extends Thread {
public Handler mHandler;

  public void run() {  
      Looper.prepare();  
       
      mHandler = new Handler() {  
          public void handleMessage(Message msg) {  
              // process incoming messages here  
          }  
      };  
       
      Looper.loop();  
  }  

}

2.1、遠端過程呼叫(Remote procedure calls,RPCs)

Android有一個輕量級的遠端過程呼叫機制——方法在本地呼叫卻在遠端(另外一個程序中)執行,結果返回給呼叫者。這需要將方法呼叫和它伴隨的資料分解為作業系統能夠理解的層次,從本地程序和地址空間傳輸到遠端程序和地址空間,並重新組裝呼叫。返回值以相反方向傳輸。Android提供了做這些工作的所有程式碼,這樣我們可以專注於定義和執行RPC介面本身。

一個RPC介面僅包含方法。所有的方法同步地執行(本地方法阻塞直到遠端方法執行完成),即使是沒有返回值。簡言之,該機制工作原理如下:首先,你用簡單的IDL(interface definition language,介面定義語言)宣告一個你想實現的RPC介面。從這個宣告中,aidl工具生成一個Java介面定義,提供給本地和遠端程序。它包含兩個內部類,如下圖所示:

內部類有管理你用IDL定義的介面的遠端過程呼叫所需要的所有程式碼。這兩個內部類都實現了IBinder介面。其中之一就是在本地由系統內部使用,你寫程式碼可以忽略它。另外一個是Stub,擴充套件自Binder類。除了用於有效地IPC(interprocess communication)呼叫的內部程式碼,內部類在RPC介面宣告中還包含方法宣告。你可以定義Stub的子類實現這些方法,如圖中所示。

通常情況下,遠端過程有一個服務管理(因為服務能通知系統關於程序和它連線的其它程序的資訊)。它有由aidl工具生成的介面檔案和Stub子類實現的RPC方法。服務的客戶端僅有由aidl工具生成的介面檔案。

下面介紹服務如何與它的客戶端建立連線:

  • 服務的客戶端(在本地端的)應該實現onServiceConnected() 和onServiceDisconnected() 方法,因此當與遠端服務建立連線成功和斷開連線是會通知它。然後呼叫bindService() 建立連線。
  • 服務的onBind()方法將實現為接受或拒絕連線,者取決於它接受到的意圖(該意圖傳送到binServive())。如果連線被接受,它返回一個Stub子類的例項。
  • 如果服務接受連線,Android呼叫客戶端的onServiceConnected()方法且傳遞給它一個IBinder物件,返回由服務管理的Stub子類的一個代理。通過代理,客戶端可以呼叫遠端服務。

這裡只是簡單地描述,省略了一些RPC機制的細節。你可以查閱相關資料或繼續關注Android開發之旅,後面將為你奉上。

2.2、執行緒安全方法

在一些情況下,你實現的方法可能會被不止一個執行緒呼叫,因此必須寫成執行緒安全的。這對遠端呼叫方法是正確的——如上一節討論的RPC機制。當從IBinder程序中呼叫一個IBinder物件中實現的一個方法,這個方法在呼叫者的執行緒中執行。然而,當從別的程序中呼叫,方法將在Android維護的IBinder程序中的執行緒池中選擇一個執行,它不在程序的主執行緒中執行。例如,一個服務的onBind()方法在服務程序的主執行緒中被呼叫,在onBind()返回的物件中執行的方法(例如,實現RPC方法的Stub子類)將線上程池中被呼叫。由於服務可以有一個以上的客戶端,所以同時可以有一個以上的執行緒在執行同一個IBinder方法。因此,IBinder的方法必須是執行緒安全的。

同樣,一個內容提供者可以接受其它程序產生的資料請求。雖然ContentResolver 和 ContentProvider 類隱藏程序通訊如何管理的,對應哪些請求的ContentResolver 方法——query()、insert()、delete()、update()、getType(),在內容提供者的程序的執行緒池中被呼叫,而不是在這一程序的主執行緒中。因為這些方法可以同時從任意數量的執行緒中呼叫,他們也必須實現為執行緒安全的。

元件的生命週期

1、活動生命週期

一個活動有三個基本狀態:

啟用狀態或執行狀態,這時它執行在螢幕的前臺(處於當前任務活動棧的最上面)。這個活動有使用者的操作的焦點。
暫停狀態,這時活動失去焦點但是它對使用者仍可見。也就是說,另一個活動在它的上面且那個活動是透明的或者沒有覆蓋整個螢幕,因此通過它可以看見暫停狀態的活動。一個暫停的活動完全是活著的(它維護著所有的狀態和成員資訊,且仍然依附在視窗管理器),但是當記憶體極小時可以被系統殺掉。
停止狀態,這時活動完全被其他活動掩蓋。它仍然保留所有狀態和成員資訊,但是對使用者它不可見,因此它的視窗時隱藏的且當其他地方需要記憶體時它往往被系統殺掉。

如果一個活動被暫停或停止,系統可以將它從記憶體移除,通過要求它結束(通過呼叫它的finish()方法),或簡單地殺掉它的程序。當它再次顯示給使用者時,必須要完全重新啟動和恢復到之前的狀態。隨著活動從一個狀態轉為另一個狀態,通過呼叫下面的受保護的方法通知該改變:

void onCreate(Bundle saveInstanceState)
void onStart()
void onRestart()
void onResume()
void onPause()
void onStop()
void onDestroy()

所有這些方法都是鉤子,你可以重寫當狀態改變時做適當的工作。所有的活動必須要實現onCreate()去做一些初始化的設定,當物件第一次例項化的時候。很多活動也會實現onPause()去提交資料修改或準備停止與使用者互動。

將他們合併在一起,這七個方法定義了活動的整個生命週期。有三個巢狀的迴圈,你可以通過這七個方法監視:

活動的整個生命時間,從第一次呼叫onCreate()開始直到呼叫onDestroy()結束。一個活動在onCreate()中做所有的“全域性”狀態的初始設定,在onDestroy()中釋放所有保留的資源。舉例來說,有一個執行緒執行在後臺從網路上下載資料,它可能會在onCreate()中建立執行緒,在onDestroy()中結束執行緒。
活動的可視生命時間,從呼叫onStart()到相應的呼叫onStop() 。在這期間,使用者可以在螢幕上看見活動,雖然它可能不是執行在前臺且與使用者互動。在這兩個方法之間,你可以保持顯示活動所需要的資源。舉例來說,你可以在onStart()中註冊一個廣播接收者監視影響你的UI的改變,在onStop() 中登出。因為活動在可視和隱藏之間來回切換,onStart()和onStop()  可以呼叫多次。
活動的前臺生命時間,從呼叫onResume()到相應的呼叫onPause()。在這期間,頻繁地在重用和暫停狀態轉換——例如,當裝置進入睡眠狀態或一個新的活動啟動時呼叫onPause(),當一個活動返回或一個新的意圖被傳輸時呼叫onResume()。因此,這兩個方法的程式碼應當是相當輕量級的。

下面這個圖解釋了這三個迴圈和狀態之間狀態的可能路徑。著色的橢圓表示活動的主要狀態,矩形表示當活動在狀態之間轉換時你可以執行的回撥方法。

下面的表格對每個方法更詳細的描述和在活動的整個生命週期中的定位。

注意上面表格的Killable列,它表示當方法返回時沒有執行活動的其它程式碼,系統是否能殺死活動寄宿的程序。三個方法(onPause()、onStop()、onDestroy())標記為Yes。因為onPause()是唯一一個保證在程序被殺之前會呼叫的,因此你應該使用onPause()來寫任何持久化儲存資料。

被標記為No的方法保護活動寄宿的程序在他們呼叫的時候不會被殺掉。因此活動是可殺掉狀態,例如onPause()返回到onResume()呼叫期間。直到onPause()再次返回,活動是不可殺掉的。其實,沒有標記為Killable的活動也是可以系統被殺掉的,不過這僅僅發生在極端困難的情況下,沒有有任何其他資源可用。
2、儲存活動狀態

當系統而不是使用者關閉一個活動來節省記憶體時,使用者可能希望返回到活動且是它之前的狀態。為了獲得活動被殺之前的狀態,你可以執行活動的onSaveInstanceState()方法。Android在活動容易被銷燬前呼叫這個方法,也就是呼叫onPause()之前。該方法的引數是一個Bundle物件,在這個物件你可以以名值對記錄活動的動態狀態。當活動再次啟動時,Bundle同時被傳遞到onCreate()和呼叫onCreate()之後的方法,onRestoreInstanceState(),因此使用一個或兩個可以重新建立捕獲的狀態。

因為onSaveInstanceState()方法不總是被呼叫,你應該僅使用onSaveInstanceState()它來記錄活動的臨時狀態,而不是持久的資料。應該使用onPause()來儲存持久資料。
3、協調活動

當一個活動啟動另一個活動,他們都經歷生命週期轉換。一個暫停或許是停止,然而另一個啟動。有時,你可能需要協調這些活動。生命週期的回撥順序是明確界定的,特別是當這兩個活動在同一個程序中:

當前活動的onPause()方法被呼叫。
接下來,啟動活動的onCreate()、onStart()、onResume()方法按序被呼叫。
然後,如果獲得不再在螢幕上可見,它的onStop()方法被呼叫。

2、服務生命週期

一個服務可以用在兩個方面:

  • 它可以啟動且允許一直執行直到有人停止它,或者它自己停止。在這種模式,通過呼叫Context.startService()啟動服務及通過呼叫Context.stopService()停止服務。服務也可以通過呼叫Service.stopSelf()或Service.stopSelfResult()停止自己。僅需要呼叫一次stopService()停止服務,而不管呼叫startService()了多少次。
  • 通過使用相關介面可以程式設計地操作服務。客戶端建立與Service物件的一個連線及使用該連線調入服務。連線通過呼叫Context.bindService()建立,通過呼叫Context.unbindService()關閉。多個客戶端可以繫結到同一個服務。如果服務尚未啟動,bindService()可以選擇啟動它。

這兩種模式並不是完全分離的。你可以繫結到一個用startService()啟動的服務。例如,一個後臺音樂服務可以通過使用定義了音樂播放的Intent物件呼叫startService()啟動。直到後來,使用者可能想對播放器做一些控制或者獲取當前歌曲的一些資訊,一個活動將呼叫bindService()與服務建立連線。在這種情況下,實際上直到最後一個繫結關閉stopService()並不會停止。

像活動一樣,一個服務也有生命週期方法,你可以執行監視它的狀態改變。但是比活動的生命週期方法更少,只有三個且它們是公有的(public)而不是受保護的(protected)(說明:活動的生命週期方法是protected的):

  • void onCreate()
  • void onStart(Intent intent)
  • void onDestory()

通過這三個方法,你可以監視服務生命週期的兩個巢狀迴圈:

  • 服務的整個生命時間(entire lifetime),從呼叫onCreate()到相應地呼叫onDestory()。像一個活動一樣,服務在onCreate()中做一些初始設定,且在中釋放所有的資源。例如,一個音樂播放服務可以在onCreate()中建立執行緒,然後在onDestory()中停止執行緒。
  • 服務的活躍生命時間(active lifetime),從呼叫onStart()開始。這個方法傳遞引數是傳送給startService()的Intent物件。音樂服務將開啟Intent,瞭解播放哪個音樂並且開始播放。
    沒有相應的回撥方法,因為服務停止沒有onStop()方法。

startService()和onDestory()被所有服務呼叫,不管是通過Context.startService()啟動還是通過Context.bindService()啟動的。然而,onStart()僅被通過startService()啟動的服務呼叫。

如果一個服務允許別的繫結到它,有一些額外的回撥方法來實現它:

  • IBinder onBind(Intent intent)
  • boolean onUnbind(Intent intent)
  • void onRebind(Intent intent)

onBind()回撥傳遞的引數是傳給bindService()的Intent物件,onUnbind()回撥傳遞的引數是傳給unbindService()的Intent物件。如果服務允許繫結,onBind()返回客戶端與服務互動的通訊通道。onUnbind()方法可以要求呼叫onRebind(),如果一個新的客戶端連線到服務。

下圖解釋了服務的回撥方法。雖然,它分離了由startService()啟動的服務和由bindService()啟動的服務,記住任何服務,無論它怎麼啟動的,都可能允許客戶端繫結到它,因此任何服務可能接收onBind()和onUnbind()呼叫。

3、廣播接收者生命週期

一個廣播接收者有一個回撥方法:void onReceive(Context curContext, Intent broadcastMsg)。當一個廣播訊息到達接收者是,Android呼叫它的onReceive()方法並傳遞給它包含訊息的Intent物件。廣播接收者被認為僅當它執行這個方法時是活躍的。當onReceive()返回後,它是不活躍的。

有一個活躍的廣播接收者的程序是受保護的,不會被殺死。但是系統可以在任何時候殺死僅有不活躍元件的程序,當佔用的記憶體別的程序需要時。

這帶來一個問題,當一個廣播訊息的響應時費時的,因此應該在獨立的執行緒中做這些事,遠離使用者介面其它元件執行的主執行緒。如果onReceive()衍生執行緒然後返回,整個程序,包括新的執行緒,被判定為不活躍的(除非程序中的其它應用程式元件是活躍的),將使它處於被殺的危機。解決這個問題的方法是onReceive()啟動一個服務,及時服務做這個工作,因此係統知道程序中有活躍的工作在做。