本文詳細介紹了Android應用編程中Activity的生命週期、通信方式和IntentFilter等內容,並提供了一些日常開發中經常用到的關於Activity的技巧和方法。通過本文,你可以進一步了接Android中Activity的運作方式。
詳解Android的Activity組件
Activity的生命週期
和J2ME的MIDlet一樣,在android中,Activity的生命週期交給系統統一管理。與MIDlet不同的是安裝在android中的所有的Activity都是平等的。
Activity的狀態及狀態間的轉換
在android中,Activity擁有四種基本狀態:
Active/Runing一個新Activity啓動入棧後,它在屏幕最前端,處於棧的最頂端,此時它處於可見並可和用戶交互的激活狀態。
Paused當Activity被另一個透明或者Dialog樣式的Activity覆蓋時的狀態。此時它依然與窗口管理器保持連接,系統繼續維護其內部狀態,所以它仍然可見,但它已經失去了焦點故不可與用戶交互。
Stoped當Activity被另外一個Activity覆蓋、失去焦點並不可見時處於Stoped狀態。
KilledActivity被系統殺死回收或者沒有被啓動時處於Killed狀態。
當一個Activity實例被創建、銷燬或者啓動另外一個Activity時,它在這四種狀態之間進行轉換,這種轉換的發生依賴於用戶程序的動作。下圖說明了Activity在不同狀態間轉換的時機和條件:
圖1.Activity的狀態轉換
如上所示,Android程序員可以決定一個Activity的“生”,但不能決定它的“死”,也就時說程序員可以啓動一個Activity,但是卻不能手動的“結束”一個Activity。當你調用Activity.finish()方法時,結果和用戶按下BACK鍵一樣:告訴ActivityManager該Activity實例完成了相應的工作,可以被“回收”。隨後ActivityManager激活處於棧第二層的Activity並重新入棧,同時原Activity被壓入到棧的第二層,從Active狀態轉到Paused狀態。例如:從Activity1中啓動了Activity2,則當前處於棧頂端的是Activity2,第二層是Activity1,當我們調用Activity2.finish()方法時,ActivityManager重新激活Activity1併入棧,Activity2從Active狀態轉換Stoped狀態,Activity1.onActivityResult(intrequestCode,intresultCode,Intentdata)方法被執行,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(); }
這些方法的說明如下:
protectedvoidonCreate(BundlesavedInstanceState)一個Activity的實例被啓動時調用的第一個方法。一般情況下,我們都覆蓋該方法作爲應用程序的一個入口點,在這裏做一些初始化數據、設置用戶界面等工作。大多數情況下,我們都要在這裏從xml中加載設計好的用戶界面。例如:
setContentView(R.layout.main);
當然,也可從savedInstanceState中讀我們保存到存儲設備中的數據,但是需要判斷savedInstanceState是否爲null,因爲Activity第一次啓動時並沒有數據被存貯在設備中:
if(savedInstanceState!=null){ savedInstanceState.get("Key"); }
protectedvoidonStart()該方法在onCreate()方法之後被調用,或者在Activity從Stop狀態轉換爲Active狀態時被調用。
protectedvoidonResume()在Activity從Pause狀態轉換到Active狀態時被調用。
protectedvoidonResume()在Activity從Active狀態轉換到Pause狀態時被調用。
protectedvoidonStop()在Activity從Active狀態轉換到Stop狀態時被調用。一般我們在這裏保存Activity的狀態信息。
protectedvoidonDestroy()在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的IntentFilter
IntentFilter描述了一個組件願意接收什麼樣的Intent對象,Android將其抽象爲android.content.IntentFilter類。在Android的AndroidManifest.xml配置文件中可以通過<intent-filter>節點爲一個Activity指定其IntentFilter,以便告訴系統該Activity可以響應什麼類型的Intent。
當程序員使用startActivity(intent)來啓動另外一個Activity時,如果直接指定intent了對象的Component屬性,那麼ActivityManager將試圖啓動其Component屬性指定的Activity。否則Android將通過Intent的其它屬性從安裝在系統中的所有Activity中查找與之最匹配的一個啓動,如果沒有找到合適的Activity,應用程序會得到一個系統拋出的異常。這個匹配的過程如下:
圖4.Activity種IntentFilter的匹配過程
Action匹配
Action是一個用戶定義的字符串,用於描述一個Android應用程序組件,一個IntentFilter可以包含多個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"/>
如果在IntentFilter中指定了這些屬性,那麼只有所有的屬性都匹配成功時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);