Activity整理總結(1)
1. Activity的生命週期
Activity說的通俗一點,一個Activity對應手機螢幕上的一個頁面,就像一個網頁一樣。Activity什麼時候能被看到?是不是一鎖屏,Activity就要被銷燬釋放?要回答這些問題,就涉及到Activity的生命週期。
1. Activity任務棧
android系統用任務棧來管理Activity。當建立一個新的Activity後,此Activity將被加入到任務棧的棧頂,從而先前在棧頂的Activity就被新建Activity壓到下一層,其他Activity依次往下移動一層;反之,當棧頂的Activity被銷燬移出任務棧後,任務棧下一層的Activity就會依次上升一層。
2. Activity四種狀態
1) 執行狀態(Running):當Activity位於棧頂時,此時Activity對使用者可見並且可互動;
2) 暫停狀態(Paused):當Activity不在棧頂,但對使用者仍然可見,此時Activity介面失去了焦點無法與使用者進行互動,例如,棧頂的Activity是透明的或者棧頂Activity並不是鋪滿整個手機螢幕;
3) 停止狀態(Stopped):此時Activity介面對使用者完全不可見也不可互動;
4) 銷燬狀態(Destroyed):此時Activity介面被銷燬,等待資源被回收 。
3. Activity生命週期
在每個不同的狀態階段,Android系統會自動對Activity內相應的方法進行回撥,呼叫順序看下圖:

