Unable to add window -- token android.os.BinderProxy@3d88be0 is not valid; is your activity running?
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@3d88be0 is not valid; is your activity running?
등의 UI 처리를 시도할 때 발생하는 오류입니다. 설명대로 너의 Activity 가 이미 중지가 되었는데, 중지된 Activity 에서 View 에 변동사항을 발생키는 경우 오류가 발생합니다.
처음에는 참 당황스럽고, 멀티쓰레드 환경이라면 오류 메시지도 Looper, Handler 를 통하여 처리되므로 특정 위치를 찾기도 쉽지 않습니다.
하여 애초에 개발시 Handler, WorkerThread 에서 UIThread 로 행위가 이전되면서 Activity 의 상태를 확인하지 않고 UI 를 조작하기 때문입니다.
원인을 파악했으니 처리방법도 있겠지요?
처리방법은 간단합니다.
무언가 UI 조작을 하기전에 Activity 의 메소드인 isFinishing() 를 호출하여 Activity 의 상태를 확인해보면 됩니다.
예를 들어 AsyncTask, Handler 조합을 안드로이드에서는 많이 사용하는데, AsyncTask 의 경우는 작업을 WorkerThread(백그라운드 쓰레드) 에서 작업을 진행하고, 이후 onPostExecute() 를 통하여 처리된 결과를 Handler 를 통하여 전달하는 방법을 많이 사용하죠. Handler 에는 Looper 를 설정하여 전달되는 메시지를 UI 쓰레드에서 받을 수 있습니다.
위 오류는 대부분 처리된 결과를 ListView 에 출력, TextView 에 문자열 표시, ProgressBar, ProgressDialog, Dialog, Toast 등 UI 적인 작업을 진행할 때 발생하죠.
물론 Handler 등을 통하여 비동기적으로 전달되므로 StackTrace 를 봐도 위치를 특정하기가 쉽지 않습니다.
소스 하나를 보겠습니다. 개인 프로젝트에서 인쇄를 요청하는 부분이 하드웨어와 블루투스 통신을 하기 때문에 Handler 를 통하여 인쇄 결과를 전달받습니다.
그리고 UI 에 잘 처리되었다 내지는 오류가 발생했다라는 메시지를 출력합니다. Handler 로 전달되기전에 Activity 를 종료하면, 종료된 이후에 Dialog, Toast 등의 메시지를 띄우는 순간 위의 에러가 발생하죠.
new PrintTask(this, DATAS, new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (isFinishing()) return; switch (msg.what) { case PrinterListener.Error.ID_SUCCESS: showToast(R.string.printer_msg_success, false); break; case PrinterListener.Error.ID_USER_INTERRUPT: showDialogCommon(R.string.printer_msg_interrupted_by_user); break; default: // 재인쇄 확인 다이얼로그 showDialog(DlgFrgCommon.NewInstanceYesOrNo(getString(R.string.dialog_title_print), getString(R.string.printer_msg_error_retry, msg.what, msg.obj), getString(R.string.button_yes), getString(R.string.button_no), DATAS), DIALOG_TAG_RETRY_PRINT); break; } } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
소스에서 PrintTask 는 AsyncTask 를 사용하여 구현한 인쇄 Task 입니다. 백그라운드로 실행됩니다. 결과는 뒤의 new Handler(Looper.getMainLooper()) 를 통하여 전달됩니다.
public void handleMessage(Message msg) 의 내부 코드에서 첫번째 줄에 보이는 부분을 추가하면 됩니다.
if (isFinishing()) return; // 액티비티가 이미 종료된 경우라면, 무슨 짓을 하더라도 사용자는 해당 메시지를 볼 수 있는 방법이 없습니다.
이상으로 Activity 가 종료된 시점에서 UI 처리를 하지 않도록 하는 방법에 대해서 알아보았습니다.
여담으로 다이얼로그에서도 종종 비동기 처리를 하는 경우가 있는데, 이런 경우에는 액티비티보다는 다이얼로그의 자원을 확인하는 코드를 넣어야 합니다.