詳解 Android 的 Activity 元件【Z】

liuchang0001發表於2011-04-27

 

詳解 Android 的 Activity 元件

Activity 的生命週期

和 J2ME 的 MIDlet 一樣,在 android 中,Activity 的生命週期交給系統統一管理。與 MIDlet 不同的是安裝在 android 中的所有的 Activity 都是平等的。

Activity 的狀態及狀態間的轉換

在 android 中,Activity 擁有四種基本狀態:

  1. Active/Runing 一個新 Activity 啟動入棧後,它在螢幕最前端,處於棧的最頂端,此時它處於可見並可和使用者互動的啟用狀態。
  2. Paused 當 Activity 被另一個透明或者 Dialog 樣式的 Activity 覆蓋時的狀態。此時它依然與視窗管理器保持連線,系統繼續維護其內部狀態,所以它仍然可見,但它已經失去了焦點故不可與使用者互動。
  3. Stoped 當 Activity 被另外一個 Activity 覆蓋、失去焦點並不可見時處於 Stop ed 狀態。
  4. Killed Activity 被系統殺死回收或者沒有被啟動時處於 Killed 狀態。

當一個 Activity 例項被建立、銷燬或者啟動另外一個 Activity 時,它在這四種狀態之間進行轉換,這種轉換的發生依賴於使用者程式的動作。下圖說明了 Activity 在不同狀態間轉換的時機和條件:


圖 1. Activity 的狀態轉換
圖 1. Activity 的狀態轉換

如上所示,Android 程式設計師可以決定一個 Activity 的“生”,但不能決定它的“死”,也就時說程式設計師可以啟動一個 Activity,但是卻不能手動的“結束”一個 Activity。當你呼叫 Activity.finish() 方 法時,結果和使用者按下 BACK 鍵一樣:告訴 Activity Manager 該 Activity 例項完成了相應的工作,可以被“回收”。隨後 Activity Manager 啟用處於棧第二層的 Activity 並重新入棧,同時原 Activity 被壓入到棧的第二層,從 Active 狀態轉到 Paused 狀態。例如:從 Activity1 中啟動了 Activity2,則當前處於棧頂端的是 Activity2,第二層是 Activity1,當我們呼叫 Activity2.finish() 方法時,Activity Manager 重新啟用 Activity1 併入棧,Activity2 從 Active 狀態轉換 Stoped 狀態,Activity1. onActivityResult(int requestCode, int resultCode, Intent data) 方法被執行,Activity2 返回的資料通過 data 引數返回給 Activity1。

Activity 棧

Android 是通過一種 Activity 棧的方式來管理 Activity 的,一個 Activity 的例項的狀態決定它在棧中的位置。處於前臺的 Activity 總是在棧的頂端,當前臺的 Activity 因為異常或其它原因被銷燬時,處於棧第二層的 Activity 將被啟用,上浮到棧頂。當新的 Activity 啟動入棧時,原 Activity 會被壓入到棧的第二層。一個 Activity 在棧中的位置變化反映了它在不同狀態間的轉換。Activity 的狀態與它在棧中的位置關係如下圖所示:


圖 2. Activity 的狀態與它在棧中的位置關係
圖 2. Activity 的狀態與它在棧中的位置關係

如上所示,除了最頂層即處在 Active 狀態的 Activity 外,其它的 Activity 都有可能在系統記憶體不足時被回收,一個 Activity 的例項越是處在棧的底層,它被系統回收的可能性越大。系統負責管理棧中 Activity 的例項,它根據 Activity 所處的狀態來改變其在棧中的位置。

Activity 生命週期

android.app.Activity 類中,Android 定義了一系列與生命週期相關的方法,在我們自己的 Activity 中,只是根據需要複寫需要的方法,Java 的多型性會保證我們自己的方法被虛擬機器呼叫,這一點與 J2ME 中的 MIDlet 類似。

 public class OurActivity extends Activity { 
    protected void onCreate(Bundle savedInstanceState); 
    protected void onStart(); 
    protected void onResume(); 
    protected void onPause(); 
    protected void onStop(); 
    protected void onDestroy(); 
 } 

 

