(원문: http://developer.android.com/guide/components/activities.html)

(위치: Develop > API Guides > App Components > Activities)

(전체 번역 글 목록으로 이동하기)


Activity에 대하여


액티비티(Activity)는 사용자에게 UI가 있는 화면을 제공하는 앱 컴포넌트입니다. 다시 말해서, 폰 다이얼러 화면, 카메라 촬영 화면, 이메일 쓰기 화면, 지도 보기 화면 등과 같이 사용자들이 뭔가 하기 위해 상호작용을 할 수 있는 화면을 제공한다는 것입니다. 각 액티비티는 하나의 윈도우에 UI를 그리며, 그 윈도우가 보통은 화면을 꽉 채우지만, 화면보다 작을수도 있고, 다른 윈도우의 위에 떠 있을 수도 있습니다.

앱은 보통 여러개의 액티비티로 이루어져 있고, 각 액티비티는 서로 느슨한 관계를 갖습니다. 일반적으로, 앱은 하나의 메인(main) 액티비티를 갖고, 그것은 사용자가 앱을 처음 실행했을때 보여지는 액티비티입니다. 각 액티비티는 다른 액티비티를 실행할 수 있습니다. 다른 액티비티가 실행되면 이전의 액티비티는 정지되지만(stopped), 시스템이 "백스택"이라 불리는 스택에 저장해뒀기 때문에 없어지지는 않습니다. 다시 말해서, 시스템은 새로운 액티비티를 시작하면 백스택에 담고 나서 사용자에게 보여줍니다. 백스택은 "후입선출"의 스택 메커니즘을 따르며, 사용자가 뒤로버튼을 누를 경우, 스택의 최상위(top)에 있는 현재 액티비티를 제거(pop and destroy)하고 이전의 액티비티를 시작합니다. 백스택에 대한 더 자세한 내용은 태스크와 백스택에서 학습하실 수 있습니다.

새로운 액티비티가 실행되면서 현재 액티비티가 정지하게 되면, 시스템은 생명주기의 콜백 메소드를 호출함으로써 액티비티의 상태가 변경되었음을 알려줍니다. 이러한 콜백 메소드들은 시스템이 액티비티를 생성하고, 보여주고, 멈추고, 제거하는 등의 상황에 호출되며, 적절히 오버라이드(override)함으로써 각각의 상황에 맞는 동작을 구현할 수 있습니다. 예를 들어, 액티비티는 정지되었을 때 네트웍이나 데이터베이스 관련 객체와 같이 덩치가 큰 객채들을 해제하는 것이 좋고, 액티비티가 다시 화면에 보여질 때 필요한 리소스들을 다시 가져와서 중지되었던 작업들을 다시 실행할 수 있습니다. 이렇게 액티비티가 생성되고 보여지고 멈추고 제거되고 하는 등의 상태변화는 모두 액티비티 생명주기의 일부입니다. 

여기서는 액티비티를 만들고 사용하는 기본적인 방법과 액티비티의 생명주기에 대한 내용, 그리고 다양한 콜백 메소드 구현 방법에 대해 학습할 것입니다.


액티비티 생성하기


액티비티를 생성하기 위해서는 Activity 클래스를 상속받거나, Activity 클래스의 서브클래스를 상속받아서, 생명주기에 따른 콜백 메소드들을 오버라이드하여 원하는대로 구현하면 됩니다. 콜백 메소드들 중 아래 2개의 메소드가 가장 중요합니다.

onCreate()
반드시 구현되어야 하는 콜백 메소드로서, 액티비티가 생성될 때 호출됩니다. 여기서 액티비티에 필요한 요소들을 초기화 및 멤버변수로 저장하고, setContentView()를 호출하여 액티비티의 레이아웃을 지정해야 합니다.

onPause()
사용자와 액티비티의 거리가 한단계 멀어지는 경우에 호출되는 콜백 메소드입니다. 달리 말하면, 액티비티가 화면에 보여지고 있는 상태와 완전히 정지되어 화면에서 사라진 상태의 중간 단계라고 볼 수 있습니다. 이 상태 이후에 액티비티가 종료될 수도 있기 때문에, 여기서는 액티비티가 다시 시작되었을 때 유지되어야 하는 값들이 있을 경우 저장하는 일을 합니다.

위의 두 콜백 메소드 외에도, 유연한 사용자 경험을 제공하고 예상치 못하게 액티비티가 멈추거나 종료되는 상황들에 대응하기 위하여 사용할 수 있는 콜백 메소드들이 더 있습니다. 모든 생명주기 콜백 메소드들에 대한 더 자세한 내용은 아래의 액티비티 생명주기 관리하기 부분에서 학습하실 수 있습니다.


사용자 인터페이스(UI) 구현하기

액티비티의 UI는 View클래스를 상속받은 뷰(view)들의 계층구조에 의해 제공됩니다. 각 뷰는 액티비티의 윈도우 안에서 일부 사각형 영역을 할당 받고, 사용자와 상호작용을 할 수 있습니다. 예를 들어, 액티비티 안에 버튼뷰가 있다면, 그 버튼뷰가 차지한 사각형 영역을 사용자가 터치할 경우 앱이 그에 따른 동작을 수행할 수 있습니다. 

안드로이드는 앱의 레이아웃을 쉽게 구성할 수 있도록 미리 만들어진 뷰들을 제공합니다. "위젯(Widgets)"은 Button, EditText, CheckBox, ImageView 등과 같이 사용자와 상호작용할 수 있는 뷰이고, "레이아웃(Layouts)"은 LinearLayout, GridView, RelativeLayout 등과 같이 자식뷰들을 갖는 뷰로서 ViewGroup 클래스를 상속받아 만들어졌습니다. 그리고 View클래스나 ViewGroup클래스, 또는 위젯이나 레이아웃 등을 상속받아 그것의 서브클래스를 만들어서 사용할 수도 있습니다. 

액티비티의 레이아웃을 정의하는 가장 일반적인 방법은 XML 레이아웃 파일을 만들고 앱의 리소스로 저장하는 것입니다. 이 방법은 앱의 동작을 책임지는 소스코드와 앱의 UI 디자인을 분리시켜 줍니다. 액티비티에서 레이아웃은 보통 setContentView()에 레이아웃의 리소스 ID를 넘겨서 지정하지만, 소스코드에서 생성된 뷰 객체를 setContentView()에 넘기기도 합니다. 

UI를 만드는 방법에 대하여 더 자세한 내용은 사용자 인터페이스 문서에서 학습하실 수 있습니다.


매니페스트에 액티비티 정의하기

시스템이 액티비티를 인식하기 위해서는 매니페스트 파일(AndroidManifest.xml)에 그 액티비티가 정의되어 있어야 합니다. 따라서 아래와 같이 매니페스트 파일의 <application>요소 안에 <activity>요소를 추가합니다.

<manifest ... >
 
<application ... >
     
<activity android:name=".ExampleActivity" />
      ...
 
</application ... >
  ...
</manifest >

<activity>요소에는 액티비티의 이름이나 아이콘, 테마, 스타일 등의 속성을 추가할 수 있습니다. android:name 속성은 필수이며 액티비티의 클래스명을 지정합니다. 이 값은 앱 출시 이후에는 되도록이면 바꾸지 않는 것이 좋습니다. 왜냐하면 바뀌기 전의 액티비티명을 갖는 명시적 인텐트에 의해 실행되는 경우가 만약에라도 있다면 에러가 발생할 것이기 때문입니다. 

<activity>요소 정의하기에서 더 자세한 내용을 학습하실 수 있습니다.


인텐트 필터 사용하기

<activity>요소는 다른 앱의 컴포넌트가 자신을 실행할 수 있도록 하기 위해 <intent-filter>요소를 포함할 수 있습니다.

안드로이드 SDK를 이용하여 새 앱 프로젝트를 생성할 때, 아래와 같이 "main" 액션과 "launcher" 카테고리를 갖는 인텐트 필터가 추가된 액티비티를 자동으로 생성할 수 있습니다.

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
   
<intent-filter>
       
<action android:name="android.intent.action.MAIN" />
       
<category android:name="android.intent.category.LAUNCHER" />
   
</intent-filter>
</activity>

위의 예제에서, <action>요소는 해당 액티비티가 앱의 진입점임을 나타내고, <category>요소는 시스템 런처의 앱 목록에 보여준다는 것을 의미합니다.

내 앱이 다른 앱에 의해 실행되는 것을 원치 않는다면, 인텐트 필터를 추가할 필요가 없으며, 명시적 인텐트를 통해서만 액티비티를 실행하도록 하면 됩니다. 그리고 위와 같이 "main" 액션과 "launcher" 카테고리를 갖는 인텐트 필터는 하나의 앱에 대하여 오직 하나의 액티비티에서만 가질 수 있습니다. 

반대로, 다른 앱으로부터나 내 앱 내에서 암묵적 인텐트로 액티비티를 실행하고자 한다면, 액티비티에 인텐트 필터를 추가해야 합니다. <intent-filter><action>은 필수사항이며, <category><data>는 선택사항입니다. 암묵적 인텐트로 어떤 액티비티가 실행될 것인가를 이러한 요소들로 정해줄 수 있는 것입니다.

액티비티가 인텐트에 어떻게 반응하는지에 대한 더 자세한 내용은 인텐트와 인텐트 필터에서 학습하실 수 있습니다.


액티비티 실행하기


어떤 액티비티를 실행할지에 대한 정보를 가지고 있는 인텐트를 startActivity()에 넘김으로써 액티비티를 실행할 수 있습니다. 인텐트는 명시적으로 실행할 액티비티를 지정할 수도 있고, 암묵적으로 액션타입을 지정하여 시스템이 조건에 부합하는 액티비티들을 찾도록 할 수도 있습니다. 또한 인텐트는 실행될 액티비티에서 필요로 하는 데이터들을 전달하는 역할도 합니다.

다른 앱과 상호작용하는 앱이 아니라면, 아래 예제와 같이 명시적으로 액티비티를 지정하는 명시적 인텐트만 사용해도 될 것입니다.

Intent intent = new Intent(this, SignInActivity.class);
startActivity
(intent);

하지만, 이메일을 쓰거나 메시지를 보내거나 SNS앱의 내 상태를 업데이트 하는 등의 일을 하려는데 내 앱에서는 해당 액티비티들을 구현하지 않았다면, 아래 예제와 같은 암묵적 인텐트를 이용해서 디바이스에 설치된 다른 앱의 해당 액티비티를 내 앱의 일부인 것처럼 실행할 수 있습니다. 이것이 인텐트의 매력적인 부분인데, 인텐트에 액션값을 지정하여 액티비티 실행요청을 하면 시스템이 조건에 부합하는 액티비티들을 찾아서, 1개이면 바로 실행하고 2개 이상이면 사용자가 선택할 수 있도록 보여줍니다. 

Intent intent = new Intent(Intent.ACTION_SEND);
intent
.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity
(intent);

위 예제의 인텐트는 이메일앱이나 메신저앱, SNS앱 등의 ACTION_SEND를 허용하는 앱들로 메시지를 전송하기 위해 만든 인텐트이며, EXTRA_EMAIL은 사용자가 이메일앱을 선택한 경우에 수신자 입력란(To.)에 미리 채워줄 수신자 이메일 주소들입니다. 위의 인텐트로 액티비티가 실행되었다가 할일을 모두 마치고 액티비티가 종료되면 바로 이전의 액티비티(새 액티비티를 실행했던 액티비티)가 다시 시작됩니다.


액티비티를 실행하여 결과값 얻기

때로는 액티비티를 실행하여 그로부터 어떤 결과값을 얻어야 하는 경우가 있습니다. 예를 들면, 내 사진 중 하나를 업로드하기 위해 갤러리앱에서 사진을 선택하는 경우가 있을 수 있겠죠. 이때는 액티비티 실행을 위해 startActivityForResult()를 실행하고, 실행된 액티비티에서 결과값 셋팅 후 종료되면, 이전 액티비티의 onActivityResult()에서 해당 결과값을 전달받아 관련 작업을 할 수가 있습니다.

아래 예제는, 내 액티비티가 주소록의 사람 목록 액티비티로부터 누군가의 정보를 가져와서 원하는 작업을 하는 코드 일부입니다.

private void pickContact() {
   
// Create an intent to "pick" a contact, as defined by the content provider URI
   
Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
    startActivityForResult
(intent, PICK_CONTACT_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   
// If the request went well (OK) and the request was PICK_CONTACT_REQUEST
   
if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
       
// Perform a query to the contact's content provider for the contact's name
       
Cursor cursor = getContentResolver().query(data.getData(),
       
new String[] {Contacts.DISPLAY_NAME}, null, null, null);
       
if (cursor.moveToFirst()) { // True if the cursor is not empty
           
int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
           
String name = cursor.getString(columnIndex);
           
// Do something with the selected contact's name...
       
}
   
}
}

위의 예제에서는 onActivityResult()를 어떻게 구현할지에 대한 기본적인 로직을 보여줍니다. 첫째로 요청이 성공했는가, 즉 실행된 액티비티에서 정상적으로 결과값을 셋팅했는지를 확인하기 위해 resultCodeRESULT_OK인지를 비교합니다. 그리고 요청이 여러개일 수 있기 때문에 요청을 구분하기 위해 requestCodestartActivityForResult() 호출할 때 보냈던 값과 같은지를 비교합니다. 그리고나서, 매개변수로 전달받은 Intent객체(data)를 이용하여 선택된 주소록 사용자 정보를 구합니다.

ContentResolver는 사용자 정보를 쿼리하여 Cursor객체를 래턴해줍니다. 더 자세한 내용은 컨텐트 프로바이더에 대하여에서 학습하실 수 있습니다.

그리고 인텐트 사용 방법에 대한 더 자세한 내용은 인텐트와 인텐트 필터에서 학습하실 수 있습니다.


액티비티 종료하기


finish()를 호출함으로써 현재 액티비티를 종료할 수 있고, finishActivity()를 호출함으로써 startActivityForResult()로 호출했던 액티비티를 종료할 수 있습니다. 

메모: 보통은 위의 메소드들을 이용하여 액티비티를 종료할 필요가 없습니다. 시스템이 액티비티의 생명주기를 관리해주기 때문이며, 액티비티를 강제로 종료하는게 오히려 사용자에게 좋지 않은 영향을 미칠 수도 있기 때문입니다. 따라서, 액티비티가 다음 액티비티를 실행하면서 자신은 스택에 아예 남지 않기를 원하는 경우에만 액티비티를 명시적으로 종료하도록 합니다.


액티비티 생명주기 관리하기


액티비티의 콜백 메소드들을 구현함으로써 생명주기를 관리하는 것은 안드로이드 앱을 만드는데에 필수적인 부분입니다. 액티비티의 생명주기는 다른 액티비티들과의 관계, 그리고 그들의 태스크와 백스택에 직접적으로 영향을 받습니다. 

액티비티의 상태에는 아래의 세가지가 있습니다.

Resumed
액티비티가 전면(foreground)에 있고, 사용자가 인터페이스를 사용할 수 있도록 포커스를 가지고 있는 상태입니다. ("running" 상태라고도 합니다.)

Paused
다른 액티비티가 resumed 상태가 되었으나 여전히 이전 액티비티가 일부 보이고 있는 상태입니다. 다시 말해서, 이전 액티비티 위에 다음 액티비티가 보여지고 있지만, 반투명하거나 화면의 일부만 덮고 있는 상태입니다. paused 상태의 액티비티는 resumed 상태와 마찬가지로 메모리에 살아 있으며 여전히 윈도우 매니저와 연결되어 있습니다. 하지만 메모리가 부족한데 다른데에서 끌어쓸 자원이 없다면 시스템에 의해 종료될 수도 있는 상태입니다. 

Stopped
다른 액티비티가 resumed 상태가 되면서 이전 액티비티가 화면에서 완전히 사라져서 후면(background)으로 넘어간 상태입니다. stopped 상태의 액티비티도 paused 상태와 마찬가지로 메모리에 살아는 있으나 윈도우 매니저와의 연결은 끊어진 상태입니다. 따라서 메모리가 부족할 때 시스템에 의해서 종료될 수 있습니다.

만약 액티비티가 pausedstopped 상태가 되면, 시스템은 finish()메소드를 호출하거나, 바로 프로세스를 종료함으로써 액티비티 객체를 메모리에서 날려버릴 수 있습니다. 그럴 경우 액티비티를 다시 실행하면, 당연히도 다시 생성하게 됩니다.


생명주기 콜백 메소드 구현하기

액티비티의 상태들이 변경될 때, 콜백 메소드들이 호출됩니다. Activity클래스를 상속받아 내 액티비티를 만들 때, 모든 콜백 메소드들을 오버라이드(override)하여 구현할 수 있습니다. 아래 예제에는 액티비티의 주요 콜백 메소드들이 포함되어 있습니다.

public class ExampleActivity extends Activity {
   
@Override
   
public void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
// The activity is being created.
   
}
   
@Override
   
protected void onStart() {
       
super.onStart();
       
// The activity is about to become visible.
   
}
   
@Override
   
protected void onResume() {
       
super.onResume();
       
// The activity has become visible (it is now "resumed").
   
}
   
@Override
   
protected void onPause() {
       
super.onPause();
       
// Another activity is taking focus (this activity is about to be "paused").
   
}
   
@Override
   
protected void onStop() {
       
super.onStop();
       
// The activity is no longer visible (it is now "stopped")
   
}
   
@Override
   
protected void onDestroy() {
       
super.onDestroy();
       
// The activity is about to be destroyed.
   
}
}

