轉自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-actvt/
Activity 的生命週期
和 J2ME 的 MIDlet 一樣,在 android 中,Activity 的生命週期交給系統統一管理。與 MIDlet 不同的是安裝在 android 中的所有的 Activity 都是平等的。
Activity 的狀態及狀態間的轉換
在 android 中,Activity 擁有四種基本狀態:
- Active/Runing一個新 Activity 啟動入棧後,它在螢幕最前端,處於棧的最頂端,此時它處於可見並可和使用者互動的啟用狀態。
- Paused 當 Activity 被另一個透明或者 Dialog 樣式的 Activity 覆蓋時的狀態。此時它依然與視窗管理器保持串連,系統繼續維護其內部狀態,所以它仍然可見,但它已經失去了焦點故不可與使用者互動。
- Stoped 當 Activity 被另外一個 Activity 覆蓋、失去焦點並不可見時處於 Stoped狀態。
- Killed Activity 被系統殺死回收或者沒有被啟動時處於 Killed狀態。
當一個 Activity 執行個體被建立、銷毀或者啟動另外一個 Activity 時,它在這四種狀態之間進行轉換,這種轉換的發生依賴於使用者程式的動作。說明了 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 的狀態與它在棧中的位置關係
如上所示,除了最頂層即處在 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(); } |
這些方法的說明如下:
- protected void onCreate(Bundle savedInstanceState)一個 Activity 的執行個體被啟動時調用的第一個方法。一般情況下,我們都覆蓋該方法作為應用程式的一個進入點,在這裡做一些初始化資料、設定使用者介面等工作。大多數情況下,我們都要在這裡從 xml 中載入設計好的使用者介面。例如:
setContentView(R.layout.main); |
當然,也可從 savedInstanceState中讀我們儲存到存放裝置中的資料,但是需要判斷 savedInstanceState是否為 null,因為 Activity 第一次啟動時並沒有資料被存貯在裝置中:
if(savedInstanceState!=null){ savedInstanceState.get("Key"); } |
- protected void onStart()該方法在 onCreate() 方法之後被調用,或者在 Activity 從 Stop 狀態轉換為 Active 狀態時被調用。
- protected void onResume()在 Activity 從 Pause 狀態轉換到 Active 狀態時被調用。
- protected void onResume()在 Activity 從 Active 狀態轉換到 Pause 狀態時被調用。
- protected void onStop()在 Activity 從 Active 狀態轉換到 Stop 狀態時被調用。一般我們在這裡儲存 Activity 的狀態資訊。
- protected void onDestroy()在 Active 被結束時調用,它是被結束時調用的最後一個方法,在這裡一般做些釋放資源,清理記憶體等工作。
圖 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 的匹配過程
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); |
回頁首
下載
描述 |
名字 |
大小 |
下載方法 |
範例代碼 |
EX01.zip |
21KB |
HTTP |
關於下載方法的資訊
參考資料
- Google Android 的開發人員網站:http://developer.android.com/。
- 中國移動 OPhone 網站關於 OPhne 的文章:http://www.ophonesdn.com/article/show/32。
- 訪問 developerWorks Open source 專區 獲得豐富的 how-to 資訊、工具和項目更新以及 最受歡迎的文章和教程,協助您用開放源碼技術進行開發,並將它們與 IBM 產品結合使用。