這些方法的說明如下:

  1.  
    1.  
      1. protected void onCreate(Bundle savedInstanceState) 一個 Activity 的例項被啟動時呼叫的第一個方法。一般情況下,我們都覆蓋該方法作為應用程式的一個入口點,在這裡做一些初始化資料、設定使用者介面等工作。大多數情況下,我們都要在這裡從 xml 中載入設計好的使用者介面。例如:
 setContentView(R.layout.main); 

 

當然,也可從 savedInstanceState 中讀我們儲存到儲存裝置中的資料,但是需要判斷 savedInstanceState 是否為 null ,因為 Activity 第一次啟動時並沒有資料被存貯在裝置中:

 if(savedInstanceState!=null){ 
 savedInstanceState.get("Key"); 
 } 

 

  1.  
    1.  
      1. protected void onStart() 該方法在 onCreate() 方法之後被呼叫,或者在 Activity 從 Stop 狀態轉換為 Active 狀態時被呼叫。
      2. protected void onResume() 在 Activity 從 Pause 狀態轉換到 Active 狀態時被呼叫。
      3. protected void onResume() 在 Activity 從 Active 狀態轉換到 Pause 狀態時被呼叫。
      4. protected void onStop() 在 Activity 從 Active 狀態轉換到 Stop 狀態時被呼叫。一般我們在這裡儲存 Activity 的狀態資訊。
      5. protected void onDestroy() 在 Active 被結束時呼叫,它是被結束時呼叫的最後一個方法,在這裡一般做些釋放資源,清理記憶體等工作。


圖 3. 這些方法的呼叫時機
圖 3. 這些方法的呼叫時機

此外,Android 還定義了一些不常用的與生命週期相關的方法可用:

 protected void onPostCreate(Bundle savedInstanceState); 
 protected void onRestart(); 
 protected void onPostResume(); 

 

Android 提供的文件詳細的說明了它們的呼叫規則。

建立一個 Activity

在 android 中建立一個 Activity 是很簡單的事情,編寫一個繼承自 android.app.Activity 的 Java 類並在 AndroidManifest.xml 宣告即可。下面是一個為了研究 Activity 生命週期的一個 Activity 例項(工程原始碼見下載):

Activity 檔案:

 public class EX01 extends Activity { 
    private static final String LOG_TAG = EX01.class.getSimpleName(); 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);      
        setContentView(R.layout.main); 
        Log.e(LOG_TAG, "onCreate"); 
    } 
   @Override 
    protected void onStart() { 
        Log.e(LOG_TAG, "onStart"); 
        super.onStart(); 
    } 
    @Override 
    protected void onResume() { 
        Log.e(LOG_TAG, "onResume"); 
        super.onResume(); 
    } 
    @Override 
    protected void onPause() { 
        Log.e(LOG_TAG, "onPause"); 
        super.onPause(); 
    } 
    @Override 
    protected void onStop() { 
        Log.e(LOG_TAG, "onStop"); 
        super.onStop(); 
    } 
    @Override 
    protected void onDestroy() { 
        Log.e(LOG_TAG, "onDestroy "); 
        super.onDestroy(); 
    } 
 } 

 

AndroidManifest.xml 中通過 <activity> 節點說明 Activity,將 apk 檔案安裝後,系統根據這裡的說明來查詢讀取 Activity,本例中的說明如下:

 <activity android:name=".EX01" android:label="@string/app_name"> 
	 <intent-filter> 
		 <action android:name="android.intent.action.MAIN" /> 
		 <category android:name="android.intent.category.LAUNCHER" /> 
	 </intent-filter> 
 </activity> 

 

啟動另外一個 Activity

Activity.startActivity() 方法可以根據傳入的引數啟動另外一個 Activity:

 Intent intent =new Intent(CurrentActivity.this,OtherActivity.class); 
 startActivity(intent); 

 

