태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.


* 이번에는 안드로이드 앱의 기초 단위인 Activity의 lifecycle에 대해서 공부해보자.



2012/11/05 - [Android(안드로이드) 앱 개발 기초] Eclipse 개발환경 설정하기, Android SDK 설치하기

2012/11/07 - [Android(안드로이드) 앱 개발 기초] 안드로이드 프로젝트 생성하고 에뮬레이터로 앱 실행하기

2012/11/10 - [Android(안드로이드) 앱 개발 기초] 간단한 인터페이스 구현과 다른 Activity로 넘어가기



* Activity 라이프 사이클

: 사용자가 앱을 시작하고 앱 안에서 앞으로 뒤로 이동하고 다른 앱을 실행하는 등의 행위를 컨트롤하는 것이 바로 Activity이다. 이러한 Activity는 처음 시작할때, 전면에 나와서 사용자의 포커스를 받는 등의 다양한 상태로 변환을 하게 된다. 안드로이드 시스템에서는 이러한 프로세스 중에 라이프사이클과 관련된 함수들을 호출하게 된다. 이러한 함수들은 주로 사용자가 현재 상태에서 다른 상태로 갈 때 이루어지는 것으로 기존에 있는 상태로 다시 돌아갈 때를 대비하거나 다른 상태 중에 있을 때 강제 종료를 하는 등의 예외 상황을 대비하는 등 다양한 처리를 해주어야한다.


: 이렇게 호출되는 함수에 따라서 사용자가 Activity를 떠나거나 다시 들어왔을때의 행동들을 설정하여 더 유연하고 매끄럽게 앱의 기능을 엮어줄 수 있을 것이다. 한가지 예로 스트리밍 비디오 플레이어 앱의 기능을 구현한다고 치면, 만약 사용자가 앱을 나갔다고 하면 앱상의 비디오를 멈추고 커넥션을 끊어주는 것이 사용자의 리소스 낭비를 막기 위해 좋을 것이다. 그리고 다시 앱에 들어오게 되면 커넥션을 재개하고 같은 곳에서 비디오를 재생하는 것으로 Activity의 라이프사이클을 효율적으로 잘 이용하게 되는 것이 하나의 예이다.



* Activity 시작하기

: 다른 프로그래밍 언어와 달리 안드로이드 시스템은 어느 특정한 main() 함수가 없다. 대신 Activity를 초기화하고, 여기에 있는 콜백 함수들이 호출이 되게 된다. 처음에 Activity를 시작할 때 호출되는 콜백 함수들이 있으며, Activity를 종료할 때에도 순서대로 호출되는 콜백 함수들이 있다.


* 라이프사이클 콜백 함수들

: 콜백 함수들은 앱이 각 단계별로 새로운 상태에 들어갈 때마다 콜백 함수가 하나씩 호출되는 방식을 취하고 있다. 각 상태로 변환이 될때마다 해당하는 함수가 호출 되는 것이다. 앱의 상태와 콜백함수 상관도는 아래와 같다.





: 사용자가 처음 시작했을 때부터 잠시 다른 앱으로 들어갈 때와 다시 돌아올 때, 사용자의 행동에 따라 다른 상태로 이동하게 되고 다시 전면에 나오기도 한다. 앱에 따라서 특정 상태로 들어가는 콜백 함수를 이용해서 추가 구현을 해야할수도 있고, 아닌 경우도 있지만, 이러한 것을 판단하는 것은 앱에 잠시 나갔다가 돌아왔을 때 이전에 보던 화면을 다시 보여주는 등 사용자가 충분히 예측가능한 결과를 보여줘야한다는 것이다.


* 왜 라이프사이클을 고려해야하는가?

: 아주 간단한 앱이라면 라이프사이클을 그다지 고려를 안해도 될 것이다. 하지만 이렇게 앱이 멈추거나 다른 상태로 들어가는 경우는 수시로 발생하게 되며, 사용자를 위해 앱을 최적화하는 것이 필수가 된 요즘, 이러한 라이프사이클을 고려하는 것을 기본이 될 것이다. 라이프사이클을 활용하면 좋은 예를 몇가지 알아보면,

    • 사용자가 앱을 사용하는 중에 전화를 받았을 경우 앱에서 충돌이 일어나지 않게 방지한다.
    • 사용자가 앱을 사용하다가 다른 앱에 들어갔을 때 충돌이 일어나지 않게 방지한다.
    • 사용자가 앱을 사용하고 있지 않을 때 다양한 리소스들을 낭비하지 않게 방지한다.
    • 사용자가 앱을 사용하다가 떠났다가 다시 들어왔을 때 이전에 하고 있던 프로세스를 이어서 할 수 있도록 지원한다.
    • 화면이 가로나 세로로 돌릴 때 사용자의 프로세스를 이어서 해주도록 해주고 충돌이 일어나지 않게 해준다.