메모: 위와 같이 콜백 메소드를 구현할 때는 항상 부모 클래스의 메소드인 super.onXXX()를 호출해줘야 합니다. 

종합적으로 생각해보면, 이러한 콜백 메소드들은 액티비티의 전체 생명주기를 정의합니다. 콜백 메소드들을 구현함으로써 생명주기 안에 있는 세가지 중첩된 반복구간(nested loops)을 관리할 수 있습니다. 그 세가지는 아래와 같습니다.

  • 전체 구간(entire lifetime)onCreate()에서 시작하여 onDestroy()로 끝납니다. onCreate()에서 레이아웃 정의와 같은 액티비티 전체에 대한 준비 작업을 하고, onDestroy()에서 리소스들을 해제합니다. 예를 들어 액티비티에서 네트웍을 통해 필요한 데이터를 받아와야 한다면, 쓰레드를 onCreate()에서 생성하여 멤버 변수로 가지고 있다가 onDestroy()에서 해제하면 될 것입니다.

  • 보이는 구간(visible lifetime)onStart()에서 시작하여 onStop()으로 끝납니다. 이때 사용자는 액티비티를 화면에서 볼 수 있으며 상호작용(interact)할 수 있습니다. 만약 다른 액티비티가 실행되면서 현재 액티비티의 onStop()이 호출되었다면 그 액티비티는 화면에 보이지 않을 것입니다. onStart()와 onStop() 사이에서는 액티비티가 사용자에게 보여지는 동안 필요한 리소스들을 잘 가지고 있어야 합니다. 예를 들면, 브로드캐스트 리시버를 등록하는 작업은 보통 onStart()에서 하며, onStop()에서 등록해제를 합니다. 이것은 브로드캐스트를 받아서 하는 일이 보통 화면 UI를 변경하는 일이기 때문에 보이는 구간에서만 등록되어 있도록 하는 것입니다. 전체 구간 안에서 보이는 구간은 여러번 있을 수 있습니다. 다시 말해서, onCreate()와 onDestroy() 사이에서 onStart()와 onStop()이 여러번 호출될 수 있다는 것입니다.

  • 전면 구간(foreground lifetime)onResume()에서 시작하여 onPause()로 끝납니다. 이때 액티비티는 가장 전면에, 즉 사용자와 가장 가까운 곳에 있고 사용자와 상호작용할 수 있도록 포커스를 갖습니다. onPause()는 디바이스가 슬립모드로 들어가는 경우(화면이 꺼지는 경우)와 다이얼로그가 뜨는 경우 등에 호출됩니다. 이렇듯 액티비티는 전면에 나섰다가 빠지는 경우가 자주 발생하기 때문에, 콜백 메소드에서 시간이 오래 걸리는 작업은 되도록 피해야 합니다.

