设备旋转与Activity生命周期

设备旋转时,当前看到的Activity实例会被系统销毁,然后再创建一个新的当前Activity实例。

设备配置与备选资源

旋转设备时会改变设备配置。设备配置是用来描述设备当前状态的一系列特征。这些特征包括:屏幕的方向、屏幕的密度、屏幕的尺寸、键盘类型、底座模式以及语言等等。

通常,为匹配不同的设备配置,应用会提供不同的备选资源。为适应不同分辨率的屏幕,在hdpi、xhdpi中添加不同分辨率的图片就是如此。

在应用运行时配置发生变更,应用会去寻找更合适的资源来匹配新的设备配置。在res目录下新建一个文件夹并命名为layout-land,若屏幕切换为横屏时,应用会在layout-land目录寻找更合适的布局资源来匹配新的设备配置(注意,放在layout-land目录下的布局文件名必须与放在layout目录下的布局文件名相同,这样它才可以用同一个资源ID被引用)。

请记住,只要在应用运行中设备配置发生了改变,android就会销毁当前activity,然后再新建一个activity。

竖屏效果图
设备旋转与Activity生命周期_第1张图片

竖屏布局代码

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

横屏效果图
设备旋转与Activity生命周期_第2张图片

横屏布局代码

<!-- 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生命周期

当用户离开当前activity管理的用户界面,或Android需要回收内存时,activity也会被销毁。不过Android从不会为了回收内存,而去销毁正在运行的activity。activity只有在暂停或停止状态下才可能会被销毁。此时,会调用onSaveInstanceState(Bundle outState)方法,将用户数据保存在Bundle对象中。然后操作系统将Bundle对象放入acivtiy记录中。为了方便理解activity记录,我们增加一个暂存状态到activity生命周期,如下图。
设备旋转与Activity生命周期_第3张图片

activity暂存后,Activity对象不再存在,但操作系统会将activity记录对象保存起来。这样,在需要回复activity时,操作系统可以使用暂存的activtiy记录重新激活activity。

注意,activty进入暂存状态并不一定需要调用onDestory方法。不过onPause和onSaveInstanceState通常是我们需要调用的两个方法。

有时,Android不仅会销毁activity,还会彻底停止当前应用的进程。不过,只有在用户离开当前应用时才会发生这种情况。即使这种情况真的发生了,暂存的activity记录依然被系统保留着,以便于用户返回应用时activity的快速恢复。

那么暂存的activity记录到底可以保留多久?用户按了后退键后,系统会彻底销毁当前的activity。此时,暂存的activity记录同时被清除。此外,系统重启或长时间不使用activity,暂存的activity记录通常也会被清除。

代码下载

你可能感兴趣的:(生命周期,设备旋转)