设备旋转时,当前看到的Activity实例会被系统销毁,然后再创建一个新的当前Activity实例。
旋转设备时会改变设备配置。设备配置是用来描述设备当前状态的一系列特征。这些特征包括:屏幕的方向、屏幕的密度、屏幕的尺寸、键盘类型、底座模式以及语言等等。
通常,为匹配不同的设备配置,应用会提供不同的备选资源。为适应不同分辨率的屏幕,在hdpi、xhdpi中添加不同分辨率的图片就是如此。
在应用运行时配置发生变更,应用会去寻找更合适的资源来匹配新的设备配置。在res目录下新建一个文件夹并命名为layout-land,若屏幕切换为横屏时,应用会在layout-land目录寻找更合适的布局资源来匹配新的设备配置(注意,放在layout-land目录下的布局文件名必须与放在layout目录下的布局文件名相同,这样它才可以用同一个资源ID被引用)。
请记住,只要在应用运行中设备配置发生了改变,android就会销毁当前activity,然后再新建一个activity。
竖屏布局代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" >
<TextView android:id="@+id/tv_question" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" />
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" >
<Button android:id="@+id/true_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/true_button" />
<Button android:id="@+id/false_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/false_button" />
</LinearLayout>
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" >
<ImageButton android:id="@+id/pre_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/arrow_left" android:contentDescription="@string/next_button"/>
<ImageButton android:id="@+id/next_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/arrow_right" android:contentDescription="@string/prev_button"/>
</LinearLayout>
</LinearLayout>
横屏布局代码
<!-- FrameLayout是一种最简单的ViewGroup组件,它以特定方式安排其子视图的位置。 FrameLayout子视图的位置排列都是由它们各自的andrid:layout_gravity属性决定的 -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" >
<TextView android:id="@+id/tv_question" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:padding="24dp" />
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:orientation="horizontal" >
<Button android:id="@+id/true_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/true_button" />
<Button android:id="@+id/false_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/false_button" />
</LinearLayout>
<ImageButton android:id="@+id/pre_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/next_button" android:layout_gravity="bottom|left" android:src="@drawable/arrow_left" />
<ImageButton android:id="@+id/next_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:contentDescription="@string/prev_button" android:src="@drawable/arrow_right" />
</FrameLayout>
package com.huangfei.geoquiz;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
public class QuizActivity extends Activity {
private Button mTrueButton;//正确按钮
private Button mFalseButton;//错误按钮
private ImageButton mNextButton;//下一道问题
private ImageButton mPreButton;//上一道问题
private TextView mQuestionTextView;//问题描述
private int mCurrentIndex;//当前问题下标
private TrueFalse[] mQuestionBank = new TrueFalse[] {//问题集合
new TrueFalse(R.string.question_oceans, true),
new TrueFalse(R.string.question_mideast, false),
new TrueFalse(R.string.question_africa, false),
new TrueFalse(R.string.question_americas, true),
new TrueFalse(R.string.question_asia, true) };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);//必须先调用onCreate()方法的超类的实现方法,然后再调用其它方法,这一点很关键。而在Activity其它的生命周期方法中,是否首先调用超类方法就不那么重要了。
setContentView(R.layout.activity_quiz);// 布局命名规则:将activity名称的单词顺序颠倒过来并全部转换为小写字母,让后在单词间添加下划线
mQuestionTextView = (TextView) findViewById(R.id.tv_question);
mTrueButton = (Button) findViewById(R.id.true_button);
mFalseButton = (Button) findViewById(R.id.false_button);
mNextButton = (ImageButton) findViewById(R.id.next_button);
mPreButton = (ImageButton) findViewById(R.id.pre_button);
updateQuestion();
// 类包组织倒入的快捷键:1、Command+shift+O(Mac系统);2、Ctrl+Shift+O(Windows系统和Linux系统)
mTrueButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
checkAnswer(true);
}
});
mFalseButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
checkAnswer(false);
}
});
mNextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
updateQuestion();
}
});
mPreButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = mCurrentIndex == 0 ? mQuestionBank.length - 1 : mCurrentIndex - 1;
updateQuestion();
}
});
mQuestionTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
updateQuestion();
}
});
}
/** * 判断用户回答是否正确 * @param userPressTrue 用户的答案 */
protected void checkAnswer(boolean userPressTrue) {
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isTrueQuestion();
int messageResId = 0;
if (userPressTrue == answerIsTrue)
messageResId = R.string.correct_toast;
else
messageResId = R.string.incorrect_toast;
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show();
}
/** * 更新问题 */
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getQuestion();
mQuestionTextView.setText(question);
}
}
在上述代码中,每次设备旋转,activty都会重建,即不管当前显示的是第几道问题,当设备旋转后,mCurrentIndex都会等于0,只会显示第一道问题,那该如何保存以前的数据呢?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if(savedInstanceState != null)
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0);
updateQuestion();
...
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.i(TAG, "onSaveInstanceState");
outState.putInt(KEY_INDEX, mCurrentIndex);
}
通过重写activity中的onSaveInstanceState(Bundle outState)方法即可实现,该方法通常在onPause()、onStop()以及onDestroy()方法之前由系统调用。方法onSaveInstanceState(Bundle outState)默认的实现要求所有activty的视图将自身状态数据保存到Bundle对象中。Bundle是存储字符串键与限定类型值之间映射关系(键-值对)的一种结构。
覆盖onCreate(Bundle savedInstanceState)方法时,我们实际是在调用activity超类的onCreate(Bundle savedInstanceState)方法,并传入收到的bundle。在超类实现代码中,通过取出保存的视图状态数据,activity的视图级结构得以重新创建。
当用户离开当前activity管理的用户界面,或Android需要回收内存时,activity也会被销毁。不过Android从不会为了回收内存,而去销毁正在运行的activity。activity只有在暂停或停止状态下才可能会被销毁。此时,会调用onSaveInstanceState(Bundle outState)方法,将用户数据保存在Bundle对象中。然后操作系统将Bundle对象放入acivtiy记录中。为了方便理解activity记录,我们增加一个暂存状态到activity生命周期,如下图。
activity暂存后,Activity对象不再存在,但操作系统会将activity记录对象保存起来。这样,在需要回复activity时,操作系统可以使用暂存的activtiy记录重新激活activity。
注意,activty进入暂存状态并不一定需要调用onDestory方法。不过onPause和onSaveInstanceState通常是我们需要调用的两个方法。
有时,Android不仅会销毁activity,还会彻底停止当前应用的进程。不过,只有在用户离开当前应用时才会发生这种情况。即使这种情况真的发生了,暂存的activity记录依然被系统保留着,以便于用户返回应用时activity的快速恢复。
那么暂存的activity记录到底可以保留多久?用户按了后退键后,系统会彻底销毁当前的activity。此时,暂存的activity记录同时被清除。此外,系统重启或长时间不使用activity,暂存的activity记录通常也会被清除。
代码下载