說明:
1. 從Activity建立到執行態,系統會依次回撥如下方法:onCreate->onStart->onResume。
2. onCreate和onDestroy配對,onStart和onStop配對,onResume和onPause配對。
1)onCreate和onDestroy:在回撥onCreate之後,系統會為Activity分配資源,在呼叫onDestroy之後系統才能回收這些資源。因此,介面初始化工作一般都放在onCreate裡完成,而釋放資源的工作應該放在onDestroy裡。
2)onStart和onStop:當Activity執行onCreate後,介面對使用者依然是不可見的,只有執行onStart回撥方法後,Activity才對使用者可見;直到系統回撥onStop方法後,Activity對使用者才不可見了 。
3)onResume和onPause:當Activity執行onResume回撥方法時,Activity就可以響應使用者互動;當回撥onPause方法後,Activity就不再響應使用者互動。
3. 其它狀態回到執行態:
1) 當前如果處於onPause狀態,那麼要回到執行狀態系統就只會回撥:onResume。
2) 當前如果處於onStop狀態,那麼要回到執行狀態系統就會依次回撥:onRestart->onStart->onResume
3) 當前如果處於onDestory狀態,那麼要回到執行狀態系統就會依次回撥:onCreate->onStart->onResume
4. 開啟新的Activity2的時候,先前在棧頂的Activity1,這二者回調方法執行順序如下
Activity1: onPause
Activity2: onCreate
Activity2: onStart
Activity2: onResume
Activity1: onStop
2. Activity啟動模式
我們可能希望跳轉到原來建立過的某個Activity例項,但是這個Activity例項如果不在任務棧的棧頂,這時就要再建立一個新的Activity例項並將其壓入棧頂,這樣就可能產生大量重複的 Activity,有時我們不希望這樣。這時就需要我們為 Activity 配置特定的啟動模式,而不是使用預設的啟動模式。設定啟動模式的位置在AndroidManifest.xml 檔案中 Activity的android:launchMode 屬性
1. standard模式
這是預設模式,無需設定,每次啟用Activity時都會建立Activity例項,並放入任務棧中。相當於入棧,按back鍵返回到前一個Activity相當於退棧。
2. singleTop模式
如果在任務的棧頂正好存在該Activity的例項,就重用該例項( 會呼叫例項的 onNewIntent()),否則就會建立新的例項並放入棧頂,即使棧中已經存在該Activity的例項,只要不在棧頂,都會建立新的例項。可用來解決棧頂多個重複相同的Activity的問題
3. singleTask模式
如果在棧中已經有該Activity的例項,就重用該例項(會呼叫例項的 onNewIntent() )。重用時,會讓該例項回到棧頂,因此在它上面的例項將會被移出棧。如果棧中不存在該例項,將會建立新的例項放入棧中。singleTask 模式可以用來退出整個應用。將主Activity設為SingTask模式,然後在要退出的Activity中轉到主Activity,然後重寫主Activity的onNewIntent函式,並在函式中加上一句finish。
4. singleInstance模式
在一個新棧中建立該Activity的例項,並讓多個應用共享該棧中的該Activity例項。一旦該模式的Activity例項已經存在於某個棧中,任何應用再啟用該Activity時都會重用該棧中的例項( 會呼叫例項的 onNewIntent() )。其效果相當於多個應用共享一個應用,不管誰啟用該 Activity 都會進入同一個應用中。
3. Activity之間資料傳遞
3.1 通過Intent傳遞
這是一種比較規範的資料傳遞方式。首先,在要傳送資料的Activity物件中建立一個Intent(意圖)物件,然後可以將Integer、String、Float、Double等簡單資料型別或可序列化物件(使用者自己定義的序列化物件,記住自定義物件必須是序列化的)儲存在Intent物件中,然後啟動另一個,在另一個Activity中先獲取Intent物件,再通過Intent物件來獲得這些資料。示例程式碼如下:
1. 傳送資料
1. 在當前要傳送資料的Activity物件(即CurrentActivity)中,建立Intent物件
Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);//CurrentActivity.this代表傳送者,OtherActivity.class代表接收者
2. 將要傳送的資料放入Intent物件,有兩種方式:
a. 直接將資料放入Intent物件,以key-value對方式存入。獲取資料時,是用key來獲取value。有多個數據可以全部用這種方式逐個存入Intent物件。
intent.putExtra("age", 5); //前面的"age"是個key,5代表存入一個整數的value
intent.putExtra("price",1.5f); //再存入一個Float型別的資料
b. 先建立Bundle物件,將資料放入Bundle物件,然後將Bundle物件再放入Intent物件
Bundle bundle =newBundle();// 建立Bundle
bundle.putBoolean("male", true);//放入一個布林型資料
bundle.putString("name","Jack"); //再放入一個字串資料
intent.putExtras(bundle); //將Bundle物件存入Intent物件中
3. 啟動新Activity(就是要接收資料的Activity)
startActivity(intent);
2. 獲取資料
在新開啟的Activity物件(對應上面的OtherActivity)中,獲取Intent物件,然後通過Intent獲取資料,也有兩種方式:
a. 直接通過Intent物件來獲取
Intent intent =getIntent(); // 獲取上一個Activity傳遞過來的Intent物件,getIntent方法是Activity自帶的
int age = intent.getIntExtra("age",0); //"age"這個key要與傳送的一致才能接收到資料,後面的0表示如果讀取失敗時,就使用這個預設值,保證獲取到的資料不為空。
boolean sex = intent.getBooleanExtra("male",true); //後面的true表示如果讀取失敗時的預設值
b. 先獲取Bundle物件,在通過Bundle物件獲取資料
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String name = bundle.getString("name");
float price = bundle.getFloat("price", 0f);
注:不論前面用何種方式傳送資料,接受資料時都可以用上面這兩種方式的任意一種。
3. 傳遞自定義物件
首先,自定義物件必須是可序列化的,就是自定義物件必須是實現Serializable或者Parcelable(序列化細節,在此不展開了)。如下定義一個序列化物件User:
public class User implements Serializable{
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
publicvoid setName(String name) {
this.name = name;
}
}
傳送物件時,可以這樣:
User user = new User("Tom");
Intent intent = new Intent(SendActivity.this, ReceiveActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("user", user);
intent.putExtras(bundle);
startActivity(intent);
接收時,可以這樣獲取User物件:
Intent intent = getIntent();
User user= (User)intent.getSerializableExtra("user");
4. 返回Activity的資料傳遞
上面的方法都是說從Activity1跳到新建的Activity2怎麼傳遞資料,如果現在要關閉Activity2並返回到Activity1,這時要帶資料回Activity1怎麼辦呢?android給Activity提供了一套方法來實現這個目的。
1. 從Activity1跳轉到Activity2:
Intent intent =new Intent();
intent = intent.setClass(Activity1.this, Activity2.class);
Bundle bundle =new Bundle();
bundle.putString("name", "GG");
intent.putExtras(bundle);
startActivityForResult(intent,1249); //只有這裡不同。這裡的1249表示請求碼,可以隨意設定一個整數,如果有多個跳轉到不同Activity的startActivityForResult方法被呼叫時,為了區分,每個跳轉都要設定不同的請求碼。
2. 從Activity2返回資料到Aactivity1:
Intent intent =new Intent(Activity2.this, Aactivity1.class);
Bundle bundle =new Bundle();
bundle.putInt("resultData","Activity2返回的資料");
intent.putExtras(bundle);
this.setResult(RESULT_OK, intent); //RESULT_OK是返回代表處理結果的狀態碼
this.finish(); //關閉Activity2
3. 在Activity1中重寫Activity的onActivityResault方法,並在方法裡接收資料:
@override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1249){//先判斷請求碼是否匹配
switch(resultCode) {//根據狀態碼,處理返回結果
case RESULT_OK:
Bundle bundle =data.getExtras();//獲得資料
String result = bundle.getInt("resultData"); // 這個key要與Activity2中設定的一致
break;
......
default:break;
}
} else if(requestCode==xxxx){ //xxxx表示別的請求碼
......
}
}
3.2 通過全域性物件傳遞資料
如果某些資料需要經常使用,要長時間駐留記憶體(例如使用者的基本資訊,比如使用者id、使用者名稱、頭像等,在程式執行時要經常用到),一般這種資料定義為全域性物件,將該物件定義在Application類中。因為Application代表App本身,在App初始化時就會被建立,並且在程式執行過程中一直都在,可以保證資料物件不會被輕易釋放。首先需要自定義一個類,該類繼承Android的Application類,例如命名為MyApplication類;然後在MyApplication中定義資料物件,在編寫完自定義的MyApplication類後,還需要開啟AndroidManifest配置檔案,在<application>標籤中指定全域性類名name為MyApplication,這樣App啟動時系統會使用我們自定義的MyApplication類來建立Application物件。
public class User{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name= name;
}
}
public class MyApplication extends Application{
private User user;
public String getUser(){
return this.user;
}
public void setUser(String name){
this.user = new User();
this.user.setName(name);
}
}
public class Activity1 extends Activity{
private MyApplication application;
public void onCreate(Bundle savedInstanceState){
......
application = (MyApplication) getApplication();
application.setUser("Mary");
}
}
public class Activity2 extends Activity{
private MyApplication application;
private User user;
protected void onCreate(Bundle savedInstanceState){
......
application = (MyApplication) getApplication();
user= application.getUser();
}
}
這個在Activity1中初始化過的User物件,在任何可以獲取Application物件的地方都可以再次獲得這個User物件。
有些人說可以在Activity中使用靜態變數來傳遞資料,個人建議儘量不要在此使用靜態變數,這種操作很可能導致資料不一致,靜態變數會打破物件的封裝性。
5. Activity現場儲存狀態
一般情況下,呼叫Activity的onPause()和onStop()方法後,Activity例項其實仍然存在於記憶體中,Activity例項的所有信息和狀態資料不會被銷燬,當Activity重新回到執行態後,例項的狀態資料都會得到保留。但是當系統記憶體不足時, 呼叫onPause()和onStop()方法後的Activity可能會在某個時刻被系統銷燬,此時記憶體中沒有該Activity的例項了,當用戶重新開啟這個Activity,就要新建一個例項,而這個例項就沒有先前例項的狀態資料。為了解決這個問題,我們可以在Activity被殺掉之前儲存例項的當前狀態,可以通過覆寫Activity的onSaveInstanceState()方法來實現這個目的。
如果開發者沒有覆寫onSaveInstanceState()方法,此方法的預設實現會自動儲存activity中的某些狀態資料,比如activity中各種UI控制元件的狀態。android應用框架中定義的幾乎所有UI控制元件都恰當的實現了onSaveInstanceState()方法,因此當activity被摧毀和重建時,這些UI控制元件會自動儲存和恢復狀態資料。比如EditText控制元件會自動儲存和恢復輸入的資料,而CheckBox控制元件會自動儲存和恢復選中狀態。開發者只需要為這些控制元件指定一個唯一的ID(通過設定android:id屬性即可),剩餘的事情就可以自動完成了。如果沒有為控制元件指定ID,則這個控制元件就不會進行自動的資料儲存和恢復操作。所以複寫onSaveInstanceState方法,多用來儲存Activity中的一些成員變數值。
如果我們複寫了onSaveInstanceState方法,呼叫過程大致如下:
1. onSaveInstanceState(Bundle outState)方法有一個Bundle型別的引數outState,可以將Activity的狀態資料儲存到這個Bundle物件中,onSaveInstanceState方法會在系統銷燬Activity前被系統回撥,並且在回撥onPause()或onStop()方法之前呼叫;
2. 當該activity在將來某個時刻被重建時,系統在回撥onCreate(Bundle savedInstanceState)或者onRestoreInstanceState(Bundle savedInstanceState)方法時,我們就可以用傳進來的Bundle引數savedInstanceState來恢復Activity的狀態。
public class MyActivity extends Activity{
private String restoreData;
//恢復資料
@Override public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(savedInstanceState !=null) {
restoreData= savedInstanceState.getString("restoreData");//獲取要恢復的資料
}
//恢復資料
@Override
public void onRestoreInstanceState(Bundle saveInstanceState){
super.onRestoreInstanceState( saveInstanceState);
restoreData= saveInstanceState.getString("restoreData");
}
// 儲存狀態資料
@Override
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putString("restoreData", restoreData);
}
……
}
注意:
1. onSaveInstanceState()方法只適合儲存Activity與使用者互動的瞬態資料,比如UI控制元件的狀態,成員變數的值等。若要向資料庫中插入記錄等這種儲存持久化資料的操作,應該放在onPause()中做。
2. onSaveInstanceState()不是Activity生命週期的方法,不是什麼情況下都會被呼叫的。系統呼叫onSaveInstanceState遵循一個重要原則:即當系統“未經你許可”時銷燬了你的Activity,則onSaveInstanceState會被系統呼叫,這是系統的責任,因為它必須要提供一個機會讓你儲存你的資料。onSaveInstanceState方法在下面幾種情況下會系統被呼叫:
1) 當用戶按下HOME鍵時;
2) 長按HOME鍵,選擇執行其他的程式時;
3) 按下電源按鍵(關閉螢幕顯示)時;
4) 從Activity A中啟動一個新的Activity時;
5) 螢幕方向切換時,例如從豎屏切換到橫屏時。