Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 基礎總結:(四)Activity(InstanceState)

Android 基礎總結:(四)Activity(InstanceState)

編輯:關於Android編程

本文介紹Android中關於Activity的兩個神秘方法:onSaveInstanceState()和onRestoreInstanceState(),並且在介紹這兩個方法之後,再分別來實現使用InstanceState保存和恢復數據功能、Android實現屏幕旋轉異步下載效果這樣兩個示例。

首先來介紹onSaveInstanceState()和onRestoreInstanceState()。關於這兩個方法,一些朋友可能在Android開發過程中很少用到,但在有時候掌握其用法會幫我們起到比較好的效果。尤其是在應用程序在不知道的情況下退出後,如何實現其數據保存的功能。先來讓我們看下這兩個方法的有什麼樣的作用。

 

1. 基本作用

  Activity的onSaveInstanceState()和onRestoreInstanceState()並不是生命周期方法,它們不同於onCreate()、onPause()等生命周期方法,它們並不一定會被觸發。當應用遇到意外情況(如:內存不足、用戶直接按Home鍵)由系統銷毀一個Activity時,onSaveInstanceState()會被調用。但是當用戶主動去銷毀一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被調用。因為在這種情況下,用戶的行為決定了不需要保存Activity的狀態。通常onSaveInstanceState()只適合用於保存一些臨時性的狀態,而onPause()適合用於數據的持久化保存。
在activity被殺掉之前調用保存每個實例的狀態,以保證該狀態可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (傳入的Bundle參數是由onSaveInstanceState封裝好的)中恢復。這個方法在一個activity被殺死前調用,當該activity在將來某個時刻回來時可以恢復其先前狀態。
例如,如果activity B啟用後位於activity A的前端,在某個時刻activity A因為系統回收資源的問題要被殺掉,A通過onSaveInstanceState將有機會保存其用戶界面狀態,使得將來用戶返回到activity A時能通過onCreate(Bundle)或者onRestoreInstanceState(Bundle)恢復界面的狀態。
關於onSaveInstanceState (),是在函數裡面保存一些View有用的數據到一個Parcelable對象並返回。在Activity的onSaveInstanceState(Bundle outState)中調用View的onSaveInstanceState (),返回Parcelable對象,
接著用Bundle的putParcelable方法保存在Bundle savedInstanceState中。
當系統調用Activity的的onRestoreInstanceState(Bundle savedInstanceState)時, 同過Bundle的getParcelable方法得到Parcelable對象,然後把該Parcelable對象傳給View的onRestoreInstanceState (Parcelable state)。在的View的onRestoreInstanceState中從Parcelable讀取保存的數據以便View使用。

這就是onSaveInstanceState() 和 onRestoreInstanceState() 兩個函數的基本作用和用法。

 

2. onSaveInstanceState() 什麼時候調用

  先看Application Fundamentals上的一段話:
Android calls onSaveInstanceState() before the activitybecomes vulnerable to being destroyed by the system, but does not bothercalling it when the instance is actually being destroyed by a user action (suchas pressing the BACK key).
從這句話可以知道,當某個activity變得"容易"被系統銷毀時,該activity的onSaveInstanceState()就會被執行,除非該activity是被用戶主動銷毀的,例如當用戶按BACK鍵的時候。
注意上面的雙引號,何為"容易"?意思就是說該activity還沒有被銷毀,而僅僅是一種可能性。這種可能性有哪些?通過重寫一個activity的所有生命周期的onXXX方法,包括onSaveInstanceState()和onRestoreInstanceState() 方法,我們可以清楚地知道當某個activity(假定為activity A)顯示在當前task的最上層時,其onSaveInstanceState()方法會在什麼時候被執行,有這麼幾種情況:
(1)、當用戶按下HOME鍵時。
這是顯而易見的,系統不知道你按下HOME後要運行多少其他的程序,自然也不知道activity A是否會被銷毀,因此系統會調用onSaveInstanceState(),讓用戶有機會保存某些非永久性的數據。以下幾種情況的分析都遵循該原則
(2)、長按HOME鍵,選擇運行其他的程序時。
(3)、按下電源按鍵(關閉屏幕顯示)時。
(4)、從activity A中啟動一個新的activity時。
(5)、屏幕方向切換時,例如從豎屏切換到橫屏時。
在屏幕切換之前,系統會銷毀activity A,在屏幕切換之後系統又會自動地創建activity A,所以onSaveInstanceState()一定會被執行,且也一定會執行onRestoreInstanceState()。

