Android - Exit Applicaction with finishAffinity

WindSekirun (wind.seo)·2022년 4월 26일
0
post-custom-banner

이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.

작성 시점: 2017-06-21

기본적으로 안드로이드 앱을 종료하는 법은 finish()이다. 구글에서 가장 권장하는 방법이기도 하고. 하지만 스택 관리가 잘 안되거나 다른 버그가 발생하면 분명히 finish() 해도 다른 액티비티가 "뿅!" 하고 나오는 일이 잦다.

물론 finish 외에도 android.os.Process.killProcess(android.os.Process.myPid()) 나 System.exit(0) 등이 있기야 하지만 다소 안드로이드에 안맞긴 한다. 결과적으로 위 두개 다 다른 액티비티가 나오는 등의 버그는 일어난다.

Solutions - finishAffinity()

그래서 API 16부터 추가된게 Activity.finishAffinity() 이다. 어느 액티비티에서나 상위 스택에 쌓여진 액티비티를 종료할 수 있다.  API 16보다 아래의 버전을 지원하는 경우 ActivityCompat.finishAffinity() 를 사용하면 된다.

finishAffinity에는 또 다른 사용법이 있는데 바로 앱을 재부팅 하는 것이다. 서비스등만 따로 종료하면 문제 없이 작동한다. 아래는 개발중인 RichUtils 라이브러리에 있는 코틀린 버전의 재부팅 메소드이다. (원본 코드: RichUtilsKt/RReboot.kt) CLEAR_TOP 로 홈 액티비티나 지정한 액티비티로 이동하고 finishAffinity()로 부모 액티비티를 날려버리는 구조이다.

@file:JvmName("Utils")
@file:JvmMultifileClass

package pyxis.uzuki.live.richutilskt

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Build

@JvmOverloads 
fun Context.reboot(restartIntent: Intent = this.packageManager.getLaunchIntentForPackage(this.packageName)) {
    restartIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
    if (this is Activity) {
        this.startActivity(restartIntent)
        finishAffinity(this)
    } else {
        restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        this.startActivity(restartIntent)
    }
}

private fun finishAffinity(activity: Activity) {
    activity.setResult(Activity.RESULT_CANCELED)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        activity.finishAffinity()
    } else if (Build.VERSION.SDK_INT >= Build.VERSION\_CODES.JELLY_BEAN) {
        activity.runOnUiThread { activity.finishAffinity() }
    }
}

다만 이 finishAffinity 또한 앱 종료를 위한 최적의 방법은 아니다. 안드로이드엔 앱 종료라는 개념이 존재하지 않기 때문이다. 생명주기만 존재할 뿐이다. 단지 모든 부모 액티비티를 종료하기에 안드로이드 VM이 메모리에서 제거하는 뿐이다.

또, 서비스는 따로 종료해야 하기에 정확히는 종료한다고 보긴 어렵다.

Solution - finishAndRemoveTask()

API 21부터 추가된 Activity.finishAndRemoveTask() 는 종료함과 동시에 테스크 바(최근 내역)에서 지운다. 검색해보니 크로니움 코드의 ApiCompatibilityUtils 에 호환 코드 비슷한게 있다.


public static void finishAndRemoveTask(Activity activity) {
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
        activity.finishAndRemoveTask();
    } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
        // crbug.com/395772 : Fallback for Activity.finishAndRemoveTask() failing.
        new FinishAndRemoveTaskWithRetry(activity).run();
    } else {
        activity.finish();
    }
}

private static class FinishAndRemoveTaskWithRetry implements Runnable {
    private static final long RETRY_DELAY_MS = 500;
    private static final long MAX_TRY_COUNT = 3;
    private final Activity mActivity;
    private int mTryCount;
    FinishAndRemoveTaskWithRetry(Activity activity) {
        mActivity = activity;
    }
    @Override
    public void run() {
        mActivity.finishAndRemoveTask();
        mTryCount++;
        if (!mActivity.isFinishing()) {
            if (mTryCount < MAX_TRY_COUNT) {
                ThreadUtils.postOnUiThreadDelayed(this, RETRY_DELAY_MS);
            } else {
                mActivity.finish();
            }
        }
    }
}

다만 이거도 문제가 있는게, 다시 호출하면 어째선지 Application의 onCreate()가 불리지 않아 사용을 포기해야만 했었다.

고전 방법

그닥 신경쓰지 않아도 되는 코드면 아래와 같이 해도 무방하긴 하다.

System.runFinalizersOnExit(true);
System.exit(0);

So...

사실 안드로이드 앱 개발자에게 '어떻게 앱을 종료시킬 것인가' 는 오래전부터 연구된 문제긴 하다. killProcess, exit, runFinalizersOnExit 등... 안드로이드의 컨셉 자체가 위에서도 말했듯이 앱 종료라는 개념이 없다. 이 때 개발자가 할 수 있는건 좀 더 빨리 heap memory 에서 사라지게 하는 것 뿐인거 같다.

물론, finish() 로도 종료할 수 있게 앱의 액티비티 스택을 잘 관리하는게 Best지만..

어느정도 시행착오 해본 결과 finishAffinity 가 그나마 안드로이드 환경에 맞게 '사실상'으로 앱을 종료하는 것 같다. 서비스는 따로 종료시켜야 되면서도 이제까지는 잘 되었기 때문이다. 앞으로 더 나은 메소드가 나와 한방에 정리되었으면 하는 바람이 있다.

profile
Android Developer @kakaobank
post-custom-banner

0개의 댓글