: 위와 같은 다양한 상황에서도 사용자 경험 측면에서 앱의 진행을 끊기지 않게 제공해주는 것은 앱의 개발자가 얼마나 Activity의 라이프사이클을 이해하고 있느냐에 달린 것이다. 이러한 라이프사이클을 이용할 수 있는 가장 최적의 예가 있다면, 요즘 유행하고 있는 드래곤플라이트라는 슈팅 게임을 하다가 전화가오면 화를 내며 받는 사람들을 많이 봤을 것이다. 만약 앱 개발자가 전화가 왔을 때 게임을 자동으로 일시정지를 시켜주는 코드만 넣었더라면 저렇게 게임을 하다가 전화가 와서 화를 내는 사람들도 적어졌을지도 모른다.


* Activity의 기본 상태들

: Activity의 다양한 상태들 중에서 Activity가 한가지 상태로 오랫동안 유지 가능한 상태는 3가지 뿐이다. 나머지 상태들은 자동으로 다음 상태로 넘어가는 형태를 취하게 된다. 주요 3가지 상태는 아래와 같다.

  • Resumed: 이 상태는 Activity가 전면에 있어서 사용자와 인터랙션하고 있는 상태이다.
  • Paused: 이 상태는 Activity가 뒷면에서 반투명으로 덮히거나 부분적으로 덮힌 상태이다. Paused인 경우에는 사용자의 입력을 받지 않고 어떠한 소스코드도 실행되지 않는다. 이 때에는 다른 Activity가 Resumed 상태로 사용자와 인터랙션 중인 것이다.
  • Stopped: 이 상태는 Activity가 완전히 가려져서 숨겨진 상태이다. 백그라운드로 들어간 것이라고 보면 된다. Activity의 모든 멤버 변수는 유지되지만 코드를 실행할 수는 없다.

: 나머지 Created나 Started는 시스템에서 빠르게 다음 단계로 이동하게 되는 일시적인 상태들이다. 즉, 처음에 시스템이 onCreate() 함수를 호출하고 이어서 onStart()를 호출한 다음에 이어서 onResume()을 빠르게 순서대로 호출하게 되는 것이다.


* 시작 Activity 정하기

: 위에서 말한 일시적인 상태들은 처음에 앱의 아이콘을 눌렀을 때 실행되는 Activity에서 실행되는 것이다. 그렇다면 어떠한 Activity로 시작할지 결정하고 설정해야할 것이다. 이러한 설정은 AndroidManifest.xml 파일에서 설정할 수 있다. 해당 <activity> 안에 <intent-filter> 아래에 MAIN action에 LAUNCHER category로 설정하면 된다. 아래가 소스 코드의 예이다.

        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

: 위와 같이 설정하면 해당하는 Activity가 처음에 시작하는 Activity로 설정이 되고, 위 그림의 onCreate() - onStart() - onResume()의 순서대로 콜백함수들이 호출하게 되는 것이다.


* 새로운 인스턴스 만들기

: 만약에 해당하는 Activity가 위와 같이 초기에 시작하는 Activity라면, 안드로이드에서는 onCreate 함수를 호출해서 모든 인스턴스들을 초기화할 것이다. 따라서 이 onCreate 함수에는 앱의 초기화를 구현하고, 앱의 전체 실행에서 처음에 한번만 실행될 코드를 이곳에다가 구현하면 편리하다. 예로, onCreate에서는 UI를 초기화하거나 다양한 클래스 단위 변수들을 초기화하는 것이 편리할 것이다. 아래처럼 xml로 UI를 초기화하고 특정 위젯을 멤버변수로 저장하고자 한다면 onCreate 안에 이를 구현하면 된다. 아래는 MainActivity 안에 간단하게 구현한 예이다.

    //다음과 같이 처음에 한번 초기화가 필요한 멤버 변수 초기화 
    private EditText mEditView;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Activity를 위한 레이아웃 UI를 설정을 할 때 이용
        setContentView(R.layout.activity_main);
        
        // Activity 안의 멤버 변수를 초기화 할 때 이용
        mEditView = (EditText) findViewById(R.id.edit_message);
     
        //아래 ActionBar를 이용하기 전에 API 버전을 확인해본다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // 아래와 같이 MainActivity의 ActionBar의 아이콘은 버튼의 기능을 가지지 않게 설정하는 등 UI를 설정할 때 이용
            ActionBar actionBar = getActionBar();
            actionBar.setHomeButtonEnabled(false);
        }    
        // 이러한 앱의 전반적으로 계속 진행해야하는 스레드의 초기화도 이용
        Debug.startMethodTracing();
    }