總而言之,onSaveInstanceState()的調用遵循一個重要原則,即當系統存在“未經你許可”時銷毀了我們的activity的可能時,則onSaveInstanceState()會被系統調用,這是系統的責任,因為它必須要提供一個機會讓你保存你的數據(當然你不保存那就隨便你了)。如果調用,調用將發生在onPause()或onStop()方法之前。(雖然測試時發現多數在onPause()前)

 

3. onRestoreInstanceState()什麼時候調用

  onRestoreInstanceState()被調用的前提是,activity A“確實”被系統銷毀了,而如果僅僅是停留在有這種可能性的情況下,則該方法不會被調用,例如,當正在顯示activity A的時候,用戶按下HOME鍵回到主界面,然後用戶緊接著又返回到activity A,這種情況下activity A一般不會因為內存的原因被系統銷毀,故activity A的onRestoreInstanceState方法不會被執行 此也說明上二者,大多數情況下不成對被使用。

onRestoreInstanceState()在onStart() 和 onPostCreate(Bundle)之間調用。

 

4. onSaveInstanceState()方法的默認實現

  如果我們沒有覆寫onSaveInstanceState()方法, 此方法的默認實現會自動保存activity中的某些狀態數據, 比如activity中各種UI控件的狀態.。android應用框架中定義的幾乎所有UI控件都恰當的實現了onSaveInstanceState()方法,因此當activity被摧毀和重建時, 這些UI控件會自動保存和恢復狀態數據. 比如EditText控件會自動保存和恢復輸入的數據,而CheckBox控件會自動保存和恢復選中狀態.開發者只需要為這些控件指定一個唯一的ID(通過設置android:id屬性即可), 剩余的事情就可以自動完成了.如果沒有為控件指定ID, 則這個控件就不會進行自動的數據保存和恢復操作。

由上所述, 如果我們需要覆寫onSaveInstanceState()方法, 一般會在第一行代碼中調用該方法的默認實現:super.onSaveInstanceState(outState)。

 

5. 是否需要重寫onSaveInstanceState()方法

  既然該方法的默認實現可以自動的保存UI控件的狀態數據, 那什麼時候需要覆寫該方法呢?
如果需要保存額外的數據時,就需要覆寫onSaveInstanceState()方法。大家需要注意的是:onSaveInstanceState()方法只適合保存瞬態數據,比如UI控件的狀態,成員變量的值等,而不應該用來保存持久化數據,持久化數據應該當用戶離開當前的activity時,在onPause()中保存(比如將數據保存到數據庫或文件中)。說到這裡,還要說一點的就是在onPause()中不適合用來保存比較費時的數據,所以這點要理解。

由於onSaveInstanceState()方法方法不一定會被調用,因此不適合在該方法中保存持久化數據,例如向數據庫中插入記錄等.。保存持久化數據的操作應該放在onPause()中。若是永久性值,則在onPause()中保存;若大量,則另開線程吧,別阻塞UI線程。

 

6. 引發activity銷毀和重建的其它情況

  除了系統處於內存不足的原因會摧毀activity之外,某些系統設置的改變也會導致activity的摧毀和重建。例如改變屏幕方向(見上例),改變設備語言設定,鍵盤彈出等。

另外,當屏幕的方向發生了改變,Activity會被摧毀並且被重新創建,如果你想在Activity被摧毀前緩存一些數據,並且在Activity被重新創建後恢復緩存的數據。可以重寫Activity的 onSaveInstanceState() 和 onRestoreInstanceState()方法,如下代碼所示:

 

import android.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
/**
 * Android使用InstanceState保存和恢復數據
 */
