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

猜你喜欢

转载自blog.csdn.net/weixin_43953649/article/details/85006560