* 만약 Android 2.0 (API level 5) 이전의 버전을 쓴다면 SDK_INT에서 에러가 날 것이다. SDK를 업그레이드하면 해결될 것이다.

* onCreate(savedInstanceState)에 대해서는 이후 다시 자세하게 공부하자.


: 이렇게 onCreate가 호출되고나면 앱은 Started 상태로 가며 바로 Resumed 상태로 가게 된다. 이후에는 Created 상태에는 다시 가지 않으며 Started 상태에는 기술적으로 Started로 가기는 하지만 바로 다시 Resumed 상태로 가게 된다. 따라서 앱의 초기화는 onCreate에서 하고, 앱을 잠시 멈췄다가 다시 시작하는 부분은 onStart그리고 onResume 함수안에서 작성하면 되는 것이다. 이 부분은 아래 그림 부분의 흐름을 나타낸 것이다.





* 앱이 종료되는 콜백 함수 onDestroy

: 생성될때에는 onCreate라던 반대로 Activity가 종료될 때에는 onDestroy 함수가 콜백된다. 일반적으로는 모든 변수들이 garbage collect 되기도 하고, 기본적으로 onPause와 onStop 함수 안에 모든 메모리를 잡아먹는 인스턴스를 해제하는 것이 권장되기 때문에, onDestroy 함수를 건드리는 일은 별로 없을 것이다. 하지만 위의 예에서 처럼 만약에 앱 전반적으로 실행한 스레드가 있어서 그것이 메모리 leak으로 이어지거나 리소스 낭비로 이어지게 된다면 이는 필수적으로 onDestroy에서 해제를 해줘야할 것이다.

    @Override
    protected void onDestroy() {
    	super.onDestroy();
    	//앱의 전반적으로 돌아가던 스레드를 종료할 때 이
    	Debug.stopMethodTracing();
    }


: 대부분의 경우는 onPause와 onStop에 이어져서 onDestroy가 호출이 되지만 만약 onCreate에서 다른 Activity를 호출하고 finish()를 onCreate 함수 내에서 호출하게 된다면 onPause와 onStop 함수를 거치지 않고 바로 onDestroy로 호출되므로 이러한 경우 프로세스 흐름에 주의를 요하자.


* 앱을 Pause하고 Resume 하기

: 앱이 전면에 있다가 Pause로 들어가는 경우는 위에서 언급했듯이 다른 Activity나 컴포넌트로 덮어져서 일시적으로 사용할 수 없을 때 Pause 상태로 들어가게 된다. 예를 들면, 반투명한 다이얼로그 방식의 Activity가 위에 열리면 이전의 Activity는 Pause상태로 들어가게 된다. 이렇게 Activity가 일부라도 노출이 되어있으면 Activity는 Pause 상태에 머무르게 된다. 그러다가 만약에 완벽하게 안보이게 되거나 백그라운드로 가게 된다면 Stop 상태로 가게 된다.


: 만약 Pause 상태로 가게 된다면 시스템은 onPause 함수를 콜백하게 된다. 그럼 현재 진행 중인 기능들을 잠시 Pause 시키거나(비디오 등과 같은 기능을 일시 정지), 계속 유지해야할 정보가 있다면 사용자가 그대로 나갈것을 대비해서 이를 임시 저장소에 저장하는 등의 작업을 여기서 하면 된다.


: 만약 Pause 상태에 있다가 Activity가 다시 활성화 되어 Resume 상태로 가게 된다면 시스템은 onResume    함수를 호출하게 된다. 아래와 같은 단계를 이루게 된다.




: onPause가 호출 되는 경우에 물론 사용자는 다시 Activity를 활성화시켜서 다시 돌아갈수도 있지만, 일반적으로 사용자가 Activity를 종료시키는 첫 단계로 더 많이 나타나게 된다.


