启动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