public class MainActivity extends Activity {
	private String message = "";
	private EditText text = null;
	private Button button = null;
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		text = (EditText) findViewById(R.id.editText1);
		button = (Button) findViewById(R.id.btnSave);
		button.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Toast.makeText(getApplicationContext(), "保存",
						Toast.LENGTH_SHORT).show();
			}
		});
	}
	@Override
	public void onResume() {
		super.onResume();
		Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG)
				.show();
	}
	@Override
	public void onSaveInstanceState(Bundle savedInstanceState) {
		super.onSaveInstanceState(savedInstanceState);
		savedInstanceState.putString("message", text.getText().toString());
	}
	@Override
	public void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		message = savedInstanceState.getString("message");
	}
}
還有需要注意的是,onSaveInstanceState()方法並不是一定會被調用的,因為有些場景是不需要保存狀態數據的。比如用戶按下BACK鍵退出activity時,用戶顯然想要關閉這個activity,此時是沒有必要保存數據以供下次恢復的,也就是onSaveInstanceState()方法不會被調用。如果調用onSaveInstanceState()方法,調用將發生在onPause()或onStop()方法之前。
代碼如下所示:

 

 

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
	savedInstanceState.putBoolean("MyBoolean", true);
	savedInstanceState.putDouble("myDouble", 1.9);
	super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
	super.onRestoreInstanceState(savedInstanceState);
	boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
	double myDouble = savedInstanceState.getDouble("myDouble");
}
我們可以用旋轉屏幕來測試我們的程序保存狀態的能力。因為一般旋轉時,activity會被重新執行onCreate(),onStart()等操作,程序此時能夠保存狀態是很重要的。可以考慮使用該方法保存數據。當然了在旋轉屏幕時我們除了使用onSaveInstanceState()外,還可以使用onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()這兩個方法來保存切換屏幕的狀態。與onSaveInstanceState()不同的是,onRetainNonConfigurationInstance()和getLastNonConfigurationInstance() 方法主要用於屏幕之間的旋轉操作時保存數據。
如下代碼所示:
@Override
public Object onRetainNonConfigurationInstance() {
	// 在這裡設置需要保存的內容,在切換時不是bundle了,我們可以直接通過object來代替。
	returnsuper.onRetainNonConfigurationInstance();
}
在恢復屏幕時可以不使用onRestoreInstanceState(),而使用getLastNonConfigurationInstance()來代替。我們可以直接在oncreate()方法中獲取上次保存的對象。
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
	// TODO Auto-generated method stub
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	// 獲取上次切換屏幕保存的對象
	Object obj = getLastNonConfigurationInstance();
}

對於我們的程序而言,或多或少的都需要進行Activity之間的跳轉操作,其中有一些是為了獲得系統中的資源或是一些必要信息,而一般是通過啟動Activity(常用startActivity()和startActivityForResult()函數)來進行操作。在這個跳轉的期間,我們當前的Activity暫時失去了焦點,處於不可操作狀態,可在此之前先通過onSaveInstanceState()方法來保存一些暫時時的數據。當回到先前的Activity時,先前的Activity重新獲取了焦點,系統就是觸發onRestoreInstanceState()方法,可獲取失去焦點前的一些數據。onRetainNonConfigurationInstance()方法也具有類似的功能來處理這樣的數據操作。先前說過,onRetainNonConfigurationInstance()方法主要是用於屏幕的旋轉操作。
說到這裡了,可能有的人就要問了,既然onSaveInstanceState()和onRetainNonConfigurationInstance()都可以實現保存數據的功能,如果是兩個同時使用時,執行順序是哪個在先,哪個在後呢?根據Android官方網站上介紹,如果兩個方法同時出現時,onSaveInstanceState()方法執行在先,而onRetainNonConfigurationInstance()方法執行在後。它們的執行順序都在onStop()和onDestroy()之間,關於這點需要我們大家注意。
之前在其它網站上看到有的朋友說:"onSaveInstanceState()和onRetainNonConfigurationInstance()既然都可以實現保存數據的功能,而且onSaveInstanceState()相比onRetainNonConfigurationInstance()方法可以實現更多情況下的數據保存功能,那麼onRetainNonConfigurationInstance()豈不是多余的嗎?"。關於這點,從設計的角度看,onRetainNonConfigurationInstance()並不是多余的函數。一般情況下,如果我們要保存的數據不太大,而且適合放在Bundle中,那麼使用onSaveInstanceState()是比較合適的;如果要保存的數據不適合放在Bundle中(比如:一個socket)或是數據比較大(比如:一個Bitmap),那麼這個時間我們就應該使用onRetainNonConfigurationInstance(),而且我們使用onRetainNonConfigurationInstance()可以保存任何類型的對象,像AsyncTask和SQLiteDatabse,我們都可以進行保存。這些類型的數據可能會被一個新的Activity實例所重新使用。所以onSaveInstanceState()和onRetainNonConfigurationInstance()在我們的程序中扮演的是不同的角色,需要在不同的時機下調用,用來處理不同類型的數據。
例如下面代碼所示要保存一個復雜的數據:

 

