그래서, 리스너 패턴에 사용되던 자바의 인터페이스를 코틀린의 function로 변환하는 과정을 정리해보려 한다.
Step -> { Interface to function }
1. 변환할 Interface 의 구조 알아보기
interface PermissionRequestCallback {
fun onPermissionResult(resultCode: Int, list: ArrayList<String>)
}
Convert java into kotlin 변환기 사용해서 생성한 인터페이스이다. 하나의 메소드를 가지고 있으며 resultCode: Int, list: ArrayList<String> 의 두개의 파라미터를 가지고 있다.
2. 파라미터 수정
기존에 checkPermission은 fun checkPermission(list: ArrayList<String>, callback: PermissionRequestCallback? = null): Boolean 이런 구조였으나 인터페이스를 Function으로 교체하기 위해 callback: (Int, ArrayList<String>) -> Unit 로 교체해주었다. 두 개의 파라미터를 가지면 순서대로 해당 변수의 타입명을 적어주고, 리턴값은 없어도 되니 Unit를 사용한다. 기본값은 설정하지 않았다. (보통, 권한 요청하고 받으면 바로 작업을 실행하게 만들기 때문이다.)
기본값을 추가시키게 되면 형태는 callback: (Int, ArrayList<String>) -> Unit = {} 가 된다.
만일 파라미터가 하나일 경우 callback: Int.() -> Unit 라고 해도 무방하다.
최종적으로 수정된 checkPermission은 아래와 같다.
/**
* check and request Permission which given.
*
* @param[array] array of Permission to check
* @param[callback] callback object
* @return check result
*/
fun checkPermission(array: Array<String>, callback: (Int, ArrayList<String>) -> Unit): Boolean {
val permissionList: ArrayList<String> = ArrayList()
array.forEach { permissionList.add(it) }
return checkPermission(permissionList, callback)
}
기타 checkPermission, requestPermission 등도 위와 같이 수정했다.
3. callback 파라미터 사용
사용할때는 비교적 간단하다. 기존에는 callback?.onPermissionResult(PERMISSION_ALREADY, list) 이런 식으로 사용했었는데, callback(PERMISSION_ALREADY, list) 로 바꿔주면 된다.
4. 실제로 Kotlin에 사용해보자.
Before
class Callback : RPermission.PermissionRequestCallback {
override fun onPermissionResult(resultCode: Int, list: ArrayList<String>) {
alert(message = "Permission result -> $resultCode / Requested Permission: ${TextUtils.join(",", list)}")
}
}
btnCall.setOnClickListener {
val arrays : Array<String> = arrayOf(Manifest.permission.CALL_PHONE)
RPermission.getInstance(this).checkPermission(arrays, Callback())
}
기본적으로 안드로이드 앱을 종료하는 법은 finish()이다. 구글에서 가장 권장하는 방법이기도 하고. 하지만 스택 관리가 잘 안되거나 다른 버그가 발생하면 분명히 finish() 해도 다른 액티비티가 “뿅!” 하고 나오는 일이 잦다.
물론 finish 외에도 android.os.Process.killProcess(android.os.Process.myPid()) 나 System.exit(0) 등이 있기야 하지만 다소 안드로이드에 안맞긴 한다. 결과적으로 위 두개 다 다른 액티비티가 나오는 등의 버그는 일어난다.
그래서 API 16부터 추가된게 Activity.finishAffinity() 이다. 어느 액티비티에서나 상위 스택에 쌓여진 액티비티를 종료할 수 있다. API 16보다 아래의 버전을 지원하는 경우 ActivityCompat.finishAffinity() 를 사용하면 된다.
finishAffinity에는 또 다른 사용법이 있는데 바로 앱을 재부팅 하는 것이다. 서비스등만 따로 종료하면 문제 없이 작동한다. 아래는 개발중인 RichUtils 라이브러리에 있는 코틀린 버전의 재부팅 메소드이다. (원본 코드: RichUtilsKt/RReboot.kt) CLEAR_TOP 로 홈 액티비티나 지정한 액티비티로 이동하고 finishAffinity()로 부모 액티비티를 날려버리는 구조이다.
API 21부터 추가된 Activity.finishAndRemoveTask() 는 종료함과 동시에 테스크 바(최근 내역)에서 지운다. 검색해보니 크로니움 코드의 ApiCompatibilityUtils 에 호환 코드 비슷한게 있다.
/**
* @see android.app.Activity#finishAndRemoveTask()
*/
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 가 그나마 안드로이드 환경에 맞게 ‘사실상’으로 앱을 종료하는 것 같다. 서비스는 따로 종료시켜야 되면서도 이제까지는 잘 되었기 때문이다. 앞으로 더 나은 메소드가 나와 한방에 정리되었으면 하는 바람이 있다.