本篇隨筆將詳細的講解Activity儲存狀態的概念,也就是saving activity state。
一、Activity狀態保持概念
儲存Activity的狀態是非常重要的,例如我們在玩一個遊戲的時候,突然來了一個電話,這個時候在接聽完電話之後我們返回到遊戲中,這個時候我們希望遊戲還是之前那個進度,或者說發生突發事件,遊戲這個應用程式被關閉了,這個時候我們如果再重新開啟遊戲的話,我們如果還是希望回到之前的進度,我們就需要將其狀態儲存起來,這樣在Activity的摧毀時,我們還能夠根據儲存的狀態回到之前的進度。這就是Activity的狀態儲存。
二、兩種方式的情況下Activity的狀態會被儲存
Activity的狀態被儲存通常有兩種方式,我們首先通過android的官方文件提供的圖來看一下這兩種方式:
1.當一個Activity位於另一個Activity的前面時,也就是另一個Activity處於stop狀態,這個時候這個Activity仍然佔用著記憶體,並且保持著Activity的狀態,如果此時點選後退按鈕,那麼此時第一個Activity又會重新回到前臺介面上,此時這個Activity會保持原來的狀態,我們不需要重新獲得其狀態。
2.當我們的這個Activity處於stop狀態在後臺時,如果此時有一個優先順序別更高的Activity需要獲得資源,此時系統可能會破壞處於stop狀態的Activity,回收其記憶體,此時這個Activity物件會被destroyed,此時如果我們必須呼叫一個 onSaveInstanceState() 方法來儲存我們的Activity的物件狀態。
onSaveInstanceState(Bundle outState)這個方法接受一個Bundle型別引數,我們可以將我們需要儲存的狀態通過Bundle的 putString, putInt 方法儲存起來。
當我們的Activity處於極易被摧毀的時候,系統會呼叫 onSaveInstanceState() 方法,如果此時系統殺死了這個Activity的執行緒,這個Activity物件被destroy後,再開啟這個Activity時,又會重新建立這個Activity,這個時候系統會將 onSaveInstanceState 方法中的 Bundle 物件傳遞給Activity的 onCreate()和 onRestoreInstanceState() 方法,
使用這兩個方法中的任何一個,我們都可以根據之前儲存的 Bundle 物件來恢復我們Activity之前的狀態。
三、onSaveInstanceState方法
protected void onSaveInstanceState (Bundle outState)
下面我們具體看看這個方法,通過這個方法我們可以在一個Activity被殺死時,並在將來如果要重新建立這個Activity時可以恢復其儲存的狀態。我們不需要疑惑這個方法和Activity生命週期函式方法的呼叫時期,例如onPause()方法,當一個Activity處於後臺時或者容易受到破壞時,這個方法就會被呼叫。
有兩種情況是不會呼叫這個onSaveInstanceState方法的:
①activity B 位於 activity A的前面,此時如果點選 Back 按鈕,activity B 會分別呼叫 onPause、onStop方法,此時系統並不會呼叫 onSaveInstanceState() 方法,因為此時是我們顯示的關閉activity B,所以系統認為呼叫 onSaveInstanceState() 是沒有並要的。
②activity B 位於 activity A的前面,此時activity A處於後臺狀態,但是還是佔用了記憶體資源,當通過Back 按鈕,使得activity A重新回到前臺時,onSaveInstanceState()方法也是沒有必要呼叫的,因為此時activity A本身就完整的儲存了當前的狀態。
接下來我們通過一個例項來看看通過Activity的 onSaveInstanceState() 、onCreate()以及onRestoreInstanceState()方法的呼叫來保持我們Activity的狀態。
public class ThirdActivity extends Activity { private final String TAG = "ThirdActivity"; private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.third); Log.i(TAG, "ThirdActivity onCreate"); if(savedInstanceState != null) { String name = (String)savedInstanceState.getString("name"); Toast.makeText(ThirdActivity.this, "onCreate ---> " + name, 1).show(); } button = (Button)findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(); intent.setClass(ThirdActivity.this, FourthActivity.class); startActivity(intent); } }); } @Override protected void onStart() { super.onStart(); Log.i(TAG, "ThirdActivity onStart"); } @Override protected void onResume() { super.onResume(); Log.i(TAG, "ThirdActivity onResume"); } @Override protected void onPause() { super.onPause(); Log.i(TAG, "ThirdActivity onPause"); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.i(TAG, "ThirdActivity onSaveInstanceState"); outState.putString("name", "xiaoluo"); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.i(TAG, "ThirdActivity onRestoreInstanceState"); if(savedInstanceState != null) { String name = (String)savedInstanceState.getString("name"); Toast.makeText(ThirdActivity.this, "onRestoreInstanceState ---> " + name, 1).show(); } } @Override protected void onStop() { super.onStop(); Log.i(TAG, "ThirdActivity onStop"); } @Override protected void onRestart() { super.onRestart(); Log.i(TAG, "ThirdActivity onRestart"); } @Override protected void onDestroy() { super.onDestroy(); Log.i(TAG, "ThirdActivity onDestroy"); } }
我們看到,在這個Activity中,我們實現了其 onSaveInstanceState()、onCreate()和onRestoreInstanceState()方法,我們在 onSaveInstanceState() 方法中將當前的Activity的狀態儲存下來:
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.i(TAG, "ThirdActivity onSaveInstanceState"); outState.putString("name", "xiaoluo"); }
然後在onCreate()方法和onRestoreInstanceState() 方法中試圖得到儲存的Bundle物件,當Activity第一次被建立的時候,onCreate()和onRestoreInstanceState()方法中的Bundle物件是null的。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.third); Log.i(TAG, "ThirdActivity onCreate"); if(savedInstanceState != null) { String name = (String)savedInstanceState.getString("name"); Toast.makeText(ThirdActivity.this, "onCreate ---> " + name, 1).show(); } button = (Button)findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(); intent.setClass(ThirdActivity.this, FourthActivity.class); startActivity(intent); } }); }
@Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.i(TAG, "ThirdActivity onRestoreInstanceState"); if(savedInstanceState != null) { String name = (String)savedInstanceState.getString("name"); Toast.makeText(ThirdActivity.this, "onRestoreInstanceState ---> " + name, 1).show(); } }
我們在這兩個方法裡分別使用 Toast 的彈出框來看看是否能將Bundle儲存的狀態值列印出來。我們為了模擬這個實驗,需要通過將手機螢幕的橫豎屏進行切換。
當螢幕的方向被改變的時候,系統會首先destroy然後recreate這個Activity物件來根據我們配置的資原始檔重新載入介面,這個時候儲存我們的Activity的狀態是非常重要的,因為在大多數情況下,螢幕放心的改變是經常發生的事,所以這個時候我們必須通過 onSaveInstanceState() 方法來儲存我們的Activity的狀態。
我們來看看實驗結果:
我們看到,當我們反轉螢幕的時候,因為之前已經通過 onSaveInstanceState()方法儲存了Activity的狀態,所以在Activity從destroy到recreate時,會將儲存的Bundle物件傳給onCreate和onRestoreInstanceState方法,此時我們就能夠恢復我們Activity的狀態了。
四、Android View控制元件的onSaveInstanceState()方法
當我們在建立一個Activity物件的時候,我們如果沒有重寫父類的 onSaveInstanceState()方法,此時我們的一些Activity狀態也會通過呼叫父類Activity的預設的 onSaveInstanceState()方法來儲存下來。特別地:父類的onSaveInstanceState()方法會呼叫佈局檔案中每一個View物件的相應的 onSaveInstanceState()方法 來保持各自的狀態。在Android的大多數的widget控制元件都非常好的實現了 onSaveInstanceState()方法,因此我們對這些空間的值的改變都會被自動的儲存下來。例如我們的EditText、Checkbox控制元件,當我們在輸入了我們的值只會,當Activity被destroy-->recreate的時候,此時我們的值仍然會被儲存下來,前提是:如果我們需要儲存一個View控制元件的狀態,我們必須要給其指定一個唯一的識別符號(通過 android:id 屬性來指定),如果我們沒有指定的話,系統則不會儲存其狀態。例如我們來看一下下面這個例子:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:text="usenrame"/> <EditText android:id="@+id/username" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/textView1" android:inputType="text"/> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/username" android:textSize="20sp" android:text="email"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/username" android:layout_toRightOf="@id/textView2" android:inputType="textEmailAddress"/> <CheckBox android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="100dp" android:text="籃球"/> <CheckBox android:id="@+id/checkBox2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/checkBox1" android:layout_alignTop="@id/checkBox1" android:text="足球"/> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/checkBox2" android:layout_alignTop="@id/checkBox1" android:text="網球"/> </RelativeLayout>
我們這個Activity的介面一共有5個View控制元件,其中username這個EditText我們指定了ID,email這個EditText沒有指定ID,而下面的三個CheckBox,只有最後一個CheckBox沒有為其指定ID,我們來看看,當這個Activity被重新建立時,其會不會儲存每個View控制元件的狀態:
我們在兩個文字框中輸入了值,然後將三個CheckBox都勾選上,此時我們翻轉我們的螢幕:
我們看到,因為username這個EditText和前兩個CheckBox我們給其指定了ID,所以系統會呼叫其 onSaveInstanceState() 方法來儲存我們的View控制元件狀態,而對於email這個EditText和最後一個CheckBox,我們沒有指定ID識別符號,所以系統不會自動為其儲存狀態。
注意:儘管預設的Activity的onSaveInstanceState() 方法會儲存我們的View控制元件的狀態,但是我們仍然推薦重新其onSaveInstanceState() 方法來儲存我們額外的一些Activity的狀態,在分別重寫 onCreate()、onSaveInstanceState() 和 onRestoreInstanceState()方法時,我們要首先呼叫父類的方法才行,這樣就會預設的儲存我們View控制元件的狀態了。
最後再總結一句:因為 onSaveInstanceState() 方法不能保證一定會被呼叫,所以我們在onSaveInstanceState() 方法中只能用來儲存我們的Activity的臨時的狀態資訊,而對於要持久化儲存的物件或狀態,我們應該在 onPause() 方法中來做。