* Activity가 Pause상태로 가는 경우

: Pause 상태는 사용자가 Activity를 종료 시키는 첫 단계이기 때문에 곧 Stop 단계로 들어설 것을 준비하는 기능을 구현하면 될 것이다. 따라서 Pause 상태로 가게 된다면 아래와 같은 기능들을 넣는게 좋다.


  • 애니메이션을 정지한다.
  • CPU 리소스를 차지하지 않게 하기 위해 계속되는 기능들을 멈춘다.
  • 사용자가 보관하고 싶은 내용이라면 저장안된 변경 내용들을 임시저장한다. (이메일 임시보관 기능 등)
  • 시스템에서 차지하고 있는 리소스를 풀어준다.
  • 브로드캐스트 기능, 센서 핸들 기능(GPS, 카메라 등) 배터리 소모를 일으키는 기능들을 정지한다.
  • 사용자가 필요하지 않은 기능이라면 다 멈춘다.

: 특정 앱들을 깔고나면 배터리소모가 심각한 것도 이러한 라이프사이클을 잘 활용하지 못했고 잘 모르고 있다는 증거이다. 이러한 이유 때문에 앱 개발은 충분한 이론이 바탕이 되어야 훌륭한 앱이 나올 수 있게 되는 것이다. 


: 위에서 예를 하나로 들고 있는 카메라 같은 경우는 만약 현재 Activity가 활성화해서 센서 핸들러를 가지고 있다면 다른 Activity와 충돌이 일어날수도 있고, 리소스도 소모되므로 아래와 같이 onPause에서 잠시 풀어주는 것이 현명하다.

@Override
public void onPause() {
    super.onPause(); 

    // 다른 Activity가 사용할 수 있게 카메라를 활성화시켰다면 풀어준다.
    if (mCamera != null) {
        mCamera.release()
        mCamera = null;
    }
}

: 사용자가 보관하고 싶은 내용을 저장하라고는 했지만 이를 CPU가 많이 잡아먹는 실제로 저장소에 저장하는 것은 안좋고, 사용자가 돌아왔을 때 바로 계속 작업을 할 수 있도록 잠시 임시 저장소에 자동저장하는 방식을 채택한다면 좋을 것이다. onPause에서의 역할은 사용자가 빠르게 상태 전환을 하기 전에 감당할만큼의 간단한 기능들을 구현하는 것이다. 위의 그림에서도 작은 순환을 거치는 만큼 CPU에 부담이 되지 않는 선에서 구현하는 것이 좋다. 만약 Pause 상태로 들어가게 된다면 Activity는 아직 메모리에 남아있기 때문에 Activity의 변수등 내용들은 계속 유지가 되므로 중요한 내용 이외에는 놔두면 그대로 재생될 것이다.


* Activity가 Resume상태로 가는 경우

: 사용자가 Pause 상태에서 다시 Activity로 돌아왔을 때에는 onResume 함수가 호출 된다. 이렇게 onResume은 매번 Activity가 전면으로 다시 나올때마다 호출이 된다는 것을 기억하자. 맨 처음 Activity를 생성해서 실행할 때에도 Activity는 onResume 함수를 거쳐가게 되므로 Activity가 전면에 나올 때 초기화해야하는 기능들은 onCreate가 아니라 onResume에서 초기화를 하면 된다. 그리고 onResume에서 초기화를 했던 내용들은 반드시 onPause에서 다시 해제를 해줘야하는 것을 잊지 말자. onResume은 onPause와 반대로 리소스를 잡아먹는 기능들을 다시 초기화를 한다는 생각으로 하면 편할 것이다.


: 위의 카메라를 일시정지하는 onPause와 반대되는 onResume은 바로 카메라를 초기화하는 것이다.

@Override
public void onResume() {
    super.onResume();  // Always call the superclass method first

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera init
    }
}


* Activity 멈추고 다시 시작하기

