본문 바로가기

Programming/Android

안드로이드 어플리케이션 종료하기

How to quit an application in Android



 안드로이드는 멀티태스킹 그리고 어플리케이션 생명주기에 대하여 조금 독특한 철학을 갖고 있습니다. 덕분에, 기존 윈도우나 다른 OS 에서 어플리케이션을 만들었던 개발자들이 안드로이드를 시작할 때, 굉장히 간단해 보이는 일 때문에 고생을 하곤 하는데요. 무엇보다도 '어플리케이션 종료' 에 대하여, 도대체 어떻게 하면 어플리케이션을 종료 시킬 수 있는지 헤매는 경우가 많습니다. (저도 관련해서 여러가지 고생을 했습니다...ㅠㅠ) 난감한 '안드로이 어플리케이션 종료' 에 대하여, 그 동안 삽질한 내용을 기반으로 간단히 5 단계로 정리해 보았습니다.


1. 안드로이드 사전에 '어플리케이션 종료' 는 없습니다.

 안드로이드 어플리케이션 생명주기에 관해서 가장 먼저 기억할 점이 있습니다. '기본적'으로 안드로이드에는 '어플리케이션 종료' 라는 개념 자체가 없습니다. 안드로이드의 기본정책은 다음과 같습니다.

"한 번 실행된 프로세스는 메모리가 허락하는 한 영원히 죽지 않습니다." 

 안드로이드 개발팀은 모바일 디바이스 사용자들이 동영상을 보던 중에, 문자 메시지를 확인하고, 전화를 걸다가, 다시 동영상을 시청하는 등으로, 짧은 시간 여러 어플리케이션을 번갈아 가며 사용한다는 점에 주목하고, 최대한 어플리케이션 간의 전환이 빠르고, 사용자가 어플리케이션을 종료한다는 개념 자체를 생각하지 않아도 되도록 프레임워크를 디자인했습니다. 

 

 단, 메모리가 부족한 경우 안드로이드 플랫폼은 우선 순위가 낮은 어플리케이션 프로세스를 강제로 종료하여 메모리를 확보고, 이 경우에도 어플리케이션의 마지막 상태 정보를 저장하고 필요한 메모리가 확보되면 이전 상태로 어플리케이션을 복원하는 동작을 수행합니다. 바로 이런 특성 때문에, 강제로 프로세스를 종료해도 어플리케이션이 재시작되는 현상이 일어납니다. 이 주제에 관하여, 개발자 블로그에 올라온 'Multitasking the Android Way' 라는 제목의 포스트를 한 번 읽어보시면 큰 도움이 될 것 같습니다. (번역 되었습니다.)


2. 어플리케이션 종료에는 두 가지 의미가 있습니다.

 네. '기본적' 으로 안드로이드 어플리케이션은 종료되지 않습니다. 하지만, 예외없는 규칙은 없는 법. 안드로이드에서도 어플리케이션은 종료될 수 있습니다. 그리고, 어플리케이션 종료는 사실 다음 두 가지의 서로 다른 의미를 뜻할 수 있습니다.

  • 유지하고 있던 어플리케이션 상태 정보를 모두 초기화 합니다.
  • 어플리케이션 프로세스를 종료합니다.

 안드로이드 어플리케이션은 대게 여러 개의 '엑티비티' 와 '서비스' 로 구성됩니다. 엑티비티는 한 화면을 표현하는 기본 단위로, 어플리케이션 진행에 따라 'TASK - 테스크' 라고 불리우는 스택에 하나 씩 차곡 차곡 쌓이게 됩니다. 그리고 이 엑티비티 스택 정보와 해당 어플리케이션에서 동작하고 있는 서비스는 어플리케이션 프로세스에 저장되는 것이 아니라, 안드로이드 시스템 서비스 내부에 간직됩니다.



<실행중인 어플리케이션 정보는 System Service 프로세스 상에서도 관리됩니다.>


 따라서, 안드로이드 어플리케이션을 종료하는 첫 번째 방법은 바로 시스템 서비스단에 저장된 어플리케이션 상태 정보를 모두 초기화하는 것 입니다. 상태 정보가 모두 초기화되면, 백그라운드로 돌아간 어플리케이션이 재시작할 때, 기존 상태가 복원하는 것이 아니라, 최초로 어플리케이션이 실행된 것 처럼 메인 엑티비티가 실행될 것 입니다. 두 번째 방법은 바로 어플리케이션 프로세스를 종료하는 것 입니다. 프로세스만 종료하면 된다니, 얼핏 생각하면 너무나 간단한 방법이지만, 사실 이 작업이 생각보다 쉽지는 않습니다. 차근 차근 살펴보도록 하겠습니다.