아래의 그림1은 위의 세가지 구간과 상태들간의 이동흐름을 나타냅니다. 사각형은 상태가 변할때 호출되는 콜백 메소드를 표현합니다.


그림 1. 액티비티 생명주기


아래의 표1에서도 생명주기를 표현하는데, 각 콜백 메소드에 대하여 더 자세히 설명하며, 콜백 메소드가 호출된 후 (다른 콜백 메소드가 호출되기 전) 시스템에 의해 종료될 수 있는지 여부와 다음으로 호출될 콜백 메소드가 무엇인지를 알려줍니다.


표 1. 액티비티 콜백 메소드에 대한 설명

 콜백메소드

 설명

 콜백메소드
 호출후 종료
 가능여부

 다음으로
 호출될
 콜백메소드

 onCreate()

 액티비티가 생성될때 호출되며, 뷰를 생성하거나 리스트에 데이터를 바인드하는 등의 구현을 여기에 하도록 합니다. 매개변수로 Bundle객체를 받는데, 종료될때 액티비티의 상태를 저장했다면(액티비티의 상태 저장하기 참고) 해당 Bundle객체를 통해 그 상태를 복구할 수 있습니다.
onCreate() 다음으로는 항상 onStart()가 호출됩니다.

 불가능

 onStart()

 - onRestart()

 액티비티가 정지되었다가 다시 시작되기 직전에 호출됩니다. 
onRestart() 다음으로는 항상 onStart()가 호출됩니다.

 불가능

 onStart()

 - onStart()

 액티비티가 사용자에게 보여지기 전에 호출됩니다. 
 이어서 액티비티가 전면에 나서며 포커스를 받을때 onResume()이 호출되며, 그렇지 않고 바로 가려지면 onStop()이 호출됩니다.

 불가능

 onResume()
 또는
 onStop()

 - - onResume()

 액티비티가 사용자와 상호작용을 할 수 있는 상태가 되기 직전에 호출됩니다. 이때 액티비티는 백스택의 최상단에 있고, 사용자는 액티비티에 뭔가를 할 수 있는 상태가 됩니다.
 onResume() 다음으로는 항상 onPause()가 호출됩니다.

 불가능

 onPause()

 - - onPause()

 시스템이 다른 액티비티를 resume상태로 만들때 호출됩니다. 여기서는 보통 데이터를 저장하거나 애니메이션을 정지하거나 CPU 사용량을 줄이기 위해 정지시켜도 될만한 객체들의 동작을 정지시키는 등의 구현을 합니다. 다만 onPause()가 완료된 후에야 다른 액티비티가 resume상태가 될 수 있기 때문에 시간이 오래 걸리는 작업은 피해야 합니다.
 이어서 액티비티가 다시 resume상태가 된다면 onResume()이 호출되고, 아예 화면에서 보이지 않게 되면 onStop()이 호출됩니다.

 가능

 onResume()
 또는
 onStop()

 - onStop()

 액티비티가 더이상 사용자에게 보여지지 않을때 호출됩니다. 이러한 경우는 보통 액티비티가 종료되는 상황이거나, 다른 액티비티가 resume상태가 되면서 이전 액티비티를 완전히 가려버리는 상황입니다. 
 이어서 액티비티가 다시 사용자에게 보여지는 상태가 되는 경우 onRestart()가 호출되고, 액티비티가 종료되고 백스택에서도 빠지는 경우 onDestroy()가 호출됩니다.

 가능 

 onRestart()
 또는
 onDestroy()

 onDestroy()

 액티비티가 종료될 때 호출되며, 생명주기에서 마지막으로 호출되는 콜백 메소드입니다. finish()가 호출되었거나, 시스템이 다른 작업을 위한 메모리를 확보하기 위해 임의로 종료시키는 경우에 호출되는데, 이 두가지 경우는 isFinishing()을 통해 구별할 수 있습니다. finish()가 호출된 경우 isFinishing()은 true를 리턴합니다.

 가능

 없음


"콜백 메소드 호출후 종료 가능여부"는 해당 메소드가 리턴된 후 다른 코드의 실행이 없는 상태에서 시스템이 액티비티를 종료할 수 있는지 여부를 나타내며, onPause(), onStop(), onDestroy() 이렇게 세개만이 "가능"합니다. 이 중 onPause()가 가장 먼저 호출되는 메소드이고 여기서 시스템에 의해 종료되면 onStop()onDestroy()가 호출되지 않을 수도 있기 때문에, onPause()에서 중요한 데이터들은 저장해야 합니다. 하지만 onPause()에서 어떤 일을 할지에 대해서는 신중히 결정해야 하는데, 그것은 onPause()에서 시간이 오래 걸리는 작업으로 프로시져를 붙들고 있으면 다음 액티비티의 실행이 늦어질 수 있기 때문입니다.

"콜백 메소드 호출후 종료 가능여부"가 "불가능"인 콜백 메소드들이 호출된 후 다른 메소드가 호출되기 전까지는, 액티비티를 실행중인 프로세스가 시스템의 종료 명령으로부터 보호됩니다. 따라서, 액티비티는 onPause()가 호출된 후부터 다시 onResume()이 호출되기 전까지는 시스템에 의해 종료될 수 있으며, 반대로 onResume()이 호출된 후부터 onPause()가 호출되기 전까지는 종료될 수 없습니다. 

메모: 표1에서 "콜백 메소드 호출후 종료 가능여부"가 "불가능"인 메소드가 호출된 상태라 하더라도 아주 긴급한 상황에서는 시스템에 의해 액티비티가 종료될 수도 있습니다. 액티비티가 종료되는 상황에 대한 더 자세한 내용은 프로세스와 쓰레드에서 학습하실 수 있습니다.


액티비티의 상태 저장하기

액티비티 생명주기 관리하기에서 설명한 바와 같이 액티비티는 paused나 stopped상태에서도 메모리에 액티비티의 상태를 나타내는 데이터들을 가지고 있습니다. 따라서, 다시 resume상태가 되면 paused나 stopped상태가 되기 전과 같은 상태가 되는 것입니다.

하지만 시스템이 메모리 확보를 위해 액티비티를 종료시키는 경우에는 그 액티비티를 다시 실행할 때 이전 상태가 메모리에 남아있지 않기 때문에 복구할 수 없습니다. 이러한 경우, 액티비티가 종료되기 전 호출되는 콜백 메소드인 onSaveInstanceState()에서 유지해야하는 데이터들을 저장해야 액티비티가 다시 생성될 때 저장된 데이터들을 전달받아 이전 상태를 복구할 수 있습니다.

onSaveInstanceState()에서 매개변수로 Bundle객체를 전달받는데, 이 객체의 putString(), putInt() 등의 메소드를 이용하여 이름-값 쌍의 데이터를 저장할 수 있습니다. 그러면 액티비티가 종료되었다가 다시 생성될 때 onCreate()onRestoreInstanceState()에서 매개변수로 받는 Bundle객체에 저장된 데이터들을 꺼내서 액티비티의 이전 상태를 복구할 수 있는 것입니다. 액티비티를 처음 생성하는 경우와 같이 복구할 데이터가 없는 경우에는 Bundle객체가 null로 옵니다.


그림 2. 액티비티가 이전 상태를 복구하는 두가지 경우를 보여줍니다. 하나는 액티비티가 종료되었다가 다시 생성될 때 저장된 데이터를 이용하여 복구하는 경우이고, 다른 하나는 액티비티가 stopped상태가 되었다가 다시 resume상태가 될 때 메모리에 데이터들이 그대로 살아있기 때문에 자연스럽게 이전 상태가 복구된 경우입니다.

메모: 액티비티가 종료되기 전에 onSaveInstanceState()의 호출이 반드시 보장되는 것은 아닙니다. 사용자가 명시적으로 뒤로버튼을 누르는 경우와 같이 액티비티의 이전 상태를 복구할 필요가 없는 경우에는 onSaveInstanceState()가 호출되지 않습니다. 만약 시스템이 onSaveInstanceState()를 호출한다면, 그것은 아마도 onStop()이나 onPause()보다 먼저 호출될 것입니다.


onSaveInstanceState()를 오버라이드하여 추가적인 구현을 하지 않더라도, 기본적으로 몇가지 복구되는 것들이 있습니다. Activity클래스의 onSaveInstanceState()에서는 액티비티의 모든 뷰의 onSaveInstanceState()를 호출하도록 되어 있습니다. 즉, 뷰가 이전 상태를 복구하도록 구현되어 있다면 그 뷰는 액티비티가 종료 및 재생성 될 때 이전 상태로 복구된다는 것입니다. 안드로이드 프레임웍의 거의 모든 위젯들은 이 onSaveInstanceState()가 적절히 구현되어 있습니다. 예를 들면, EditText 위젯은 사용자로부터 입력받은 문자열을 저장하고, CheckBox 위젯은 체크여부를 저장합니다. 다만, 이러한 뷰 및 위젯들을 레이아웃 xml파일에 정의할 때 android:id를 지정해야만 상태를 저장 및 복구하며, android:id를 지정하지 않으면 이전 상태가 복구되지 않습니다.

뷰나 위젯이 이전 상태를 저장 및 복구하지 않도록 하기 위해서는 android:saveEnabled"false"로 선언하거나, setSaveEnabled()메소드를 호출하면 됩니다. 보통은 굳이 이렇게 설정할 필요가 없으나, 액티비티의 UI를 뭔가 다르게 복구되도록 구현하고 싶다면 이용할 수도 있겠죠.

비록 뷰나 위젯이 기본적으로 이전 상태를 복구할 수 있다 하더라도, 원하는 모든게 복구될 수는 없으므로 결국 onSaveInstanceState()를 오버라이드해야 하는 경우가 생깁니다. 예를 들어, 내 액티비티를 만들면서 추가한 멤버변수에 대하여, 액티비티가 실행되는 동안 그 값이 변한다면, 액티비티가 종료 및 재생성될때 그 값을 저장 및 복구해야할 것입니다.

