Handler 동작
- Handler는 Message를 MessageQueue에 보내는 것과 Message를 처리하는 기능을 함께 제공한다.
- post(), postAtTime(), postDelayed() 메서드를 통해서 Runnable 객체도 전달되는데, Runnable도 내부적으로 Message에 포함되는 값이다.
- sendEmptyMessage(), sendEmptyMessageDelaye(), sendEmptyMessageAtTime() 메서드는 Message의 what 값만을 전달한다.
- Delayed()로 끝나는 메서드는 내부적으로 -AtTime()메서드를 호출한다. 현재시간 uptimeMillis에 delayMills를 더한 값이 uptimeMillis 파라미터에 들어간다.
- sendMessageAtFrontOfQueue()나 postAtFrontOfQueue()메서드는 특별한 상황이 아니면 쓰지 말라는 가이드가 있다.
dispatchMessage() 메서드
Looper.loop() 메서드에서 Handler의 dispatchMessage()메서드를 호출한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
public void dispatchMessage(Message msg){ if(msg.callback != null){ handleCallback(msg); } else{ if(mCallback != null){ if(mCallback.handleMessage(msg)){ return; } } handleMessage(msg); } } private static void handleCallback(Message message){ message.callback.run(); }
callback Runnable이 있으면 그것을 실행하고 아니면 handleMessage()를 호출한다.
- dispatchMessage()는 public method여서 snedXxx()나 postXxx()를 쓰지않고 dispatchMessage() 메서드를 직접 호출하기도 하는데, 이때는 MessageQueue를 거치지 않고 직접 Message를 처리한다.
Handler 용도
- Handler는 일반적으로 UI 갱신을 위해 사용된다.
백그라운드 스레드에서 UI 업데이트
- 백그라운드 스레드에서 네트워크나 DB 작업 등을 하는 도중에 UI를 업데이트한다.
- AsyncTask에서도 내부적으로 Handler를 이용해서 onPostExecute()메서드를 실행해서 UI를 업데이트한다.
메인 스레드에서 다음 작업 예약
- UI 작업 중에 UI 갱신 작업을 MessageQueue에 넣어 예약한다.
- 작업 예약이 필요한 경우가 있는데, 예를 들어 Activity의 onCreate() 메서드에서 하지 못하는 일들이 있다. 소프트 키보드를 띄우는 것이나, ListView의 setSelection() 메서드를 호출하는 작업을 onCreat() 메서드에서는 잘 동작하지 않는다. 이때 Handler에 Message를 보내면 현재 작업이 끝나 이후의 다음 타이밍에 Message를 처리한다.
반복 UI 갱신
반복해서 UI를 갱신하는 DigitalClock이나 TextClock 같은 위젯도 Handler를 이용해서 현재 시간을 갱신해서 보여준다.
1 2 3 4 5 6 7 8 9 10 11 12
private Runnalbe updateTimeTask = new Runnable(){ @Override public void run(){ systemInfo.setText(monitorServcie.getSystemInfo()); handler.postDelayed(this, DELAY_TIME); // UI 갱신이 끝나고 postDelay()에 Runnable 자체를 전달해서 계속 반복한다. } }; public void onClickButton(View view){ handler.post(updateTimeTask); }
시간 제한
- 시간을 제한할 때 사용한다.
- 안드로이드 내부에서 ANR을 판단할 때도 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 블루투스 LE 스캔 시간 제한 private static final long SCAN_PERIOD = 10000; private void scanLeDevice(final boolean enable){ if(enable){ mHandler.postDelayed(new Runnable(){ @Override public void run(){ mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); { }, SCAN_PERIOD); mScanning = true; mBlutoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBlutoothAdapter.stopLeScan(mLeScanCallback); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 백 키를 두 번 이상 연속해서 누를 때만 액티비티 종료 private boolean isBackPressedOnce = false; @Override public void onBackPressed(){ if(isBackPressedOnce){ super.onBackPressed(); } else { Toast.makeText(this, R.string.backpressed_message, Toast.LENGTH_SHORT).show(); isBackPressedOnce = true; timerHandler.postDelayed(timerTask, 5000); } } private finak Runnable timerTask = new Runnable(){ @Override public void run(){ isBackPressedOnce = false; } };
안드로이드 프레임워크 내부에서 쓰이는 Handler
- 안드로이드 프레임워크에서도 내부적으로 Handler를 많이 사용한다. 메인 스레드에서 실행해야 하는 작업들이, Handler를 사용해서 메인 Looper의 MessageQueue를 거쳐서 순차적으로 실행된다.
- ActivityThread의 내부 클래스인 H는 Handler를 상속한다. 컴포넌트 생명주기 Message는 모두 H를 거친다. Message 의 what 변수에 들어가는 int 상수에는 LAUNCH_ACTIVITY, RESUME_ACTIVITY, PAUSE_ACTIVITY, DESTROY_ACTIVITY, CREATE_SERVICE, STOP_SERVICE, RECEIVER, BIND_APPLICATION, EXIT_APPLICATION등이 있다. ActivityThread.class
- ViewRootImpl 클래스에서 Handler를 이용해서 터치(touch)나 그리기(invalidate)등 이벤트를 처리한다.(MSG_INVALIDATE, MSG_RESIZED, MSG_DISPATCH_INPUT_EVENT, MSG_CHECK_FOCUS, MESSAGE_DISPACH_DRAG_EVENT 등). ViewRootImpl은 ICS까지는 Handler를 직접 상속했지만, 젤리빈부터는 내부 클래스인 ViewRootHandler가 Handler를 상속한다. ViewRootImpl에서 처음 그릴 때나 다시 그릴 때(invalidate()호출, 레이아웃 변경, Visibility 변경 등) 화면에 그리라는 Message를 Choreographer에 위임하는데, Choreographer에서도 내부적으로 Handler를 상속한 FrameHandler를 사용한다.
- Acvitiy는 멤버 변수에 Handler가 있고 runOnUiThread() 메서도에서만 사용된다.
- View에는 ViewRootImpl에서 전달된 ViewRootHandler를 post() 와 postDelayed() 메서드에서 사용한다.
- Activity는 멤버 변수에 Handleer가 있고 runOnUiThread() 메서드에서만 사용된다.
- View에는 ViewRootImpl에서 전달된 ViewRootHandler를 post()와 postDelayed()메서드에서 사용한다.
Handler의 타이밍 이슈
- 원하는 동작 시점과 실제 동작 시점에서 차이가 생기는데, 이런 타이밍 이슈는 메인 스레드와 Handler를 사용해서 해결할 수 있다.
- Activity의 onCreate() 메서드에서 Handler의 post() 메서드를 실행하면 Runnable이 실행되는 시점은 메인 스레드에서는 한 번에 하나의 작업밖에 하지 못하고, 여러 작업이 서로 엉키지 않기 위해서 메인 Looper의 MessageQueue에서 Message를 하나씩 꺼내서 처리 하며 MessageQueue에서 Message를 하나 꺼내오면 onCreate()에서 onResume()까지 쭉 실행되므로 onResume()이후에 실행이된다.
지연 Message는 처리 시점을 보장할 수 없음
- Handler에서 -Delayed()나 -AtTime() 메서드에 전달된 지연 Message는 지연시간(delay time)을 정확하게 보장하지는 않는다. MessageQueue에서 먼저 꺼낸 Message 처리가 오래 걸린다면 실행이 느려진다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Handler handler = new Handler(); handler.postDelayed(new Runnalbe(){ @Override public void run(){ Log.d(TAG, "200 delay"); } }, 200); handler.post(new Runnable(){ @Override public void run(){ Log.d(TAG, "just"); SystemClock.sleep(500); } });
- 첫 번째 handler에서 Message를 보내지만 200ms 이후에 작업되는 2번째 handler에서 Message는 즉시 실행되지만 sleep시간을 포함해서 500ms가 거리는 작업이다. 단일 스레드의 규칙 때문에 앞의 작업을 다 끝내야만, 뒤의 것을 처리할 수 있다. 따라서 ‘200 delay’라는 로그는 200ms가 아닌 최소 500ms 이후에 남게 된다.