android 비동기 메시지 메커니즘 소스 차원 철저히 분석(1)

Handler、Message、Loopler、MessageQueen
우선 우리 가 평소에 Handler 를 사용 하 는 가장 흔 한 용법 을 살 펴 보 자.

Handler handler =new Handler(){
  @Override
  public void handleMessage(Message msg) {
   super.handleMessage(msg);
   //      UI     
  }

   new Thread(new Runnable() {
   @Override
   public void run() {
    Message message = Message.obtain();
    ........
    handler.sendMessage(message);
   }
  });
 };
handler 의 구조 함수 의 소스 코드 를 보 세 요.

public Handler() {
 this(null, false);
}
//              
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;
 }
mLooper==null 을 볼 때"Can't create handler inside thread that has not called Looper.prepare()"라 는 이상 을 던 집 니 다.따라서 handler 인 스 턴 스 를 만 들 기 전에 먼저 Looper.prepare()를 호출 해 야 합 니 다.

public static void prepare() {
  prepare(true);
}
// looper   ThreadLocal ,     ThreadLocal             Map,            looper
private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {
  throw new RuntimeException("Only one Looper may  be created per thread");
  }
 sThreadLocal.set(new Looper(quitAllowed));
}
//     new Looper(quitAllowed) ,         MessageQueen
private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed);
 mThread = Thread.currentThread();
}
다음은 handler.sendmessage(message)라 는 방법 을 살 펴 보 겠 습 니 다.문자 그대로 메 시 지 를 보 내 는 것 입 니 다.일반적으로 sendmessage 힘 든 방법 은 결국 sendmessage AtTime(Message msg,long uptime Millis)이라는 방법 을 호출 합 니 다.

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(queue,msg,uptime Millis)라 는 방법 을 실행 하 는 것 을 보 았 다.

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  msg.target = this;
  if (mAsynchronous) {
   msg.setAsynchronous(true);
  }
  return queue.enqueueMessage(msg, uptimeMillis);
 }
마지막 으로 Message Queen 의 quue.enqueue Message(msg,uptime Millis)방법 을 호출 합 니 다.여기 있 는 quue 는 looper 구조 방법 에서 만 든 메시지 대기 열 입 니 다.

//MessageQueen enqueueMessage  
 boolean enqueueMessage(Message msg, long when) {
  if (msg.target == null) {
   throw new IllegalArgumentException("Message must have a target.");
  }
  if (msg.isInUse()) {
   throw new IllegalStateException(msg + " This message is already in use.");
  }

  synchronized (this) {
   if (mQuitting) {
    IllegalStateException e = new IllegalStateException(
      msg.target + " sending message to a Handler on a dead thread");
    Log.w(TAG, e.getMessage(), e);
    msg.recycle();
    return false;
   }

   msg.markInUse();
   msg.when = when;
   Message p = mMessages;
   boolean needWake;
   if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
   } else {
    // Inserted within the middle of the queue. Usually we don't have to wake
    // up the event queue unless there is a barrier at the head of the queue
    // and the message is the earliest asynchronous message in the queue.
    needWake = mBlocked && p.target == null && msg.isAsynchronous();
    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;
   }

   // We can assume mPtr != 0 because mQuitting is false.
   if (needWake) {
    nativeWake(mPtr);
   }
  }
  return true;
 }
Message Queen 은 이름 이 하나의 대기 열 이지 만 실질 적 으로 그 는 단 방향 링크 입 니 다.이 구 조 는 빠 른 삽입 과 삭제 작업 을 할 수 있 습 니 다.위의 소스 코드 에서 알 수 있 듯 이 주로 메 시 지 를 보 내 는 시간 순서에 따라 msg 를 메시지 대기 열 에 삽입 합 니 다.이제 우 리 는 메시지 대기 열 에서 msg 를 꺼 내야 한다.이 럴 때 는 Looper.loop()방법 을 사용 해 야 합 니 다.

public static void loop() {
  final Looper me = myLooper();
  if (me == null) {
   throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }
  final MessageQueue queue = me.mQueue;

  // Make sure the identity of this thread is that of the local process,
  // and keep track of what that identity token actually is.
  Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();

  for (;;) {
   //          msg
   Message msg = queue.next(); // might block
   if (msg == null) {
    // No message indicates that the message queue is quitting.
    return;
   }

   // This must be in a local variable, in case a UI event sets the logger
   Printer logging = me.mLogging;
   if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " " +
      msg.callback + ": " + msg.what);
   }
   // msg  handler  
   msg.target.dispatchMessage(msg);

   if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
   }

   // Make sure that during the course of dispatching the
   // identity of the thread wasn't corrupted.
   final long newIdent = Binder.clearCallingIdentity();
   if (ident != newIdent) {
    Log.wtf(TAG, "Thread identity changed from 0x"
      + Long.toHexString(ident) + " to 0x"
      + Long.toHexString(newIdent) + " while dispatching to "
      + msg.target.getClass().getName() + " "
      + msg.callback + " what=" + msg.what);
   }

   msg.recycleUnchecked();
  }
 }