: Activity를 제대로 멈추고 다시 시작하는 것은 앱을 구현함에 있어서 매우 중요한 요소이다. 이것을 잘 이해하고 구현해놔야 앱이 중단 되었을 때 사용자들이 하던 프로세스를 잃어버리지 않고 계속 작업을 이어서 할 수 있도록 할 수 있다. 먼저 Activity가 Stop 상태로 가고 다시 Restart되는 경우를 살펴보면 아래와 같다.

  • 사용자가 최근 사용 앱 창을 열어서 다른 앱으로 갔을 때 Stop 된다. 만약 사용자가 다시 최근 사용 앱 창을 통해서 들어오거나 홈 스크린에서 아이콘으로 들어오게 된다면 Restart 된다.
  • 사용자가 앱에서 다른 Activity를 시작하는 기능을 했을 때 현재 있는 Activity는 Stop 되고 새로운 Activity가 Create 된다. 이 때 사용자가 Back 버튼을 누르게 된다면 기존 Activity가 Restart 된다.
  • 사용자가 앱을 사용하는 중에 전화가 왔을 때 Stop 되고 전화를 끊고 나면 다시 Restart 된다.

: Stop 상태로 갔을 때에는 onStop 함수가 콜백되고 Activity가 다시 시작할 때에는 onRestart 함수가 콜백된다. onStop의 호출 은 onPause와는 다르게 Activity가 확실하게 백그라운드로 갔을 때 호출하게 되고 UI가 더이상 보이지 않게 됐음을 의미한다. 


: 위의 그림에서 Stop과 Restart는 Pause와 Resume보다 큰 상태 변환 사이클을 보여준다.




: 하지만 시스템에서는 기본적으로 Activity 인스턴스의 정보를 유지시켜주기 때문에 많은 앱들은 굳이 onStop과 onRestart를 구현하지 않아도 될 것이다. 왜냐하면 Stop 상태로 가기 위해서는 반드시 onPause와 Restart될 때에는 onResume의 콜백 함수를 호출하기 때문에, 따라서 onPause에서 스레드로 돌아가는 리소스를 잡아먹는 동작들을 멈춰주고 onStop에서는 사용자가 강제 종료를 하는 등의 동작에 대해 미리 대비를 해주면 된다. 


* Activity가 Stop상태로 가는 경우

: Activity가 Stop해서 사용자가 더이상 인터랙션이 불가능하게 된다면 이때에 사용하고 있는 거의 모든 리소스는 시스템에 반환해주는 것이 좋다. 이렇게 Stop 된 상태에서 만약 리소스가 부족하다면 시스템에서는 자동으로 앱이 차지하고 있는 리소스들을 해제하고 Destroy 시킬지도 모르기 때문에 그러한 상태들을 대비하고 메모리 leak가 일어나지 않게 하기 위해 onStop에서 메모리 관리까지 잘 해주어야 한다.


: onPause에서는 CPU를 많이 사용하지 않는, 상태 전환을 빠르게 해야했다면 onStop에서는 CPU를 많이 사용하는 메모리 해제, 리소스 해제 작업들, 그리고 데이터베이스에 쓰는 등 데이터 저장의 동작들을 행하면 된다. 예를 들면, onStop에서는 아래와 같이 유지하고자하는 내용을 저장소에 저장하는 것이 할일이다.

@Override
protected void onStop() {
    super.onStop();

    // 작업하던 내용을 가져와서 저장한다. Note의 내용과 Note의 타이틀을 가져오는 함수가 있다고 가정한다.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    getContentResolver().update(
            mUri,    // Note를 업데이트할 URI
            values,  // 저장할 값들. 위에서 저장하고자한 값
            null,    // SELECT 기준 없음
            null     // WHERE 조건 없음
            );
}

: 여기서는 ContentResolver를 사용하고 있지만 이것이 데이터베이스와 연결된 Handler 등이나 로컬 스토리지에 쓰는 등의 작업으로 바뀌어도 같은 의미로 사용자가 작업하던 내용을 저장하겠다는 것으로 동작하게 되는 것이다.


: 이렇게 onStop가 호출되고나서 Activity 는 메모리상에 머무르고 있다가 다시 호출이 되면 Restart, Resume을 하게 된다. 따라서 이런 Activity는 다시 초기화를 할 필요가 없고 onCreate 함수는 호출되지 않는다. 시스템에서는 UI의 각 View의 상태도 유지가 되므로 EditText 등에 들어있던 텍스트들은 저장했다가 다시 설정할 필요가 없다. 만약 시스템이 리소스 부족으로 Activity를 멈춘다해도 Activity의 View들은 그 상태를 계속 저장하고 유지하게 된다. 이러한 경우는 Bundle에 데이터를 저장하는 것이 유용한데 조금 뒤에 살펴보자.


* Activity가 Restart 상태로 가는 경우