當然,OtherActivity 同樣需要在 AndroidManifest.xml 中定義。

Activity 之間通訊

使用 Intent 通訊

在 Android 中,不同的 Activity 例項可能執行在一個程式中,也可能執行在不同的程式中。因此我們需要一種特別的機制幫助我們在 Activity 之間傳遞訊息。Android 中通過 Intent 物件來表示一條訊息,一個 Intent 物件不僅包含有這個訊息的目的地,還可以包含訊息的內容,這好比一封 Email,其中不僅應該包含收件地址,還可以包含具體的內容。對於一個 Intent 物件,訊息“目的地”是必須的,而內容則是可選項。

在上面的例項中通過 Activity. startActivity(intent) 啟動另外一個 Activity 的時候,我們在 Intent 類的構造器中指定了“收件人地址”。

如果我們想要給“收件人”Activity 說點什麼的話,那麼可以通過下面這封“e-mail”來將我們訊息傳遞出去:

 Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);
  // 建立一個帶“收件人地址”的 email 
 Bundle bundle =new Bundle();// 建立 email 內容
 bundle.putBoolean("boolean_key", true);// 編寫內容
 bundle.putString("string_key", "string_value"); 
 intent.putExtra("key", bundle);// 封裝 email 
 startActivity(intent);// 啟動新的 Activity 

 

那麼“收件人”該如何收信呢?在 OtherActivity 類的 onCreate() 或者其它任何地方使用下面的程式碼就可以開啟這封“e-mail”閱讀其中的資訊:

 Intent intent =getIntent();// 收取 email 
 Bundle bundle =intent.getBundleExtra("key");// 開啟 email 
 bundle.getBoolean("boolean_key");// 讀取內容
 bundle.getString("string_key"); 

 

上面我們通過 bundle 物件來傳遞資訊,bundle 維護了一個 HashMap<String, Object> 物件,將我們的資料存貯在這個 HashMap 中來進行傳遞。但是像上面這樣的程式碼稍顯複雜,因為 Intent 內部為我們準備好了一個 bundle ,所以我們也可以使用這種更為簡便的方法:

 Intent intent =new Intent(EX06.this,OtherActivity.class); 
 intent.putExtra("boolean_key", true); 
 intent.putExtra("string_key", "string_value"); 
 startActivity(intent); 

 

接收:

 Intent intent=getIntent(); 
 intent.getBooleanExtra("boolean_key",false); 
 intent.getStringExtra("string_key"); 

 

使用 SharedPreferences

SharedPreferences 使用 xml 格式為 Android 應用提供一種永久的資料存貯方式。對於一個 Android 應用,它存貯在檔案系統的 /data/ data/your_app_package_name/shared_prefs/ 目錄下,可以被處在同一個應用中的所有 Activity 訪問。Android 提供了相關的 API 來處理這些資料而不需要程式設計師直接操作這些檔案或者考慮資料同步問題。

 // 寫入 SharedPreferences 
 SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); 
 Editor editor = preferences.edit(); 
 editor.putBoolean("boolean_key", true); 
 editor.putString("string_key", "string_value"); 
 editor.commit(); 
        
 // 讀取 SharedPreferences 
 SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); 
 preferences.getBoolean("boolean_key", false); 
 preferences.getString("string_key", "default_value"); 

 

其它方式

Android 提供了包括 SharedPreferences 在內的很多種資料存貯方式,比如 SQLite,檔案等,程式設計師可以通過這些 API 實現 Activity 之間的資料交換。如果必要,我們還可以使用 IPC 方式。

Activity 的 Intent Filter

Intent Filter 描述了一個元件願意接收什麼樣的 Intent 物件,Android 將其抽象為 android.content.IntentFilter 類。在 Android 的 AndroidManifest.xml 配置檔案中可以通過 <intent-filter > 節點為一個 Activity 指定其 Intent Filter,以便告訴系統該 Activity 可以響應什麼型別的 Intent。