3. 어플리케이션 상태를 초기화하는 방법을 소개 합니다.

 안드로이드는 어플리케이션은 다음과 같은 방식으로 상태 정보를 초기화 할 수 있습니다.


  • 사용하는 서비스는 종료 (stopService) 하고 바인딩을 해체(unbindService) 합니다. 
  • 등록한 리시버 (registerReceiver) 는 등록을 해지(unRegisterReceiver) 합니다.
  • 사용중인 락(예를 들어 WifiLock) 은 모두 반환(release) 합니다.
  • 엑티비티 테스크를 청소합니다.

 

 네. 아쉽게도 특별한 비결은 없습니다. 서비스나 리시버, 락들은 일종의 리소스라고 생각하실 수 있습니다. 사용했던 리소스를 하나 하나 착실히 반환하는 것이 가장 기본이겠지요. 단, 엑티비티 테스크를 비우는 데는 약간의 꽁수가 필요합니다. 기본적으로 안드로이드 시스템은 현재 사용중이 아닌 엑티비티 테스크를 일정 시간 유지합니다. 테스크가 유지되는 동안 사용자가 해당 어플리케이션을 재시작하면 (홈 화면에서 아이콘을 클릭하여) 메인 엑티비티가 실행되는 대신, 테스크가 복원되어 테스크 최상단에 있던 엑티비티가 화면에 표시됩니다. 이를 초기화 하는 데는 다음 두 가지 방법이 사용될 수 있습니다.


  • 메니페스트 상에 메인 엑티비티 속성을 "android:clearTaskOnLaunch"값을 "true로 설정합니다. 어플리케이션을 종료하고자 할 때, 홈 어플리케이션 엑티비티를 시작하는 방법으로, 어플리케이션을 백그라운드로 전환합니다. 사용자가 어플리케이션을 다시 실행하면, 기존의 테스크 정보가 모두 초기화되고, 항상 메인 엑티비티를 실행합니다. 단, 사용자가 홈키를 누르는 경우에는 어플리케이션 엑티비티 테스크를 유지하고자 한다면, 이 방법을 사용할 수 없습니다. (엑티비티 테스크가 항상 초기화 됨으로)
  • FLAG_ACTIVITY_CLEAR_TOP 플래그를 활용하여, 어플리케이션 메인 엑티비티(루트 엑티비티)를 실행 합니다. 해당 플래그를 사용하면, 이때, 특별한 추가 정보를 포함하여, 실행된 엑티비티가 자기 자신을 바로 종료하도록 구현합니다. 단, 이 경우, 어플리케이션 흐름 상, 메인 엑티비티가 중복해서 실행되지 않도록 주의할 필요가 있습니다. (메인 엑티비티는 루트 엑티비티 역할을 수행함으로 어플리케이션 내에서 메인 엑티비티를 시작할 때는 항상 FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP 플래그를 사용하시는 습관을 갖는 것이 좋습니다.)


 코드 살펴 볼까요? 우산 첫 번째 방법의 메니페스트 파일은 다음과 같을 것 입니다.

        <activity android:name=".Main"

                  android:label="@string/app_name"

                  android:clearTaskOnLaunch="true">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

 현재 실행 중인 어플리케이션은 아래와 같이 홈 어플리케이션을 실행 시키는 방법으로 백그라운드로 전환 시킬 수 있습니다.

Intent launchHome = new Intent(Intent.ACTION_MAIN);

launchHome.addCategory(Intent.CATEGORY_DEFAULT);

launchHome.addCategory(Intent.CATEGORY_HOME);