: Activity가 Stop 상태에 있다가 다시 전면으로 나올 때에는 onRestart 함수를 콜백하게 되고, 바로 이어서 onStart 함수를 호출하게 된다. 그래서 Activity는 Start 상태로 갔다가 바로 이어서 onResume 콜백함수가 호출되서 Resume 상태로 가서 다시 전면에서 사용자와 인터랙션을 하게 된다. 여기서 onRestart 콜백 함수는 Stop에 있다가 Start 상태로 갈때에만 호출되는 콜백 함수로 Stop 상태에서 Start로 갈 때에 특별하게 처리하거나 복원해야하는 정보를 이 콜백 함수 안에서 처리하면 된다. 


: 하지만 onStop에서 대부분 해제했던 리소스들은 어짜피 Activity가 처음에 실행될 때 초기화되어야하는 것들이므로 onStart 상태에 넣어두면 onRestart를 많은 앱에서는 굳이 사용을 하지 않아도 될 것이다. 따라서 사용자가 Activity를 Stop 했다가 다시 Restart 하는 경우 onStart에서 해당하는 리소스의 여부를 점검하고 초기화하는 것이 더 유용하다. 아래는 GPS 리소스 점검에 대한 간단한 예이다.

@Override
protected void onStart() {
    super.onStart();
    
    // Activity가 처음으로 시작하거나 다시 시작한 상태이다. 따라서 GPS의 사용가능 여부를 검사한다. 
    LocationManager locationManager = 
            (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    
    if (!gpsEnabled) {
        // GPS가 사용가능하지 않다면 사용자에게 요청하는 다이얼로그를 띄우고 처리한다.
        // android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS 인텐트로 GPS 설정을 유도한다.
    }
}

@Override
protected void onRestart() {
    super.onRestart();
    
    // onStart에서 설정을 하므로 onRestart에서는 특별히 할게 없어진다.
}

: 위의 예처럼 Activity가 처음 시작할 때, 그리고 다시 시작할 때 필요한 초기화 기능들은 이렇게 onStart에다가 넣어두는 것이 좋다. 만약에 Stop 상태에서 시스템이 Activity를 메모리에서 해제하려고 한다면 onDestroy 함수가 콜백된다. 하지만 onStop에서 이미 대부분의 리소스를 해제했다면 역시 onDestroy 함수에서는 할 일이 많이 없을 것이다. 그래도 메모리 누구사 생기지 않게 마지막 까지 다른 실행중인 스레드가 함수 tracing과 같이 길게 지속되는 동작들은 다 멈춰주어야할 것이다.


* Destroy 된 Activity를 다시 시작하기

: 정상적인 앱의 실행 중에 Activity가 Destroy 되는 시나리오가 몇가지가 있다.

  • 사용자가 Back 버튼을 눌렀을 때
  • Activity 안에서 finish() 함수를 호출하여 자체적으로 종료할 때
  • Stop 된 상태에서 오랫동안 사용하지 않을 때
  • Stop 된 상태에서 전면에 있는 Activity가 더 많은 리소스가 필요할 때 메모리 확보를 위해
  • 사용자가 화면을 회전 시켰을 때

: 위의 상황들 중에서 사용자가 Back 버튼을 누르거나 finish() 함수를 호출했을 때에는 Activity가 이제 필요 없음을 의미하기 때문에 아예 완전히 메모리에서 사라지게 된다. 하지만 위의 경우 중 3번째 4번째 처럼 시스템이 필요에 의해 Destroy 된 경우는 예상치 못하게 Destroy 된 것이므로 메모리상에서는 없어졌지만 사용자가 다시 접근을 한다면 마치 계속 메모리에 있었던 것처럼 보이도록 시스템에서 처리를 하게 된다. 메모리에는 이미 없어졌으므로 시스템에서는 새로운 인스턴스를 만들고 저장된 데이터를 통해서 Destroy 되었던 상태로 최대한 복원하게 된다. 이 때 사용하게 되는 데이터는 "instance state" 그리고 key-value의 쌍을 이루는 Bundle 객체에 저장된 데이터이다.


* 노트: 사용자가 화면을 rotation 시킬 때에도 Destroy가 일어나게 되는데, 이는 화면의 방향이 바뀌면서 화면의 설정들을 다시 불러오기 위해 시스템에서 전면에 있는 Activity를 종료시켰다가 다시 시작하는 루틴이 일어나게 된다. 따라서 화면의 회전이 많이 일어나는 앱이라면 이러한 개념을 잘 숙지하고 있어야할 것이다.


: 기본적으로 시스템에서는 Bundle의 데이터를 이용해서 각 View 위젯의 상태에 대해서 저장하게 된다. 따라서 Destroy 되었다가 다시 생성되는 경우 사용자는 아무런 추가 작업없이 View의 상태들이 유지된다. 하지만 Activity가 자체적으로 가지고 있던 여러 가지 상태 정보들은 가지고 있지 않으므로 이를 저장하고 복원시키는 작업도 필요할 것이다.


* 노트: 안드로이드 시스템에서 자동으로 View의 상태들을 복원시키려면 각 View는 반드시 android:id로 설정한 유니크한 id를 가지고 있어야한다.


: 이렇게 추가적인 데이터를 저장하고 싶다면 onSaveInstanceState() 콜백 함수를 오버라이드해야한다. 시스템은 사용자가 Activity를 떠날 때 Bundle로 현재 Activity View의 정보들을 줘서 예상치 못하게 종료될 경우를 대비하게 된다. 나중에 Activity가 다시 시작될 때에는 onCreated 가 먼저 호출되고 Created 에서 Resumed 상태로 넘어가면서 onRestoreInstanceState() 함수가 콜백된다. 




: 이러한 루틴은 시스템에서 예상치 못한 상황에서 Destroy를 한 경우, 그리고 화면을 회전하는 경우 일어나게 된다.


* Activity의 상태 저장하기

: 위에서 언급했다시피 Destroy 될때 onSaveInstanceState() 함수를 오버라이드 해야한다. 이 때 Bundle객체에서는 EditText에 저장되어있는 텍스트라던지, ListView의 현재 스크롤 위치 등을 자동으로 저장하게 된다. 하지만 Activity의 다른 상태들은 저장을 안하므로 이러한 경우는 직접 저장해줘야한다. 대표적인 예가 바로 게임에서의 점수를 저장하는 것이라고 생각할 수 있을 것이다. 아래는 간단한 예이다.

// Bundle에 저장할 키값
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    //  사용자가 현재 진행 중이던 상태를 Bundle에 저장한다.
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
    
    // *항상 super를 호출해줘야 안드로이드에서 View의 상태들도 저장할 것이다.
    super.onSaveInstanceState(savedInstanceState);
}


