3.2裝置旋轉時資料丟失解決方式之一
啟動GeoQuiz應用,單擊NEXT按鈕顯示第
二道地理知識問題,然後旋轉裝置。發現又會回到第一道題,所以現在需要解決這個問題。
裝置旋轉時生命週期變化
裝置旋轉時,系統會銷燬當前Activity例項,然後建立一個新的Activity例項。再次旋轉裝置,又一次見證這個銷燬與再建立的過程。
這就是問題所在。每次旋轉裝置,當前Activity例項會完全銷燬,例項中的資料就會被被抹掉。旋轉後,Android重新建立了Activity新例項,一切重頭再來。
裝置配置與備選資源
旋轉裝置會改變裝置配置(device configuration)。裝置配置實際是一系列特徵組合,用來描述裝置當前狀態。這些特徵有:螢幕方向、螢幕畫素密度、螢幕尺寸、鍵盤型別、底座模式以及
語言等。通常,為匹配不同的裝置配置,應用會提供不同的備選資源。為適應不同解析度的螢幕,裝置的螢幕畫素密度是個固定的裝置配置,無法在執行時發生改變。然而,螢幕方向等特徵,可以在應用執行時改變。在執行時配置變更發生時,可能會有更合適的資源來匹配新
的裝置配置。於是,Android銷燬當前activity,為新配置尋找最佳資源,然後建立新例項使用這些資源。只要裝置旋轉至水平方位,Android就會自動發現並使用它。
儲存資料以應對裝置旋轉
protected void onSaveInstanceState(Bundle outState)
該方法通常在onStop()方法之前由系統呼叫,除非使用者按後退鍵。(記住,按後退鍵就是告訴Android,activity用完了。隨後,該activity就完全從記憶體中被抹掉,自然,也就沒有必要為重建儲存資料了。)
方法onSaveInstanceState(Bundle)的預設實現要求所有activity檢視將自身狀態資料儲存在Bundle物件中。Bundle是儲存字串鍵與限定型別值之間對映關係(鍵值對)的一種結構。可通過覆蓋onSaveInstanceState(Bundle)方法,將一些資料儲存在bundle中,然後在onCreate(Bundle)方法中取回這些資料。
關鍵程式碼:
private static final String KEY_INDEX = "index"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_quiz); if(savedInstanceState!=null) { mCurrentIndex=savedInstanceState.getInt(KEY_INDEX,0); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_INDEX,mCurrentIndex); }
詳細程式碼
public class QuizActivity extends AppCompatActivity {
private static final String TAG = "QuizActivity";
private static final String KEY_INDEX = "index";
private Button mTrueButton;
private Button mFalseButton;
private Question[] mQuestionBank=new Question[]{
new Question(R.string.question_australia,true),
new Question(R.string.question_oceans,true),
new Question(R.string.question_mideast,false),
new Question(R.string.question_africa,false),
new Question(R.string.question_americas,true),
new Question(R.string.question_asia,true)
};
private TextView mQuestionTextView;
private int mCurrentIndex;
private Button mNextButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
Log.d(TAG, "onCreate(Bundle) called");
if(savedInstanceState!=null) {
mCurrentIndex=savedInstanceState.getInt(KEY_INDEX,0);
}
initView();
initEvent();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_INDEX,mCurrentIndex);
}
private void initView() {
//例項化控制元件
mTrueButton = findViewById(R.id.true_button);
mFalseButton = findViewById(R.id.false_button);
mNextButton = findViewById(R.id.next_button);
mQuestionTextView = findViewById(R.id.question_text_view);
mQuestionTextView.setText(mQuestionBank[mCurrentIndex].getTextResId());
}
private void initEvent() {
//設定匿名內部類監聽器
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
checkAnswer(true);
}
});
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
checkAnswer(false);
}
});
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCurrentIndex=(mCurrentIndex+1)%mQuestionBank.length;
mQuestionTextView.setText(mQuestionBank[mCurrentIndex].getTextResId());
}
});
mQuestionTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCurrentIndex=(mCurrentIndex+1)%mQuestionBank.length;
mQuestionTextView.setText(mQuestionBank[mCurrentIndex].getTextResId());
}
});
}
/**
* 將使用者輸入的結果與正確結果進行對比
* @param userPressedTrue 使用者輸入的答案
*/
public void checkAnswer(boolean userPressedTrue){
boolean correctAnswer = mQuestionBank[mCurrentIndex].isAnswerTrue();
int messageResId=0;
if(userPressedTrue==correctAnswer) {
messageResId=R.string.correct_toast;
}else {
messageResId=R.string.incorrect_toast;
}
Toast.makeText(QuizActivity.this,messageResId,Toast.LENGTH_SHORT).show();
}
/**
* 注意,我們先是呼叫了超類的實現方法,然後才呼叫具體的日誌記錄方法。這些超類方法的
呼叫不可或缺。從以上程式碼可以看出,在回撥覆蓋實現方法裡,超類實現方法總在第一行呼叫。
也就是說,應首先呼叫超類實現方法,然後再呼叫其他方法。
*/
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart called");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume called");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause called");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop called");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy called");
}
}
覆蓋onSaveInstanceState(Bundle)方法並不僅僅用於處理與裝置旋轉相關的問題。使用者離開當前activity使用者介面,或Android需要回收記憶體時,activity也會被銷燬。(例如,使用者按了主螢幕鍵,然後播放視訊或玩起遊戲。)基於使用者體驗考慮,Android從不會為了回收記憶體,而去銷燬可見的activity(處於暫停或執行狀態)。只有在呼叫過onStop()並執行完成後,activity才會被標為可銷燬。系統隨時會銷燬掉已停止的activity 。不用擔心資料丟失, activity 停止時, 會呼叫onSaveInstanceState(Bundle)方法的。所以,解決旋轉資料丟失問題,就是搶在系統銷燬activity之前儲存資料。儲存在onSaveInstanceState(Bundle)的資料該如何倖免於難呢?呼叫該方法時,使用者資料隨即被儲存在Bundle物件中,然後作業系統將Bundle物件放入activity記錄中。activity暫存後,Activity物件不再存在,但作業系統會將activity記錄物件儲存起來。這樣,
在需要恢復activity時,作業系統可以使用暫存的activity記錄重新啟用activity。注意,activity進入暫存狀態並不一定需要呼叫onDestroy()方法。不過,onStop()和onSaveInstanceState(Bundle)是兩個可靠的方法(除非裝置出現重大故障)。因而,常見的做法是,覆蓋onSaveInstanceState(Bundle)方法,在Bundle物件中,儲存當activity的小的或暫存狀態的資料;覆蓋onStop()方法,儲存永久性資料,如使用者編輯的文字等。onStop()方法呼叫完,activity隨時會被系統銷燬,所以用它儲存永久性資料。
那麼暫存的activity記錄到底可以保留多久?前面說過,使用者按了後退鍵後,系統會徹底銷燬
當前的activity。此時,暫存的activity記錄同時被清除。此外,系統重啟的話,暫存的activity記錄
也會被清除。
activity 記憶體清理現狀
低記憶體狀態下,Android直接從記憶體清除整個應用程序,連帶應用的所有activity。目前,Android還做不到只銷毀單個activity。
相比其他程序,有前臺(執行狀態)或可見(暫停狀態)activity的程序的優先順序更高。需要釋放資源時,Android系統的首選目標是低優先順序程序。使用者體驗至上,理論上,作業系統不會殺掉帶有可見activity的程序。當然出現重啟或宕機這樣的大故障就難說了。
日誌記錄的級別與方法
使用android.util.Log類記錄日誌,不僅可以控制日誌的內容,還可以控制用來區分資訊重要程度的日誌級別。Android支援如表3-2所示的五種日誌級別。每一個級別對應一個Log類方法。要輸出什麼級別的日誌,呼叫對應的Log類方法就可以了。
需要說明的是,所有的日誌記錄方法都有兩種引數簽名:string型別的tag引數和msg引數;除tag和msg引數外再加上Throwable例項引數。附加的Throwable例項引數為應用丟擲異常時記錄異常資訊提供了方便。對於輸出的日誌資訊,可使用常用的Java字串連線操作拼接出需要的資訊,或者使用String.format對輸出日誌資訊進行格式化操作,以滿足個性化的使用要求。
兩種方法不同引數簽名的使用例項
// Log a message at "debug" log level
Log.d(TAG, "Current question index: " + mCurrentIndex);
Question question;
try {
question = mQuestionBank[mCurrentIndex];
} catch (ArrayIndexOutOfBoundsException ex) {
// Log a message at "error" log level, along with an exception stack trace
Log.e(TAG, "Index was out of bounds", ex);
}
Demo下載地址:
https://download.csdn.net/download/weixin_43953649/10851072