startActivity(launchHome);

 다음으로 두 번째 방법을 어떤식으로 구현할 수 있는지 살펴보겠습니다. 우선, 메인 엑티비티의 onNewIntent 메서드를 오버라이드 합니다.
    @Override
    protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    boolean bFinish = intent.getBooleanExtra("FinishSelf", false);
    if(bFinish)
    finish();
    }
 그 후, 어플리케이션을 종료시키는 시점에 (메인 엑티비티가 아니여도 됩니다.) 다음과 같이 CLEAR_TOP 플래그를 활용하고, 미리 정의한 어플리케이션 종료 코드를 EXTRA 로 덧 붙여, 메인 엑티비티를 호출합니다. onResume() 이 호출되기도 전에, finish() 가 호출 되기 때문에, UI 상의 이상한 깜박임 같은 현상 없이 자연스럽게 어플리케이션 테스크가 비워집니다.
Intent clearTop = new Intent(Activity2.this, Activity1.class);
clearTop.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
clearTop.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
clearTop.putExtra("FinishSelf", true);
startActivity(clearTop);
4. Process 종료는 정상적인 해결책은 아닙니다.

 지금까지 안드로이드에서 기본적으로 제공해주는 방법을 통해 어플리케이션 상태를 초기화 하는 방법에 대하여 살펴보았습니다. 하지만, 한번 시작된 안드로이드 어플리케이션 프로세스는 메모리가 허락하는한 종료되지 않고 계속 살아있습니다. 이 녀석 어떻게 죽일 수 있을까요? 일단, 왜 꼭 프로세스를 종료시켜야 하는지 스스로에게 질문을 던져 봅니다. 어플리케이션이 사용중인 리소스를 정상적으로 잘 정리하였다면, 여러분의 어플리케이션 프로세스는 별다른 문제없이 그저 메모리상에 상주할 뿐 특별한 작업을 수행하진 않을 것 입니다. 더군다나, 사용자가 여러분의 어플리케이션을 재시작하면 어플리케이션이 번개같이 시작될 것이고, 메모리가 부족하면 시스템이 알아서 여러분의 어플리케이션을 프로세스를 종료시켜 줄 것 입니다.


 특히 주의할 점이 있습니다. System.exit(0) 이나 Process.killProcess(Process.myPid()) 등의 방식으로 어플리케이션 프로세스를 강제로 종료시키는 것은 결코 좋은 방법이 아닙니다. 여러번 말씀드리지만, 2번 항목의 그림처럼, 안드로이드 어플리케이션의 생명주기와 프로세스의 생명주기는 일치하지 않고, 안드로이드 어플리케이션의 상태정보는 해당 어플리케이션 프로세스 외에 다양한 별도의 시스템 서비스들에 의하여 관리되고 있습니다. (엑티비티 테스크라던가 서비스 정보등등) 만일, 위의 방법을 이용하여 프로세스를 강제로 종료하면, 안드로이드 시스템은 마치 시스템 메모리가 부족하여 해당 프로세스가 종료된 것으로 판단하고, 현재 가용한 시스템 메모리를 확인 한 후, 저장된 인텐트 정보를 활용하여, 해당 어플리케이션을 되살릴 것 입니다. (어플리케이션 종료에 관한 가장 흔한 실수 중에 하나 입니다.)


5. Process 종료는 최후의 해결책입니다.

 네. 그럼에도 피치못할 사정으로 프로세스를 종료시켜야 하는 순간이 있습니다. 두 가지 이유가 있을 수 있겠네요.

  • 플레쉬 메모리 특성상 여분 메모리가 적으면, 시스템 전체 성능이 저하될 수 있습니다. 따라서, 어플리케이션이 사용하는 메모리가 많고, 어플리케이션 전환이 자주 일어나지 않는 게임 같은 종류의 어플리케인의 경우에는 선제적인 방법으로 프로세스를 종료시키는 편이 좋습니다.
  • 현재 어플리케이션이, 프로세스와 라이프사이클이 일치하는 static 변수들과 무한 루프를 도는 thread 등을 너무 광범위하게 사용하고 있는 경우 (외부 라이브러리 사용등의 이유로) 해당 리소스들을 깔끔하게 종료시키기 위하여 프로세스 종료를 고려해볼 수 있습니다.

 프로세스를 종료하는 방법은 다음과 같습니다. 우선 프로세스를 종료할 때는, 안드로이드에서 제공하는 ActivityManager.killBackgroundProcess() 메서드를 사용합니다. 이 메서드를 사용하기 위해서 어플리케이션은 KILL_BACKGROUND_PROCESSES 권한을 갖고 있어야 합니다.

<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>


ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