* Activity의 상태 복원하기

: Activity가 Destroy 되었다가 다시 복원될 때에는 onCreate 함수, 그리고 onRestoreInstanceState() 함수를 통해서 초기화를 하면 된다. 따라서 onCreate를 통해서 복원하는 방법과 onRestoreInstanceState를 통해서 초기화하는 2가지 방법이 있다. 아래는 onCreate에서 복원하는 방법이다.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // super부터 호출해야 View의 값들이 복원될 것이다.
   
    // 만약 Bundle이 null이 아니라면 Destroy 되었던 Activity를 다시 초기화하는 것이다.
    if (savedInstanceState != null) {
        // 저장해둔 Bundle이 있다면 상태를 복원한다.
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // 저장해둔 Bundle이 없는 경우 기본값 설정
    }
}


: onCreate는 Activity가 Destroy되었다가 다시 복원되는 경우와 처음으로 생성되는 두가지 경우를 고려하여 저장된 상태(Bundle savedInstanceState)가 있는지 구분해서 복원하거나 초기화를 하면 된다. 반면 onRestoreInstanceState() 함수를 오버라이드한다면 이는 무조건 Destroy에서 복원되는 것이므로 그냥 바로 상태를 복원하면 된다.

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // * 반드시 super를 실행해야 View의 상태들이 복원될 것이다.
    super.onRestoreInstanceState(savedInstanceState);
   
    // 저장했던 상태들을 바로 복원하면 된다.
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}


: 화면이 회전을 할 때에는 이러한 방법을 사용해도 되고 또 다른 방법에 대해서는 다음에 공부를 해볼 것이다.


