onSaveInstanceState和onRestoreInstanceState

當某個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一定會被執行。 
總而言之,onSaveInstanceState的調用遵循一個重要原則,即當系統“未經你許可”時銷燬了你的activity,則onSaveInstanceState會被系統調用,這是系統的責任,因爲它必須要提供一個機會讓你保存你的數據(當然你不保存那就隨便你了)。 
至於onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成對的被調用的,onRestoreInstanceState被調用的前提是,activity A“確實”被系統銷燬了,而如果僅僅是停留在有這種可能性的情況下,則該方法不會被調用,例如,當正在顯示activity A的時候,用戶按下HOME鍵回到主界面,然後用戶緊接着又返回到activity A,這種情況下activity A一般不會因爲內存的原因被系統銷燬,故activity A的onRestoreInstanceState方法不會被執行。 

另外,onRestoreInstanceState的bundle參數也會傳遞到onCreate方法中,你也可以選擇在onCreate方法中做數據還原。 



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()前)

  1. 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)。

  1. 是否需要重寫onSaveInstanceState()方法 既然該方法的默認實現可以自動的保存UI控件的狀態數據, 那什麼時候需要覆寫該方法呢?

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

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

  1. 引發activity銷燬和重建的其它情況 除了系統處於內存不足的原因會摧毀activity之外, 某些系統設置的改變也會導致activity的摧毀和重建. 例如改變屏幕方向(見上例), 改變設備語言設定, 鍵盤彈出等。 另外,當屏幕的方向發生了改變, Activity會被摧毀並且被重新創建,如果你想在Activity被摧毀前緩存一些數據,並且在Activity被重新創建後恢復緩存的數據。可以重寫Activity的 onSaveInstanceState() 和 onRestoreInstanceState()方法,如下代碼所示:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.code.test; 

import android.app.Activity;
import android.content.pm.ActivityInfo;
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保存和恢復數據
 * @Description: Android使用InstanceState保存和恢復數據

 * @File: MyTestCodeActivity.java

 * @Package com.code.test

 * @Author Hanyonglu

 * @Date 2012-03-28 下午04:53:15

 * @Version V1.0
 */
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()方法之前。

代碼如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Override
    public void onSaveInstanceState(BundlesavedInstanceState) {
        savedInstanceState.putBoolean("MyBoolean", true);
        savedInstanceState.putDouble("myDouble", 1.9); 
        // etc.
        super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    public void onRestoreInstanceState(BundlesavedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        boolean myBoolean =savedInstanceState.getBoolean("MyBoolean");
        double myDouble =savedInstanceState.getDouble("myDouble"); 
    } 

我們可以用旋轉屏幕來測試我們的程序保存狀態的能力。因爲一般旋轉時,activity會被重新執行onCreate() onStart()等操作,程序此時能夠保存狀態是很重要的。可以考慮使用該方法保存數據。 當然了在旋轉屏幕時我們除了使用onSaveInstanceState ()外,還可以使用onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()這兩個方法來保存切換屏幕的狀態。與onSaveInstanceState () 不同的是,onRetainNonConfigurationInstance()和getLastNonConfigurationInstance() 方法主要用於屏幕之間的旋轉操作時保存數據。
如下代碼所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Override
    public Object onRetainNonConfigurationInstance() {
        // TODO Auto-generated method stub
        // 在這裏設置需要保存的內容,在切換時不是bundle了,我們可以直接通過object來代替。
        return super.onRetainNonConfigurationInstance();
    }  ```
在恢復屏幕時可以不使用onRestoreInstanceState(),而使用getLastNonConfigurationInstance()來代替。我們可以直接在oncreate()方法中獲取上次保存的對象。
如下代碼所示:


```java
@Override
    protected void 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()在我們的程序中扮演的是不同的角色,需要在不同的時機下調用,用來處理不同類型的數據。
例如下面代碼所示要保存一個複雜的數據:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class DataHolder {
        int a;
        Bitmap b;
        String s;
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        // TODO Auto-generated method stub
        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()來實現屏幕旋轉時異步下載更新進度條並保存數據的效果,代碼如下所示:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.rotation.demo; 

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<Void, Void, Void> {
        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;
        }
    }
}

我們在運行示例時就會發現,無論怎樣旋轉屏幕都不會影響進度條的更新與下載,需要解釋下的是這裏我並沒有設置下載功能,有興趣或需要的朋友自己添加即可。實現效果圖如

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章