am.restartPackage(getPackageName());

 단, 이 메서드는 안드로이드 프로요 이전 버전에서 지원하던 restartPackage 메서드 처럼, 만병 통치약은 아닙니다. 메서드 이름이 암시하는 것 처럼, 오직 어플리케이션이 백그라운드 이하의 중요도를 갖고 있어야지만 정상적으로 동작하지요. 어플리케이션 프로세스의 권한은 다음과 같이 나누어집니다.


  • IMPORTANCE_EMPTY : 말그대로 빈 깡통. 우선순위가 가장 낮은 프로세스.
  • IMPORTANCE_BACKGROUND : 동작 중인 어플리케이션 컴포넌트가 없는 프로세스. 
  • IMPORTANCE_SERVICE : 현재 Service 가 동작하고 있는 프로세스.
  • IMPORTANCE_VISIBLE : 현재 화면상에 보이는 Activity 가 동작하는 프로세스.
  • IMPORTANCE_FOREGROUND : 현재 사용자 U/X 와 직접 연관된 기능을 수행하고 있는 어플리케이션 컴포넌트를 실행 중인 프로세스. 가장 높은 우선 순위를 갖는다.


 따라서, 위 메서드를 활용하기 위해서는 현재 실행중인 Foreground 서비스를 정지하고, 현재 테스크를 백그라운드로 돌리는 작업이 선행되어야 합니다. 관련된 방법은 3번 항목에서 한 번 설명드렸으니, 여기서는 엑티비티 매니저가 제공하는 테스크 정보를 기반으로 최대한 자동으로 어플리케이션 프로세스를 종료하는 코드를 소개해보겠습니다.

final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

//stop running service inside current process.
List<RunningServiceInfo> serviceList = am.getRunningServices(100);
for(RunningServiceInfo service : serviceList){
    if( service.pid == Process.myPid()){
        Intent stop = new Intent();
        stop.setComponent(service.service);
        stopService(stop);
    }
}

//move current task to background.
Intent launchHome = new Intent(Intent.ACTION_MAIN);
launchHome.addCategory(Intent.CATEGORY_DEFAULT); launchHome.addCategory(Intent.CATEGORY_HOME);
startActivity(launchHome);

//post delay runnable(waiting for home application launching)
new Handler().postDelayed(new Runnable(){
    @Override public void run() {
        am.killBackgroundProcesses(getPackageName());
    }
}, 3000);

 코드를 간단히 설명해 볼까요? 기본 원칙은 현재 어플리케이션 프로세스를 백그라운드로 옮긴 후에, 올바른 메서드를 호출하는 것 입니다. 이를 위하여, 현재 동작하는 서비스 중, 어플리케이션 프로세스와 연결된 서비스를 검색하여 강제로 종료시킨 후, 홈 어플리케이션을 시작하여, 현재 어플리케이션 테스크를 백그라운드로 강제 이동시켰습니다. 그리고, 홈 어플리케이션이 정상적으로 시작하기 충분할거라 판단되는 시간 (위 코드에서는 3초)을 기다린 후에, killBackgroundProcesses 메서드를 호출하였습니다.  (물론, 어플리케이션 테스크를 내리기 위하여, 앞에서 설명드린 CLEAR_TOP 플래그를 활용하는 방법도 잘 동작합니다.)


결론

 안드로이드 어플리케이션의 원칙은 간단합니다. "개발자 여러분! 어플리케이션 종료에 신경 끄세요!!!" 라고 할까요? 그런만큼 안드로이드에서 어플리케이션을 종료하는 일은 생각만큼 간단한 일이 아닙니다. 그럼에도... 우리 개발자들은 언제나 예외가 필요한 법이지요. 하지만, 어플리케이션 종료 코드를 작성하기 전에, 스스로 한번쯤 정말 이런 기능이 필요한지, 그리고 어플리케이션 종료가 정확히 어떤 의미이고, 올바른 사전 작업을 수행했는지 확인할 필요가 있습니다. 그런 순간, 이 포스팅이 도움이 되었으면 좋겠습니다. 그리고, 본 포스팅에서 소개한 방법 외에 어플리케이션을 종료하는 좋은 방법을 아는 분들은 꼭 글 남겨주시면 정말 감사하겠습니다^^

[출처] 안드로이드 어플리케이션 종료하기 (http://huewu.blog.me/110120532880)|작성자 휴우