[Android(안드로이드 앱 개발 기초] Activity 라이프사이클 공부 끝.


- 다음 글

2012/11/24 - [Android(안드로이드) 앱 개발 응용] Google Map API로 지도 보여주기(MapView), Overlay Item 그려주기 예제

2012/11/28 - [Android(안드로이드) 앱 개발 응용] Location GPS 위치 가져오기 및 최적화

2012/12/05 - [Android(안드로이드) 앱 개발 기초] 런타임 설정(로테이션, orientation) 변환 라이프사이클

2012/12/19 - [Android(안드로이드) 앱 개발 응용] 쉽게 Google Map 위에 말풍선 띄우기

2013/03/03 - [Android(안드로이드) 앱 개발 기초] Fragment 기초

2014/09/24 - [Android(안드로이드) 앱 개발 기초] ContentProvider 앱 간 데이터 공유 기본

2014/10/20 - [Android(안드로이드) 앱 개발 기초] MediaPlayer 음악 재생하기


Portions of this page are reproduced from work created and shared by the Android Open Source Project and used according to terms described in theCreative Commons 2.5 Attribution License.

원문: http://developer.android.com/training/basics/activity-lifecycle/index.html

이 글을 공유하세요.

  • Sunbae.Lee 2013.02.20 09:58  댓글주소  수정/삭제  댓글쓰기

    처음 부터 정독 하였습니다.
    덕분에 도움을 많이 받고 다녀갑니다.

  • jmpark 2013.02.26 13:47  댓글주소  수정/삭제  댓글쓰기

    도움 많이 되었습니다.

  • 2013.09.23 01:08  댓글주소  수정/삭제  댓글쓰기

    다른자료도 봤는데 가장 이해가 잘되는거 같아요 ~ 감사합니다.

    한가지 질문드릴게요

    액티비티가 인텐트로 계속 전환되서 들어가는데 이게 계속 스택에 쌓이게 되잖아요 그래서 넘어가다보면 메모리가 부족하다고 합니다. 넘어가지도 않구요 이럴때 생명주기를 이용해서 액티비티 메모리 관리를 해줘야 할거 같은데 아직 어떤 부분에서 메모리를 꺼줘야하는지 잘 모르겟네요 세부적인 기능은 없는거고 배경하고 뷰페이저를 이용한 액티비티 입니다.

    • Unikys 2013.09.23 11:31 신고  댓글주소  수정/삭제

      메모리가 부족한 것은 일단 Activity를 많이 만들어서 일어날 확률은 적습니다. 'Activity를 많이 만들어서'라기보다는 Activity안에서 사용하고 있는 메모리가 많기 때문일 확률이 높습니다. 따라서, 각 Activity마다 얼마나 메모리를 사용하고 있는지 한번 감안해보시고, 메모리를 다수 차지하는 부분은 onStop에서 해제를 하고, onStart에서 다시 불러오는 방식을 취하시는 것이 좋을 겁니다. 댓글로 Activity안에 배경과 뷰페이저가 있다고 하시니, 이미지가 많다면 한번 이에 대해 생각해볼 수 있죠. 이미지는 메모리를 많이 먹는 대표적인 객체이므로, onStop에서 이미지를 해제했다가, onStart에서 다시 불러오는 등의 작업을 하시면 메모리 사용 효율이 월등하게 늘어나겠지요. 아니면 이미지의 해상도 조절로 처음부터 차지하는 메모리를 줄일 수도 있고요. 또 다른 방법으로는, 이전의 Activity를 사용안할거라면 그냥 finish 시켜버리면 Activity에서 할당 받은 메모리는 자동으로 해제해주므로 UX상 이렇게 적용해도 되는지 검토를 해봐야겠지요. 위의 방법으로도 해결책을 못찾는다면 memory leak이 있는지 체크해봐야겠지요.
      메모리 사용 분석을 위해 아래와 같은 툴을 이용하시는 것도 좋습니다.
      http://www.eclipse.org/mat/

  • 갈때까지 가보자 2013.11.19 18:20 신고  댓글주소  수정/삭제  댓글쓰기

    큰 도움 받았습니다. 내용이 좋아서 그런데 퍼가도 되나요?

  • 용용 2014.07.16 15:41  댓글주소  수정/삭제  댓글쓰기

    덕분에 많이 알고 갑니다!

  • 사랑은 2014.12.04 15:48  댓글주소  수정/삭제  댓글쓰기

    정말 감사합니다...! 이해하기 편하게 잘 쓰셨네요...

  • 5일차 2015.01.02 20:57  댓글주소  수정/삭제  댓글쓰기

    감사합니다. 콜백함수의 싸이클에대해 전혀 알지 못했었는데 정말 감사합니다
    아주 유용한 양질의 글이군요 .

질문이나 의견을 댓글로 달아 주세요