English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Handler
모든 Android 개발 초보자는 Handler라는 "벽"을 피할 수 없습니다. 왜 이렇게 말하는지요? 먼저 이것은 Android 아키텍처의 핵심 중 하나입니다. 또한 많은 사람들이 그 원인을 알지 못하면서도 그 결과를 알고 있습니다. Handler.post라는 메서드를 보고 소스 코드를 살펴보기로 결심했습니다. Handler의 구현 방식을 정리해 보기로 했습니다.
비동기 UI 업데이트
먼저 필수적인 기억할 규칙을 먼저 말씀드리겠습니다. "메인 스레드에서 시간이 걸리는 작업을 하지 마세요, 서브 스레드에서 UI를 업데이트하지 마세요" 이 규칙은 초보자에게 반드시 알려져야 할 것입니다. 그런 규칙에서 문제를 어떻게 해결해야 할까요? 이때 Handler가 우리 앞에 나타납니다(AsyncTask도 가능하지만, 본질적으로 Handler를 포장한 것입니다). 전통적인 일반적인 코드를 한 번 보겠습니다(메모리 누수 문제는 나중에 다룹니다):
Activity에서 새로운 handler를 생성해야 합니다:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: mTestTV.setText("This is handleMessage");//UI 업데이트 break; } } };
그런 다음 서브스레드에서 메시지를 전달합니다:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//서브스레드에서 시간이 걸리는 작업이 있습니다, 예를 들어 네트워크 요청 mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
서브스레드에서의 시간이 걸리는 작업이 완료되면 메인스레드에서 비동기로 UI를 업데이트하는 것을 완료했습니다. 그러나 제목의 post를 사용하지 않았습니다. 그래서 post 버전을 다시 보겠습니다:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//서브스레드에서 시간이 걸리는 작업이 있습니다, 예를 들어 네트워크 요청 Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { mTestTV.setText("This is post");//UI 업데이트 } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
표면적으로는 post 메서드에 Runnable을 전달하여 서브스레드를 시작하는 것처럼 보이지만, 서브스레드에서 UI를 업데이트할 수 없습니다. 그렇다면 문제는 무엇일까요? 이 의문을 가지고 Handler의 소스코드를 살펴보겠습니다:
먼저 일반적인 sendEmptyMessage는 어떤지 보겠습니다:
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
우리가 전달한 매개변수를 메시지로 래핑한 후 sendMessageDelayed를 호출합니다:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
sendMessageAtTime를 다시 호출합니다:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + "sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
좋습니다. 또 다른 post()를 다시 보겠습니다:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);//getPostMessage 메서드는 두 가지 메시지 전송 방법의 차이점 }
메서드는 단 하나의 문장으로 구성되어 있으며, 일반적인 sendMessage와 동일한 내부 구현을 가지고 있지만, 다른 점이 하나 있습니다.那就是 getPostMessage(r) 이 메서드:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
이 메서드에서 우리는 입력된 매개변수를 메시지로 둘러싸지만, 이번에는 m.callback = r, 이전에는 msg.what=what로 이해했습니다. Message의 이러한 속성은 보지 않았습니다
Android 메시지 메커니즘
이곳에서 우리는 post와 sendMessage의 원리가 Message를 둘러싸는 것이라는 것을 알지만, Handler의 전체 메커니즘은 무엇인지 명확하지 않습니다. 더 탐구해 보겠습니다.
최근 두 가지 메서드가 모두 sendMessageAtTime를 호출하는 것을 본적이 있습니다
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + "sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
이 메서드는 enqueueMessage를 다시 호출합니다. 이름에서 알 수 있듯이, 메시지를 퀼러에 추가하는 것 같습니다. 들어보겠습니다:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
mAsynchronous와 같은 비동기와 관련된 것은 잠시 중단하고, 매개변수를 queue.enqueueMessage 메서드에 전달합니다. msg.target의 할당에 대해서는 나중에 보겠습니다. 이제 MessageQueue 클래스의 enqueueMessage 메서드에 대해 보겠습니다. 메서드가 길어서 설명하지 않겠지만, 중요한 몇 줄을 보겠습니다:
Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg;
메서드 이름과 같이, 무한 루프가 메시지를 메시지 퀼러에(리스트 형식으로) 추가하지만, 추가와 함께 꺼내기도 해야 합니다. 이 메시지를 어떻게 꺼내지를까요?
MessageQueue의 메서드를 검토하여 next()를 찾았습니다. 코드가 너무 길어 설명하지 않겠지만, 이 메서드는 메시지를 꺼내는 데 사용된다는 것을 알고 있습니다. 그러나 이 메서드는 어디에서 호출되는지 궁금합니다. Handler에서는 아니요. Looper라는 중요한 인물을 찾았습니다. 저는 그를 '环路使者'라고 부르며, 메시지 퀼러에서 메시지를 꺼내는 데 책임을 진다고 합니다. 중요한 코드는 다음과 같습니다:
for (;;) { Message msg = queue.next(); // block할 수 있습니다; ... msg.target.dispatchMessage(msg); ... msg.recycleUnchecked(); }
단순하고 명확하게, 우리는 얼마 전에 말한 msg.target을 보았습니다. 얼마 전에 Handler에서 msg.target = this를 할당했습니다. 따라서 Handler의 dispatchMessage를 보겠습니다:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
1.msg의 callback가 비어있지 않으면 handleCallback 메서드를 호출합니다(message.callback.run())
2.mCallback가 비어있지 않으면 mCallback.handleMessage(msg)를 호출합니다
3.끝에 다른 것이 모두 비어있으면 Handler 자신의 handleMessage(msg) 메서드를 실행합니다
msg의 callback는 이미 생각할 수 있을 것입니다. Handler.post(Runnable r)를 통해 전달된 Runnable의 run 메서드이며, 여기서는 java 기본을 언급해야 합니다. 직접 스레드의 run 메서드를 호출하는 것은 일반 클래스에서 메서드를 호출하는 것과 같으며, 새로운 스레드를 시작하지 않습니다.
그래서 여기에 도달하여, 시작할 때의 의문을 해결했습니다. post에서 Runnable을 전달했지만 왜 메인 스레드에서 UI를 업데이트할 수 있는지.
계속 보면 msg.callback가 비어있는 경우의 mCallback를 보면, 이를 위해 생성자를 보여야 합니다:
1. public Handler() { this(null, false); } 2. public Handler(Callback callback) { this(callback, false); } 3. public Handler(Looper looper) { this(looper, null, false); } 4. public Handler(Looper looper, Callback callback) { this(looper, callback, false); } 5. public Handler(boolean async) { this(null, async); } 6. public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) {}} final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException(" "Can't create handler inside thread that has not called Looper.prepare()")); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 7. public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
구현은 마지막 두 개만 있습니다. mCallback이 어디서 왔는지 알고 있습니다. 생성자에传入하면 됩니다.
이 두回调이 모두 null이라면 Handler 자신의 handleMessage(msg) 메서드를 실행합니다. 이는 우리가 잘 알고 있는 새로운 Handler가 재정의한 handleMessage 메서드입니다.
Looper
여기에 의문이 있습니다. 우리가 Handler를 새로 생성할 때 어떤 매개변수도传入하지 않았고, Looper와 관련된 메서드가 호출되었다는 것이 어디에도 나타나지 않았습니다. 그렇다면 Looper의 생성 및 메서드 호출은 어디에 있습니다? 사실, 이런 것들은 Android가 이미 돕고 있습니다. 프로그램의 진입점 ActivityThread의 main 메서드에서 찾을 수 있습니다:
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); ...
요약
Handler의 메시지 메커니즘과 post 메서드, 우리가 가장 많이 사용하는 sendMessage 메서드의 차이점을 약간 정리했습니다. 요약하자면, 주요 클래스는 Handler, Message, MessageQueue, Looper입니다:
Handler를 새로 만들어sendMessage나 post를 통해 메시지를 보내면, Handler는sendMessageAtTime를 호출하여 Message를 MessageQueue에 전달합니다
MessageQueue.enqueueMessage 메서드는 Message를 링크 리스트 형태로 팀에 넣습니다
Looper의 loop 메서드는 MessageQueue.next()를 반복적으로 호출하여 메시지를 추출하고, Handler의 dispatchMessage를 호출하여 메시지를 처리합니다
dispatchMessage에서 msg.callback, mCallback, 즉 post 메서드나 생성자에 의해 전달된 것이 비어 있지 않으면 그들의 콜백을 실행합니다. 모두 비어 있으면 가장 많이 사용하는 오버라이드 handleMessage을 실행합니다.
handler의 메모리 유출 문제에 대해 마지막으로 이야기해보겠습니다
다시 우리가 새로 만든 Handler의 코드를 보겠습니다:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { ... } };
Handler를 내부 클래스(비공개 클래스 포함)로 생성할 때, Handler 객체는 암시적으로 Activity의 참조를 가집니다.
Handler는 일반적으로 시간이 오래 걸리는 백그라운드 스레드와 함께 나타나며, 이 백그라운드 스레드는 작업이 완료되면 메시지를 보내어 UI를 업데이트합니다. 하지만 사용자가 네트워크 요청 중에 Activity를 닫으면, 일반적으로 Activity는 더 이상 사용되지 않으며, GC 체크 시 회수될 수 있습니다. 그러나 이 시점에서 스레드가 아직 실행되지 않았기 때문에, 이 스레드는 Handler의 참조를 가지고 있으며(Handler에게 메시지를 보내는 방법이 무엇인가요?), 이 Handler는 Activity의 참조를 가지고 있어서, 이 Activity는 회수되지 않습니다(즉, 메모리 유출), 그리고 네트워크 요청이 끝날 때까지.
또한, Handler의 postDelayed() 메서드를 실행하면 설정된 delay가 도달하기 전까지 MessageQueue가 하나 추가됩니다. -> Message -> Handler -> Activity의 링크가 끊어지지 않아서, Activity가 참조되어 회수되지 않는 문제가 발생합니다.
해결 방법 중 하나는 약한 참조를 사용하는 것입니다:
static class MyHandler extends Handler { WeakReference<Activity > mActivityReference; MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
이上是 Android handler 메시지 메커니즘에 대한 자료 정리입니다. 앞으로도 관련 자료를 추가할 계획입니다. 많은 지지 감사합니다!
선언: 본 문서의 내용은 인터넷에서 가져왔으며, 저작권은 원작자에게 있으며, 인터넷 사용자가 자발적으로 기여하고 업로드한 것이며, 이 사이트는 소유권을 가지지 않으며, 인공적으로 편집되지 않았으며, 관련 법적 책임도 부담하지 않습니다. 저작권 위반 내용이 발견되면 notice#w로 이메일을 보내 주세요.3codebox.com(보고서 작성 시 #을 @으로 변경하여 보내주세요. 관련 증거를 제공하고, 사실을 확인하면 해당 사이트가 즉시 위반 내용을 삭제합니다。)