我們要瞭解Android手機開發與案頭開發有一個主要不同之處:通常在一部Android手機裡同時運行著多個應用(app),每個app對應一個系統進程,當系統需要更多的資源(如記憶體)而空閑資源不足時,Android系統就會選擇殺掉一些“低優先順序”的進程以便釋放所需資源。
Android系統是如何確定進程優先順序的高低的呢?
- 如果一個app正在與使用者互動,那麼它所在的進程具有最高優先順序;
- 其次,如果一個app是可見的,例如被一個對話方塊部分遮擋,它所在進程具有第二高的優先順序;
- 再次,如果app當前是不可見的,也就是被切換到了後台,則它所在進程具有第三高的優先順序;這裡要補充一點,如果這個後台app啟動了一個service,則它比一般的後台app優先順序高一些。
- 最後,如果一個進程裡沒有包含任何app,這個進程的優先順序是最低的。
當系統資源嚴重不足時,任何一個進程都有可能被殺掉,而當使用者想回到一個已經不存在於記憶體中的Activity時,系統只得建立一個這樣的Activity對象並調用它的onCreate()方法進行恢複。所以有時出現這種狀況:一個app大部分時間運行很好,偶爾在切換Activity時出現null 指標異常導致強制關閉,這多半是在onCreate()方法裡使用了已經被重設為空白的對象(例如intent裡的變數)造成的。即使不出現異常,也會造成表單資料丟失等嚴重影響使用體驗的問題。
要解決這類問題,切不可抱“現在手機記憶體大,進程一般不會被殺掉”這種僥倖心理,而應該以“應用隨時都會被殺掉”的態度來謹慎處理,下面介紹Google建議的方式。
解決方案
由於Activity隨時可能需要重建,所以我們要做的事情就是在適當的位置將Activity所需資料進行持久化(從ram複製到rom或sd卡),並在onCreate()方法裡利用這些資料恢複現場。
Activity有兩類資料需要進行持久化處理:“文件類型資料”和“內部狀態類型資料”,前者例如使用者正在編輯的表單,後者如使用者偏好。
一、為了持久化文件類型的資料,Google建議使用”即時生效”(edit in place)的編輯策略,具體的方式如下:
- 使用者建立文檔時,在SQLite資料庫(根據需要也可以使用preference)裡也立即建立一條記錄。(與此相對的方式是:為使用者提供一個“儲存”按鈕,只有當使用者按下按鈕時才將文檔儲存到資料庫。)
- 當使用者離開當前Activity時,onPause()方法會被觸發,在這個方法裡將當前正在編輯的文檔持久化到資料庫。這樣一來,如果使用者是從這個Activity切換到另一個相關Activity,仍然可以看到剛剛儲存的內容。
這種方式可以最大限度避免資料丟失,只要onPause()方法被觸發執行,即使Activity所在進程被系統kill掉也不會造成資料丟失。唯一要注意的是,介面上最好能提供一個“取消”按鈕或菜單,以便讓使用者可以選擇不儲存對文檔的更改。
二、為了持久化內部狀態類型的資料,可以在onPause()裡使用Activity#getPreferences(int)方法,這個方法返回SharedPreferences類型的對象,利用它可以記錄使用者對這個Activity的偏好資訊。例如一個日曆應用,使用者可以選擇顯示為周視圖或月視圖,這樣的資訊作為內部狀態記錄到SharedPreferences對象以後,下次再開啟這個Activity時就可以按使用者上次的選擇來顯示日曆了。
以下代碼來自官方文檔,示範了如何持久化並恢複一個Activity的當前顯示模式:
public class CalendarActivity extends Activity { ... static final int DAY_VIEW_MODE = 0; static final int WEEK_VIEW_MODE = 1; private SharedPreferences mPrefs; private int mCurViewMode; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //取回之前持久化的日曆顯示模式 SharedPreferences mPrefs = getSharedPreferences(); mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE); } protected void onPause() { super.onPause(); //持久化日曆顯示模式 SharedPreferences.Editor ed = mPrefs.edit(); ed.putInt("view_mode", mCurViewMode); ed.commit(); } }
是有點麻煩,但為了能讓app穩定運行也值了。
有些同學要問了,為什麼是在onPause()裡持久化而不是在onSaveInstanceState()裡?官方文檔有下面一段話簡要解釋了原因,即前者比後者更可靠,因為onSaveInstanceState()不屬於Activity生命週期的一部分。——既然如此,我想不出onSaveInstanceState()還有什麼其他用途了,大家乾脆忘了它吧,還有onRestoreInstanceState()。
Note that it is important to save persistent data in onPause() instead of onSaveInstanceState(Bundle) because the latter is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.
注1:關於上面這段話,網上存有一些爭議,我個人還是比較傾向在onPause()裡做持久化——既可靠又好記,唯一的缺點是調用次數稍多。
注2:從Android 3.0(HoneyComb)版本開始,Activity進程在被系統殺掉之前,將保證onStop()方法先執行完成,因此如果我們開發的應用只運行在3.0以上,可以把持久化工作放在onStop()裡以減少持久化的次數。
最佳實務
不要抱僥倖心理,你的Activity隨時可能被銷毀。
解決方案:在onPause()裡持久化Activity資料,在onCreate()裡恢複現場。
參考資料:
ActivitySaving Activity state in Android