當程式設計師使用 startActivity(intent) 來啟動另外一個 Activity 時,如果直接指定 intent 了物件的 Component 屬性,那麼 Activity Manager 將試圖啟動其 Component 屬性指定的 Activity。否則 Android 將通過 Intent 的其它屬性從安裝在系統中的所有 Activity 中查詢與之最匹配的一個啟動,如果沒有找到合適的 Activity,應用程式會得到一個系統丟擲的異常。這個匹配的過程如下:


圖 4. Activity 種 Intent Filter 的匹配過程
圖 4. Activity 種 Intent Filter 的匹配過程

Action 匹配

Action 是一個使用者定義的字串,用於描述一個 Android 應用程式元件,一個 Intent Filter 可以包含多個 Action。在 AndroidManifest.xml 的 Activity 定義時可以在其 <intent-filter > 節點指定一個 Action 列表用於標示 Activity 所能接受的“動作”,例如:

 <intent-filter > 
 <action android:name="android.intent.action.MAIN" /> 
 <action android:name="com.zy.myaction" /> 
……
 </intent-filter> 

 

如果我們在啟動一個 Activity 時使用這樣的 Intent 物件:

 Intent intent =new Intent(); 
 intent.setAction("com.zy.myaction"); 

 

那麼所有的 Action 列表中包含了“com.zy.myaction ”的 Activity 都將會匹配成功。

Android 預定義了一系列的 Action 分別表示特定的系統動作。這些 Action 通過常量的方式定義在 android.content. Intent 中,以“ACTION_ ”開頭。我們可以在 Android 提供的文件中找到它們的詳細說明。

URI 資料匹配

一個 Intent 可以通過 URI 攜帶外部資料給目標元件。在 <intent-filter > 節點中,通過 <data/> 節點匹配外部資料。

mimeType 屬性指定攜帶外部資料的資料型別,scheme 指定協議,host、port、path 指定資料的位置、埠、和路徑。如下:

 <data android:mimeType="mimeType" android:scheme="scheme" 
 android:host="host" android:port="port" android:path="path"/> 

 

如果在 Intent Filter 中指定了這些屬性,那麼只有所有的屬性都匹配成功時 URI 資料匹配才會成功。

Category 類別匹配

<intent-filter > 節點中可以為元件定義一個 Category 類別列表,當 Intent 中包含這個列表的所有專案時 Category 類別匹配才會成功。

一些關於 Activity 的技巧

鎖定 Activity 執行時的螢幕方向

Android 內建了方向感應器的支援。在 G1 中,Android 會根據 G1 所處的方向自動在豎屏和橫屏間切換。但是有時我們的應用程式僅能在橫屏 / 豎屏時執行,比如某些遊戲,此時我們需要鎖定該 Activity 執行時的螢幕方向,<activity > 節點的 android:screenOrientation 屬性可以完成該項任務,示例程式碼如下:

 <activity android:name=".EX01"
 android:label="@string/app_name" 
 android:screenOrientation="portrait">// 豎屏 , 值為 landscape 時為橫屏
…………
 </activity> 

 

全屏的 Activity

要使一個 Activity 全屏執行,可以在其 onCreate() 方法中新增如下程式碼實現:

 // 設定全屏模式
 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
    WindowManager.LayoutParams.FLAG_FULLSCREEN); 
 // 去除標題欄
 requestWindowFeature(Window.FEATURE_NO_TITLE); 

 

在 Activity 的 Title 中加入進度條

為了更友好的使用者體驗,在處理一些需要花費較長時間的任務時可以使用一個進度條來提示使用者“不要著急,我們正在努力的完成你交給的任務”。如下圖:

在 Activity 的標題欄中顯示進度條不失為一個好辦法,下面是實現程式碼:

 // 不明確進度條
 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 
 setContentView(R.layout.main); 
 setProgressBarIndeterminateVisibility(true); 

 // 明確進度條
 requestWindowFeature(Window.FEATURE_PROGRESS); 
 setContentView(R.layout.main); 
 setProgress(5000); 

相關文章