[Android] 안드로이드 화면 회전 시 데이터 사라지는 현상 막기(onSaveInstanceState, onRestoreInstanceState)

Android/Android · 2020. 6. 14. 17:46
반응형

https://survivalcoding.com/p/android_basic

 

될 때까지 안드로이드

될 때까지 안드로이드에 수록된 예제의 라이브 코딩 해설

survivalcoding.com

위 서적을 참고하였습니다.

 

 

 

 이번 포스트에서는 안드로이드 앱 화면 회전 시 데이터가 사라지는 것을 방지하기 위한 방법을 알려드리겠습니다. 

 

 먼저 액티비티가 종료되는 상황은 대표적으로 3가지를 볼 수 있습니다.

 

 1. 뒤로 가기 키를 눌러 종료

 2. finish() 메소드 호출

 3. 시스템에 의한 종료

 

 여기서 3번은 홈 키를 눌러 화면에 보이지 않는 상태로 장시간 경과한 때 혹은 화면을 회전할 때입니다. 화면 회전 시 액티비티는 onCreate()부터 다시 시작을 하기 때문에 데이터가 사라지는 것입니다. 이를 방지하기 위해 onSaveInstanceState와 onRestoreInstanceState이 있습니다.

 

 먼저 상태를 저장하는 onSaveInstanceState(Bundle outState)입니다. 이것은 강제로 종료되는 상황에 대비하여 액티비티의 상태 정보를 저장할 수 있습니다. 사용자가 화면을 강제로 회전하여 시스템이 액티비티를 종료시키면, 종료 직전에 onSaveInstanceState(Bundle outState) 콜백 메소드를 호출되면서 Bundle 객체에 상태를 저장할 수 있습니다. Bundle는 Map 형태의 데이터로 저장됩니다.

 

 그리고 저장된 상태를 복구하는 onRestoreInstanceState(Bundle savedInstanceState)입니다. Bundle 객체를 복원할 때는 onCreate() 또는 onRestoreInstanceState()로 복원할 수 있습니다.

 

onSaveInstanceState()는 onDestroy() 메소드가 호출되기 전에 호출되고, onRestoreInstanceState()는 onCreate() 메소드가 호출된 직후에 호출됩니다.

 

 또한, 이 메소드들은 생명주기 메소드가 아닙니다. onSaveInstanceState() 메소드는 onPause()와 onStop() 사이에 항상 호출되지만, onRestoreInstanceState() 메소드는 강제 종료되었을 때만 호출되기 때문입니다.

 

 

 이제 예제를 보여드리겠습니다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="hello.world.study.MainActivity">

    <TextView
        android:id="@+id/level_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="레벨 : 0"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onLevelUp"
        android:text="레벨 증가"/>

    <TextView
        android:id="@+id/score_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="점수 : 0"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onScoreUp"
        android:text="점수 증가"/>
</LinearLayout>

 

 간단한 레이아웃입니다. onClick으로 클릭 메소드를 정의했습니다.

 

 

MainActivity.java

package hello.world.study;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import org.w3c.dom.Text;

import static android.provider.AlarmClock.EXTRA_MESSAGE;

public class MainActivity extends AppCompatActivity {

    private TextView level, score;
    private int mlevel = 0, mscore = 0;
    private final String STATE_SCORE = "playerScore", STATE_LEVEL = "playerLevel";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        level = (TextView) findViewById(R.id.level_text);
        score = (TextView) findViewById(R.id.score_text);
        //onCreate()에서 저장하는 방법
        //인스턴스가 생성 혹은 재생성될 때 항상 호출되므로 Bundle이 null체크 해야함
        /*if(savedInstanceState == null) {
        }
        else {
            mlevel = savedInstanceState.getInt(STATE_LEVEL);
            mscore = savedInstanceState.getInt(STATE_SCORE);
            level.setText("레벨 : " +mlevel);
            score.setText("점수 : " +mscore);
        }*/
    }

    public void onLevelUp(View view) {
        mlevel++;
        level.setText("레벨 : " +mlevel);
    }

    public void onScoreUp(View view) {
        mscore+=100;
        score.setText("점수 : " +mscore);
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        //상태 저장
        outState.putInt(STATE_SCORE, mscore);
        outState.putInt(STATE_LEVEL, mlevel);
        //항상 슈퍼클래스의 메소드 호출
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        //복원 위해 항상 호출
        super.onRestoreInstanceState(savedInstanceState);
        mlevel = savedInstanceState.getInt(STATE_LEVEL);
        mscore = savedInstanceState.getInt(STATE_SCORE);
        level.setText("레벨 : " +mlevel);
        score.setText("점수 : " +mscore);
    }

    @Override
    public void onBackPressed() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("정말 종료?");
        builder.setPositiveButton("확인", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                finish();
            }
        });
        builder.setNegativeButton("취소", null);
        builder.show();
    }
}

 

 onCreate()부분에 작성하는 방법이랑 onRestoreInstanceState()메소드를 오버라이딩하여 2가지 방법을 구현해봤습니다. outState.putInt()로 Integer형 변수를 저장하고, getInt()로 key값을 받아서 그에 맞는 데이터를 가져오는 방법입니다. 여기서 onCreate()부분에 null체크를 하는 부분이 있는데, 이는 인스턴스가 새로 생성되거나 재생성될 때 항상 호출상태로 상태를 복원하기 때문에 상태를 복원하기 전에 Bundle이 null인지 아닌지를 확인해야 합니다.

 

 다음은 결과입니다.

 

가로

세로

 

 

 데이터가 초기화되지 않고 제데로 적용되는 것을 볼 수 있습니다. 자바 코드에서 AlertDialog를 추가하였는데 아직도 AlertDialog는 화면 회전 시 마다 자꾸 액티비티에서 사라지는 것을 볼 수 있습니다. 이는 저번에도 말했다시피 안드로이드 액티비티 생명주기때문입니다. 이를 해결하기 위해서는 Fragments에 대해 공부를 해야 합니다. 이것은 액티비티와 비슷한 개념인데요, 다음 포스트에서 다루도록 하겠습니다. 감사합니다.

반응형