import android.graphics.Bitmap;
public class DataHolder {
	int a;
	Bitmap b;
	String s;
	public Object onRetainNonConfigurationInstance() {
		DataHolder dh = new DataHolder();
		dh.a = a;
		dh.b = b;
		dh.s = s;
		return dh;
	}
}

 

不過呢,onRetainNonConfigurationInstance()在新版本的SDK中是一個過時的方法,我們可以用setRetainInstance(boolean)來代替onRetainNonConfigurationInstance(),在舊的平台中我們仍然可以使用onRetainNonConfigurationInstance()。如果是部分朋友不知道如何使用setRetainInstance(boolean)來保存自定義的對象數據,可以使用onRetainCustomNonConfigurationInstance()來代表onRetainNonConfigurationInstance(),同時使用getLastCustomNonConfigurationInstance()代替getLastNonConfigurationInstance()。

下面是一個使用onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()來實現屏幕旋轉時異步下載更新進度條並保存數據的效果,代碼如下所示:
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
/**
 * Android實現屏幕旋轉異步下載效果
 * 
 * @Description: Android實現屏幕旋轉異步下載效果
 * 
 * @File: RotationAsyncActivity.java
 * 
 * @Package com.rotation.demo
 * 
 * @Author Hanyonglu
 * 
 * @Date 2012-03-28 下午08:14:57
 * 
 * @Version V1.0
 */
public class RotationAsyncActivity extends Activity {
	// 進度條
	private ProgressBar progressBar = null;
	// 異步任務類
	private RotationAsyncTask asyncTask = null;
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		progressBar = (ProgressBar) findViewById(R.id.progress);
		// 獲取對象
		asyncTask = (RotationAsyncTask) getLastNonConfigurationInstance();
		if (asyncTask == null) {
			asyncTask = new RotationAsyncTask(this);
			asyncTask.execute();
		} else {
			asyncTask.attach(this);
			updateProgress(asyncTask.getProgress());
			if (asyncTask.getProgress() >= 100) {
				markAsDone();
			}
		}
	}
	/**
	 * 保存對象
	 */
	@Override
	public Object onRetainNonConfigurationInstance() {
		asyncTask.detach();
		return asyncTask;
	}
	private void updateProgress(int progress) {
		progressBar.setProgress(progress);
	}
	private void markAsDone() {
		findViewById(R.id.completed).setVisibility(View.VISIBLE);
	}
	// 異步任務類
	private static class RotationAsyncTask extends AsyncTask {
		private RotationAsyncActivity activity = null;
		private int progress = 0;
		/**
		 * 默認的構造器
		 */
		public RotationAsyncTask() {
			// TODO Auto-generated constructor stub
		}
		/**
		 * 帶參構造器
		 * 
		 * @param activity
		 */
		public RotationAsyncTask(RotationAsyncActivity activity) {
			attach(activity);
		}
		@Override
		protected Void doInBackground(Void... unused) {
			for (int i = 0; i < 20; i++) {
				SystemClock.sleep(500);
				publishProgress();
			}
			return null;
		}
		@Override
		protected void onProgressUpdate(Void... unused) {
			if (activity == null) {
				Log.w("RotationAsyncActivity", "onProgressUpdate()");
			} else {
				progress += 5;
				activity.updateProgress(progress);
			}
		}
		@Override
		protected void onPostExecute(Void unused) {
			if (activity == null) {
				Log.w("RotationAsyncActivity", "onPostExecute()");
			} else {
				activity.markAsDone();
			}
		}
		protected void detach() {
			activity = null;
		}
		protected void attach(RotationAsyncActivity activity) {
			this.activity = activity;
		}
		protected int getProgress() {
			return progress;
		}
	}
}
我們在運行示例時就會發現,無論怎樣旋轉屏幕都不會影響進度條的更新與下載,需要解釋下的是這裡我並沒有設置下載功能,有興趣或需要的朋友自己添加即可。實現效果圖如下所示:

以上就是在Android中關於InstanceState保存數據和恢復數據的過程,在這裡我想再重復一遍:onSaveInstanceState()和onRestoreInstanceState()機制來保存數據時,它僅在非用戶顯式的指令殺死應用程序時保存和恢復數據。我們可以使用它在我們的程序中來保存數據,可以作為保存數據的一種方式,但在使用過程中需要注意其使用原理和方法。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved