태터데스크 관리자

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

태터데스크 메시지

저장하였습니다.

액티비티 생애주기, 눈으로 확인하자!

2009.10.11 05:26

강좌 작성환경
SDK Version : Android SDK 1.6, release 1
ADT Version : 0.9.3

추후 SDK업데이트로 인해 글의 내용과 실제 내용간 차이가 있을 수 있습니다.

지난 글 (2009/09/18 - [어플리케이션 구성/액티비티(Activity)] - 액티비티의 생애주기(Lifecycle)  에서 액티비티의 생애주기에 대해 알아보았습니다. 글에서 최대한 쉽게 설명한다고 설명하긴 했는데, 아무래도 실제로 동작하는 과정을 확인해보는 것이 더 이해가 빠르겠지요?

이번 글에서는 실제로 액티비티 라이프사이클을 확인할 수 있도록 상태가 변할 때마다 호출되는 메소드 안에 디버그 메시지를 출력하는 코드를 삽입하여 LogCat을 통해 상태변화를 관찰할 수 있도록 해보겠습니다.

새 프로젝트를 생성합니다. 저는 ActivityLifeCycle라는 이름으로 프로젝트를 생성하고, 액티비티 이름은 LifeCycleTester로 지정하였습니다. 액티비티 생애주기를 위한 어플리케이션은 크게 메인 액티비티에서 다른 액티비티를 호출하고, 호출한 액티비티를 종료하는 것과 다이얼로그를 띄우는 것으로 이루어져 있습니다. 즉, 액티비티는 두 개를 생성해야겠지요?

액티비티를 생성하는 과정을 잘 모르시는 분은 이 포스트를 참고하세요.

아래와 같이 코드를 작성합니다.

[LifeCycleTester.java]

package com.androidhuman.ActivityLifeCycle;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class LifeCycleTester extends Activity {
	private static final String TAG = "LifeCycleTester";
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate()");
        setContentView(R.layout.main);
        Button launchButton = (Button)findViewById(R.id.launchbutton);
        Button showdialog = (Button)findViewById(R.id.showdialog);
        launchButton.setOnClickListener(new OnClickListener(){

			public void onClick(View arg0) {
				startActivity(new Intent(LifeCycleTester.this, SubActivity.class));
				
			}
        	
        });
        
        showdialog.setOnClickListener(new OnClickListener(){

			public void onClick(View v) {
				AlertDialog.Builder dialog = 
					new AlertDialog.Builder(LifeCycleTester.this);
				dialog.setMessage("Dialog!");
				dialog.setTitle("Dialog Title");
				dialog.setPositiveButton("OK", 
						new DialogInterface.OnClickListener() {
					
					public void onClick(DialogInterface dialog, int which) {
						// TODO Auto-generated method stub
						
					}
				});
				dialog.show();
				
			}
        	
        });
    }
    
    @Override
    public void onPause(){
    	super.onPause();
    	Log.i(TAG, "onPause()");
    }
    
    @Override
    public void onStop(){
    	super.onStop();
    	Log.i(TAG, "onStop()");
    }
    
    @Override
    public void onResume(){
    	super.onResume();
    	Log.i(TAG, "onResume()");
    }
    
    @Override
    public void onStart(){
    	super.onStart();
    	Log.i(TAG, "onStart()");
    }
    
    @Override
    public void onRestart(){
    	super.onRestart();
    	Log.i(TAG, "onRestart()");
    }
    
    @Override
    public void onDestroy(){
    	super.onDestroy();
    	Log.i(TAG, "onDestroy()");
    }
}

위의 코드에서 확인할 수 있듯이, 액티비티의 상태가 변할 때마다 호출되는 메소드들을 오버라이드 한 후, 그곳에 LogCat을 통해 메시지를 확인할 수 있게끔 코드를 추가하였습니다.


Button launchButton = (Button)findViewById(R.id.launchbutton);
        Button showdialog = (Button)findViewById(R.id.showdialog);
        launchButton.setOnClickListener(new OnClickListener(){

			public void onClick(View arg0) {
				startActivity(new Intent(LifeCycleTester.this, SubActivity.class));
				
			}
        	
        });
        
        showdialog.setOnClickListener(new OnClickListener(){

			public void onClick(View v) {
				AlertDialog.Builder dialog = 
					new AlertDialog.Builder(LifeCycleTester.this);
				dialog.setMessage("Dialog!");
				dialog.setTitle("Dialog Title");
				dialog.setPositiveButton("OK", 
						new DialogInterface.OnClickListener() {
					
					public void onClick(DialogInterface dialog, int which) {
						// TODO Auto-generated method stub
						
					}
				});
				dialog.show();
				
			}
        	
        });


첫 번째 액티비티의 버튼은 두개로, 첫번째 버튼은 다른 액티비티를 띄우도록, 두번째 버튼은 다이얼로그를 띄우도록 되어있는 것을 확인할 수 있습니다.



[SubActivity.java]

package com.androidhuman.ActivityLifeCycle;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class SubActivity extends Activity {
	
	private static final String TAG = "SubActivity";

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    Log.i(TAG, "onCreate()");
	    setContentView(R.layout.sub);

	    Button destroyButton = (Button)findViewById(R.id.destroybutton);
	    destroyButton.setOnClickListener(new OnClickListener(){

			public void onClick(View v) {
				finish();
				
			}
	    	
	    });
	}
	
	@Override
    public void onPause(){
    	super.onPause();
    	Log.i(TAG, "onPause()");
    }
    
    @Override
    public void onStop(){
    	super.onStop();
    	Log.i(TAG, "onStop()");
    }
    
    @Override
    public void onResume(){
    	super.onResume();
    	Log.i(TAG, "onResume()");
    }
    
    @Override
    public void onStart(){
    	super.onStart();
    	Log.i(TAG, "onStart()");
    }
    
    @Override
    public void onRestart(){
    	super.onRestart();
    	Log.i(TAG, "onRestart()");
    }
    
    @Override
    public void onDestroy(){
    	super.onDestroy();
    	Log.i(TAG, "onDestroy()");
    }

}

두 번쨰 액티비티의 코드입니다. 버튼을 누르면 액티비티가 종료되게 되어있습니다.

아래는 각 액티비티의 레이아웃입니다.


[main.xml - LifeCycleTester]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="Main Activity" 
    />
<Button android:layout_width="wrap_content"
  android:layout_height="wrap_content" 
  android:id="@+id/launchbutton" 
  android:text="Launch Sub-Activity"></Button>

<Button android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:id="@+id/showdialog" 
  android:text="Show Dialog"></Button>
</LinearLayout>



[sub.xml - SubActivity]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content" android:orientation="vertical">
  
<TextView android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:text="Sub-activity"></TextView> 

<Button android:layout_height="wrap_content"
 android:layout_width="wrap_content"
 android:id="@+id/destroybutton" 
 android:text="Destroy this activity"></Button>
</LinearLayout>



위와 같이 작성을 완료하였다면, 이제 테스트를 해 볼 차례입니다. 프로젝트를 실행하여 에뮬레이터에 설치하도록 합시다. 보통 실행 버튼을 눌러 프로젝트를 실행하면 어플리케이션이 자동으로 실행되게 됩니다. 우선은 테스트를 위해 홈 버튼을 눌러 메인 화면으로 빠져나온 후, 프로세스를 종료시킵니다.

이클립스의 DDMS 화면 좌측 상단을 보면 컴퓨터와 연결된 에뮬레이터/장치의 목록과 각 장치에서 실행되고 있는 프로세스의 목록이 표시되는데, 이 중에서 종료시킬 프로세스를 선택한 후 빨간색 STOP 버튼을 누르면 프로세스를 강제로 종료할 수 있습니다.

종료할 프로세스를 선택한 후, Stop Process 버튼을 눌러준다.

 

프로세스를 종료한 후, 다시 ActivityLifeCycle 어플리케이션을 실행시키기 전에 미리 DDMS 화면의 하단에 위치한 LogCat 창을 클릭해서 내용을 확인할 수 있게 해 놓은 후, 어플리케이션을 다시 실행시켜줍니다.

어플리케이션을 실행시키면, 다음과 같이 액티비티의 상태 변화에 따라 로그가 출력되는 것을 볼 수 있습니다. 이 로그를 보면서 액티비티의 상태가 어떻게 변화하는지 알 수 있죠.


1. 어플리케이션 최초 실행


LifeCycleTester 어플리케이션이 실행된 모습입니다. 첫 액티비티가 화면에 표시되었습니다. LogCat을 확인해보니, 아래와 같이 액티비티의 상태가 어떻게 변했는지 표시되는군요. 우리가 알고있던 것처럼 onCreate() -  onStart() - onResume()순으로 상태가 변화하는 것을 관찰할 수 있습니다.



2. Dialog 호출

두번째 버튼을 눌러 다이얼로그를 호출해봅시다. 현재 액티비티가 가려질 때, 그 액티비티는 일시정지 상태가 되지만 다이얼로그는 그 액티비티의 일부로 보기 때문에 화면이 가려진다 할지라도 일시정지 상태로 변하지 않습니다.



3. 다른 액티비티 호출 / 종료

이번에는 첫번째 버튼을 눌러 다른 액티비티를 호출했다가, 종료해보도록 합니다. 버튼을 누르면 두번째 액티비티가 호출되며 첫번째 액티비티는 화면에서 사라지게 되고, 정지 상태(onStop())로 변하게 됩니다.


아래 로그를 보면, 두번째 액티비티인 SubActivity를 호출하면서 첫번째 액티비티가 정지 상태로 변하는 것을 관찰할 수 있습니다.



이후, 두번째 액티비티를 종료하는 버튼, "Destroy this Activity" 버튼을 누르면 두번째 액티비티가 종료되면서 다시 첫번째 액티비티가 화면에 나타나게 됩니다.


이렇게 해서 다시 첫 번째 액티비티가 표시된 후, Home버튼이나 Back 버튼을 눌러 첫번째 액티비티가 화면에서 사라진다 할지라도 첫번째 액티비티가 소멸(onDestroy()) 되지 않고 onStop() 상태로 대기하고 있는 것을 관찰할 수 있을 겁니다. 이렇게 액티비티는 특별히 메모리가 매우 부족한 경우가 아니면 바로 메모리에서 소멸되지 않고 메모리에 상주하여 다시 액티비티가 실행될 때 걸리는 시간을 줄여줍니다.


강좌에 사용했던 어플리케이션의 소스코드를 첨부합니다.


 
저작자 표시 비영리 변경 금지
신고

커니 어플리케이션 구성/액티비티(Activity) , , , , , , ,

  1. Blog Icon

    비밀댓글입니다

  2. 답장 드렸습니다 :)

  3. Blog Icon
    철이

    subactivity 와 sub.xml 을 연결시켜주는 내용이 없네요?
    저절로 되어야 하는건가요? sub.xml이 안만들어져서 직접 만든후에 그냥 실행시켜 봤는데 subactivity를 누르면 에러가 나네요. 커니님께서 올려주신 프로젝트로 해보면 잘되는데 말이죠.

    액티비티를 만들고나서 xml 파일을 만들고 서로 연결하는 부분에 대해서 좀더 자세하게 가르쳐 주시면 안될까요?

  4. setContentView(R.layout.sub);
    여기에 레이아웃을 설정하는 코드가 있습니다.

    레이아웃을 만드는 것과 관련된 것은
    http://androidhuman.tistory.com/entry/강좌-이클립스에서-액티비티-레이아웃을-만드는-쉬운-방법

    을 참조하세요.

  5. Blog Icon

    항상 좋은 강의 잘 보고 있습니다.
    본문의 소스로 sub 액티비티를 실행하는 버튼을 누르면 에러가 발생하는데,
    [sub.xml - SubActivity] 의 <Button>에서 layout_width가 빠져서 추가하니 에러가 잡힙니다.
    감사합니다.

  6. 편집과정에서 일부가 누락되었었군요 ^^;
    수정하였습니다. 감사합니다.

  7. 좋은 내용이네요 ..ㅎㅎ 제 컴터에서는 환경이 잘못되었는지.. R.java를 찾을수 없다면서 main.xml과 sub.xml이 제대로
    R.java를 만들어 내지 못하는 에러를 범하네요..ㅠㅠ gen 폴더와 bin폴더를 지웠다 다시 빌드를 몇번 하니.. 드뎌 제대로된 화면이 나타나는 기염을 토하는 업적을 이룩하였습니다. 되다 말다 하니 더더욱 모르겠네요 한마디로 미칠 노릇이죠 ㅎㅎ 아무튼 오늘 여기 사이트 다 후벼 파고 있습니다 ㅋㅋ

  8. Blog Icon
    글사랑

    너무 좋은 정보 감사합니다 이런 정보를 가지고 많이 실력향상하고 있네요 그런데. 이내용을 실행하려면 메니페스트 파일에 Activity를 추가해 주어야할 것 같은데.. 그부분이 설명이 빠져 있네요 그리고 전 왜 LogCat에 아무 내용도 나오지 않는 경우가 발생할까요 궁금하네요..

  9. 글 상단에 액티비티 추가 방법을 설명한 글로 링크 걸어드렸습니다 :) 사실 액티비티 추가하는건 기본 중의 기본인지라 글 하나하나에 그 내용을 넣는다는건 효율적이지 못합니다 ^^;

    그리고 Loccat에 아무 내용도 안나오는건 아마 emulator가 선택되어 있지 않아 그럴 것입니다. DDMS Perspective에서 왼쪽 상단의 디바이스 목록에서 에뮬레이터를 클릭하여 선택해보세요~

  10. Blog Icon
    steroid

    LifeCycleTester.java 의 37번째 줄에 보시면

    new AlertDialog.Builder(LifeCycleTester.this);

    라는 코드가 있습니다.

    여기서 AlterDialog.Builder클래스의 설명을 디벨로퍼 사이트에서 보면

    AlertDialog.Builder(Context context)

    Constructor using a context for this builder and the AlertDialog it creates.

    라고 나와있습니다. 매개변수로 context인자를 받는데요.

    여기서 위의 빨간색 부분에 new AlertDialog.Builder(getBaseContext());

    라고 하고, 에뮬레이터에서 run 후 Show Dialog 버튼을 클릭하시면

    이렇게 해도 에러..........



    new AlertDialog.Builder(getApplicationContext());

    이렇게 해도 에러입니다.



    분명히 둘다 context를 받아오는 메서드이고, 전달인자로 들어가는게 맞는것 같은데 모르겠네요.

    대충의 의미는 알고있는데(액티비티 환경을 담고있는거라는거), 정확한 context의 의미를 모르겠습니다.

    질문을 요약하면

    1. 왜 context를 전달했는데 버튼 클릭시 에러가 나는지요?
    2. LifeCycleTester.this 이것은 참조 인스턴스 아닌가요? context가 매개변수인데 참조 인스턴스를 받 아야 에러없이 실행되는 정확한 이유를 모르겠습니다.

    ㅠㅠ 읽어주셔서 감사하요

    fakecd2@nate.com으로 답변주시면 감사하겠습니다.
    항상 커니님의 좋은강의 감사합니다. 잘보고있습니다 ^^*

  11. Blog Icon
    난최장군

    안녕하세요 커니님. 처음으로 인사드리네요.
    강좌는 아주 잘 보고 있습니다^^ 너무 설명을 잘 적어주셔서 보는 저로썬 너무 감사할 따름입니다.
    이번 강좌에서 빠진 부분이 있는 듯 하여 이렇게 댓글을 답니다.
    manifest 에서 activity 를 추가 하는 부분이 빠져있는듯 합니다. 계속 에러가 나길래 3~4번 처음부터 다시
    짜봤네요 ㅠㅠ 강좌에 추가해주세요^^
    계속 강좌 정독 하겠습니다. 좋은 강좌 써주셔서 감사합니다^^

  12. Blog Icon
    질문!

    제가 지금 게임을 만들려고 하는데요 게임실행중 홈키를 눌려서 바탕화면으로 나왔다가
    다시 게임을 실행해도 게임이 계속 돌아 가게 하고 싶어요
    아부분은 강좌 처럼 구성 했구요
    @Override
    public void onPause() {
    super.onPause();
    Log.i("Test","onPause";);
    }
    @Override
    public void onStop(){
    super.onStop();
    Log.i("Test","onStop";);
    }
    @Override
    public void onResume() {
    super.onResume();
    Log.i("Test","onResume";);
    }
    @Override
    public void onStart() {
    super.onStart();
    Log.i("Test","onStart";);
    }
    @Override
    public void onRestart() {
    super.onRestart();
    Log.i("Test","onRestart";);
    }
    @Override
    public void onDestroy() {
    super.onDestroy();
    Log.i("Test","onDestroy";);
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.i("Test","onCreate";);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(new Panel(this));
    }

    이제 저 Panel 에서 아래와 같이 SurfaceView 를 받고 있어서

    class Panel extends SurfaceView implements SurfaceHolder.Callback

    public void surfaceChanged(SurfaceHolder holder, int format, int width,int height){
    Log.d("Test","SurfaceChanged!!";);
    }
    public void surfaceCreated(SurfaceHolder holder){
    Log.d("Test","SurfaceCreated!!";);
    _thread.setRunning(true);
    _thread.start();
    }
    public void surfaceDestroyed(SurfaceHolder holder){
    Log.d("Test","SurfaceDestroyed!!";);;
    boolean retry = true;
    _thread.setRunning(false);
    while(retry){
    try{
    _thread.join();
    retry =false;
    }
    catch(InterruptedException e)
    {

    }
    }
    }
    이렇게 되어 있는데 홈키를 눌려 버리면 계속 해서 surfaceDestroyed 해버리네요 ㅠㅠㅠ 어떻게 하죠 ??

  13. SurfaceView가 화면에서 사라지면 당연히 해당 surfaceview는 사라집니다. 이건 라이프사이클로 사용한다고 해서 살려둘 수가 없습니다.

    게다가 다른 간단한 UI가 아닌 게임같이 리소스를 많이 잡아먹는 애플리케이션은 화면에서 사라질 때 그 당시의 상태를 저장했다가, 다시 복귀할 때 저장한 상태를 불러들여 화면에서 사라지기 직전의 상태로 복구해주는 식으로 작성해야합니다.

  14. Blog Icon
    프로그래머

    커니님 요즘 회사서 안드로이드 스터디 진행중인데, 항상 너무 많은 도움이 됩니다.

    커니님 강좌 없었으면 학교 시절 시험은 어떻게 봤을지, 요즘 스터디는 어떻게 진행 했을지 도무지 ㅋㅋㅋㅋ

    너무 감사 드리고, 요번 강의는 소스까지 올려주신걸 보구 감동 먹어서 댓글을 씁니다. 감사합니다 ...

  15. Blog Icon
    EmiLiO

    좋은 내용 무한 감사감사감사 드립니다. 정말 많이 배워갑니다~!!!

    특히 액티비티 생애주기를 한눈에 볼 수 있어 너무 좋습니다^^

    감사드리고 수고하십시오~!ㅎ

  16. Blog Icon
    조세베

    좋은강좌 감샇빈다.

  17. Blog Icon
    황군입니다

    Dialog 호출 상황에서는 키이벤트 재정의 하려면 어떻해야 할까요ㅠ

  18. 어떤 키이벤트를 말씀하시는건가요? OK, Cancel 버튼을 재정의하는것이라면 AlertDialog.Builder 클래스에서 OnClickListener를 지정하면 됩니다.
    하지만 다이얼로그는 액티비티와 별도로 동작하는 것이 아니라 하나의 액티비티에 포함되는 개념이기에 다이얼로그 자체에서 키이벤트를 재정의하는 것은 없습니다. (다이얼로그 테마의 액티비티를 사용하는 경우는 예외)
    즉, 다이얼로그가 표시되었을 때 어떤 키이벤트를 재정의하고 싶다면, 액티비티의 다이얼로그가 표시되는지 여부를 확인한 후 액티비티의 onKeyDown() 메서드 등에서 적절히 처리를 하거나, 아니면 다이얼로그 테마로 표시되는 액티비티를 하나 만들어 그 액티비티의 onKeyDown()에서 처리하면 됩니다.

  19. Blog Icon
    Devel

    정보 좋네요^^ 잘 보고 갑니다.