티스토리 뷰

IT

android Handler 사용 시 유의사항

Dante2k™ 2018.04.05 13:27

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 처리를 하지 않도록 하는 방법에 대해서 알아보았습니다.


여담으로 다이얼로그에서도 종종 비동기 처리를 하는 경우가 있는데, 이런 경우에는 액티비티보다는 다이얼로그의 자원을 확인하는 코드를 넣어야 합니다.

댓글
댓글쓰기 폼