위에서 언급한 바와 같이 onSaveInstanceState()onRestoreInstanceState()는 뷰나 위젯의 상태 복구와 같이 기본적인 기능이 구현되어 있기 때문에, 그것들을 오버라이드할 경우 추가적인 구현부분 전에 반드시 super.onSaveInstanceState()super.onRestoreInstanceState()를 호출해줘야 합니다.

메모: onSaveInstanceState()는 호출이 보장되지 않기 때문에, 즉 호출되지 않는 경우도 있을 수 있기 때문에 액티비티 UI의 상태를 저장하는 용도로만 사용하도록 합니다. 그리고 반드시 저장해야하는 중요 데이터는 onPause()에서 저장하도록 합니다.

액티비티의 이전 상태 저장 및 복구에 대하여 가장 손쉬운 테스트 방법은, 디바이스의 오리엔테이션을 가로(landscape), 세로(portrait)로 번갈아서 변경해보는 것입니다. 이렇게 디바이스의 오리엔테이션이 변경될 경우, 기본적으로 시스템은 액티비티를 종료 및 재생성합니다. 가로 및 세로에 대하여 서로 다른 레이아웃이 적용될 수도 있기 때문이죠. 사용자들은 디바이스의 오리엔테이션을 수시로 변경할 수 있기 때문에 액티비티의 상태를 유지하도록 조치하는 것은 상당히 중요합니다.


디바이스의 상태가 변경될 때

스크린 오리엔테이션이나 키보드의 사용가능 여부, 언어 등의 디바이스 상태는 앱이 실행중일때 변경될 수도 있습니다. 이러한 경우 안드로이드 시스템은 액티비티를 종료 및 재실행하며, onDestroy()onCreate()가 연이어 호출됩니다. 이것은 디바이스의 상태가 변경될 경우 그에 맞는 리소스를 적용하기 위해서입니다. 가로일때와 세로일때 서로 다른 레이아웃 xml파일을 적용해야 하는 경우가 그 예입니다.

내 앱이 스크린 오리엔테이션의 변화에 적절히 대응하도록 개발되었다면, 액티비티의 생명주기상에서 일어날 수 있는 다른 불청객 이벤트에 대해서도 대응할 수 있을 것입니다.

위에서 계속 언급했던바와 같이 액티비티의 종료 및 재생성시 상태를 유지하는 가장 좋은 방법은, onSaveInstanceState()에서 상태를 저장하고, onRestoreInstanceState() 또는 onCreate()에서 상태를 복구하는 것입니다.

실행중에 디바이스의 상태가 변경될 때 그것을 다루는 방법들은 실행 중 상태 저장 및 복구하기에서 학습하실 수 있습니다.


액티비티들 조직화하기

어떤 액티비티가 다른 액티비티를 실행하는 경우, 그 둘 모두 상태의 변화가 발생합니다. 이전 액티비티는 paused 및 stopped상태가 되고(완전히 가려지지 않고 일부가 보인다면 stopped상태가 되지 않습니다), 다음 액티비티는 생성됩니다. 이때, 다음 액티비티가 생성되기 전까지 이전 액티비티가 완전히 정지(stopped)되지 않는다는 것을 기억해야 합니다. 

액티비티의 생명주기 콜백 메소드가 호출되는 순서는 잘 정의되어 있습니다. 아래 예제는 액티비티A가 액티비티B를 실행하는 경우 콜백 메소드가 호출되는 순서를 보여줍니다.

  1. 액티비티A의 onPause()가 호출됩니다.
  2. 액티비티B의 onCreate(), onStart(), onResume()이 차례로 호출됩니다. (이제 액티비티B가 포커스를 가지고 있습니다.)
  3. 액티비티A가 더이상 화면에 보이지 않는다면 onStop()이 호출됩니다.

위와 같은 콜백 메소드의 호출 순서를 잘 숙지하고, 액티비티들 사이의 상태 변화시 서로 공유되는 데이터들을 처리할 때 주의해야 합니다. 예를 들어 액티비티A가 정지될때 데이터를 DB에 저장하고, 액티비티B가 생성될때 그 데이터를 DB에서 불러와야 하는 경우, 액티비티A의 onStop()에서가 아니라 onPause()에서 데이터를 저장해야 한다는 것을 알 수 있습니다.


Posted by 개발자 김태우
,