Looper.loop()방법 을 볼 수 있 습 니 다.quue.next()방법의 역할 은 메시지 대기 열 에서 msg 를 가 져 오 는 것 입 니 다.순환 을 뛰 어 내 리 는 유일한 방법 은 Message Queen 의 next 방법 으로 null 을 되 돌려 줍 니 다.현재 msg 는 이미 꺼 냈 습 니 다.다음 단 계 는 어떻게 그 를 handler 에 전달 하 는 것 입 니까?그래서 죽은 순환 에서 msg.target.dispatchMessage(msg)방법 도 있 습 니 다.msg.target 은 handler 입 니 다.위 handler 의 enqueueMessage()방법 에 들 어 오 는 msg.target=this,this 는 handler 자체 입 니 다.다음은 handler 의 dispatchMessage()방법 을 살 펴 보 겠 습 니 다.

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
   handleCallback(msg);
  } else {
   if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
     return;
    }
   }
   handleMessage(msg);
  }
 }
만약 에 우리 가 인삼 이 없 는 구조 함수 로 handler 를 만 들 면 msg.callback 과 mCallback 이 모두 비어 있 기 때문에 우 리 는 handle Message(msg)를 호출 할 것 입 니 다.그러면 글 에서 시작 하 는 인 스 턴 스 의 전체 절 차 는 끝 납 니 다.handle Message(msg)는 handler 인 스 턴 스 가 있 는 스 레 드 에서 실 행 됩 니 다.

//           handler ,dispatchMessage  mCallback   null
 public Handler(Callback callback) {
  this(callback, false);
 }
//Callback     ,           handleMessage(Message msg),dispatchMessage   if (mCallback != null)       ,         handleMessage(Message msg)  
 public interface Callback {
  public boolean handleMessage(Message msg);
 }

//     handler.post()         
 public final boolean post(Runnable r)
 {
  return sendMessageDelayed(getPostMessage(r), 0);
 }
//getPostMessage(r)          m.callback   ,       runnable  
 private static Message getPostMessage(Runnable r) {
  Message m = Message.obtain();
  m.callback = r;
  return m;
 }
//   handleCallback          run  ,                 handler.post(Runnable r)  UI
 private static void handleCallback(Message message) {
  message.callback.run();
 }
총결산
전체 집행 과정 을 정리 하 다.
1.Looper.prepare()방법 을 호출 합 니 다.이것 은 handler 를 만 드 는 데 필요 한 것 입 니 다.메 인 스 레 드 에 서 는 Activity Thread 가 Looper.prepareMainLooper()방법 으로 looper 를 만 들 었 기 때문에 메 인 스 레 드 에 handler 를 만 들 기 전에 looper 를 만 들 필요 가 없 으 며,Looper.loop()을 통 해 메 인 스 레 드 의 메시지 순환 을 엽 니 다.
2.handler.sendmessage(message)를 호출 하 는 방법 으로 최종 적 으로 enqueueMessage(queue,msg,uptimeMillis)를 실행 하고,enqueueMessage 는 MessageQueen 의 queue.enqueueMessage(msg,uptimeMillis)를 호출 합 니 다.그러면 메시지 대기 열 에 메시지 가 추 가 됩 니 다.
3.Looper.loop()방법 을 호출 하여 죽은 순환 에서 Message msg=quue.next()를 실행 하고 msg 를 메시지 대기 열 에서 계속 꺼 내 는 동시에 msg.target.dispatchMessage(msg)를 실행 하여 handler 에 메 시 지 를 전달 하고 handler 에서 처리 합 니 다.예 를 들 어 우리 가 호출 한 handleMessage 는 메 시 지 를 처리 하 는 방식 중 하나 입 니 다.
비동기 처리 메커니즘 흐름 도

하위 스 레 드 에서 UI 작업 을 하 는 몇 가지 방식
Android 는 다른 스 레 드 에서 UI 스 레 드 에 접근 할 수 있 는 몇 가지 경 로 를 제공 합 니 다.아래 에 몇 가지 유용 한 방법 을 제시 하 였 다.
• Activity.runOnUiThread(Runnable)
•View.post(Runnable)여기 view 는 우리 가 바 꿔 야 할 ui 컨트롤 입 니 다.
• View.postDelayed(Runnable, long)
• Handler.post(Runnable, long)
그러나 조작 이 갈수 록 복잡 해 지면 서 이런 코드 도 복잡 해 지고 유지 하기 어려워 질 것 이다.작업 스 레 드 를 통 해 더욱 복잡 한 상호작용 을 처리 하려 면 작업 스 레 드 에서 Handler 를 사용 하여 UI 스 레 드 에서 온 메 시 지 를 처리 하 는 것 을 고려 할 수 있 습 니 다.물론 가장 좋 은 해결 방안 은 AsyncTask 류 를 확장 하 는 것 일 수도 있 습 니 다.UI 와 상호작용 을 하 는 데 필요 한 작업 스 레 드 작업 을 간소화 합 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기