안 드 로 이 드 메시지 처리 메커니즘 상세 설명

요약
Android 응용 프로그램 은 메 시 지 를 통 해 작 동 되 며,Android 메 인 스 레 드 가 시 작 될 때 내부 에 메시지 큐 를 만 듭 니 다.그리고 무한 순환 에 들 어가 서 새로운 소식 이 있 는 지 물 어 봅 니 다.새로운 소식 이 있 으 면 새로운 소식 을 처리 하 세 요.소식 이 없 으 면 메시지 순환 이 깨 어 날 때 까지 차단 상태 로 들어간다.
그렇다면 안 드 로 이 드 시스템 에서 메시지 처리 체 제 는 어떻게 이 루어 졌 을 까?프로그램 개발 시,우 리 는 자주 Handler 를 사용 하여 Message(메시지)를 처리한다.그래서 Handler 는 메시지 처리 자 이 고 Message 는 메시지 주체 임 을 알 수 있다.그 밖 에 메시지 큐 와 메시지 폴 링 두 캐릭터 도 있다.이들 은 각각 Message Queue 와 Looper 이 고 Message Queue 는 메시지 대기 열 이 며 Looper 는 폴 링 메 시 지 를 책임 집 니 다.
간단 한 소개
우 리 는 안 드 로 이 드 의 메시지 메커니즘 처 리 는 주로 Handler,Message,Message Queue,Looper 네 가지 유형의 실현 으로 이 루어 진 다 는 것 을 이미 알 고 있다.그렇다면 그들 사이 의 관 계 는 어 떻 습 니까?
그 중에서 Message 는 메 시 지 를 보 내 는 Handler 대상,메시지 정보,메시지 표지 등 메 시 지 를 저장 하 는 각종 정 보 를 담당 하 는 메시지 주체 이다.Message Queue 는 메시지 대기 열 입 니 다.내부 에서 Message(메시지)를 대기 열 로 유지 합 니 다.Handler 는 메 시 지 를 보 내 고 처리 하 는 일 을 맡 는 다.Looper 는 폴 링 메시지 대기 열 을 책임 집 니 다.

Android 메시지 메커니즘 원리
스 레 드 메시지 큐 만 들 기
안 드 로 이 드 응용 프로그램 에서 메시지 처리 프로그램 이 실행 되 기 전에 메시지 대기 열(즉 Message Queue)을 만들어 야 합 니 다.메 인 스 레 드 에 서 는 Looper 류 의 정적 구성원 함수 prepareMainLooper()를 호출 하여 메시지 대기 열 을 만 듭 니 다.다른 하위 스 레 드 에서 정적 구성원 함수 prepare()를 호출 하여 만 듭 니 다.
prepareMainLooper()와 prepare()의 실현:

  /**
   * Initialize the current thread as a looper, marking it as an
   * application's main looper. The main looper for your application
   * is created by the Android environment, so you should never need
   * to call this function yourself. See also: {@link #prepare()}
   *           Looper, Android    ,        .
   */
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
  }
  
  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }

   /** Initialize the current thread as a looper.
   * This gives you a chance to create handlers that then reference
   * this looper, before actually starting the loop. Be sure to call
   * {@link #loop()} after calling this method, and end it by calling
   * {@link #quit()}.
   *         ,  loop()        .           ,      quit()      .
   */
  public static void prepare() {
    prepare(true);
  }

  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));
  }
이 두 함수 가 호출 되 는 과정 에서 sThreadLocal 변 수 는 모두 사용 되 었 습 니 다.이 변 수 는 ThreadLocal 형식 으로 현재 스 레 드 의 Looper 대상 을 저장 합 니 다.안 드 로 이 드 애플 리 케 이 션 에서 메시지 큐 를 만 들 때마다 유일 하 게 해당 하 는 Looper 대상 이 있다 는 것 이다.그리고 우 리 는 소스 코드 에서 대상 이 유일 하지 않 을 때 이상 을 던 지 는 것 을 볼 수 있다.

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); //      
    mThread = Thread.currentThread();
  }
위의 소스 코드 에서 볼 수 있 듯 이 Looper 대상 이 예화 되 는 과정 과 동시에 메시지 대기 열 을 만 듭 니 다.
메시지 순환 과정
메시지 큐 가 완 료 된 후에 Looper 대상 을 호출 하 는 정적 구성원 방법 loop()이 메시지 순환 을 시작 합 니 다.

  /**
   * Run the message queue in this thread. Be sure to call
   * {@link #quit()} to end the 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 (;;) { //      
      Message msg = queue.next(); //            
      if (msg == null) {
        //message null ,      
        return;
      }

      // This must be in a local variable, in case a UI event sets the logger
      final Printer logging = me.mLogging;
      if (logging != null) {...}

      final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

      final long traceTag = me.mTraceTag;
      if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {...}
      final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
      final long end;
      try {
        msg.target.dispatchMessage(msg); //    
        end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
      } finally {
        if (traceTag != 0) {
          Trace.traceEnd(traceTag);
        }
      }
      if (slowDispatchThresholdMs > 0) {
        final long time = end - start;
        if (time > slowDispatchThresholdMs) {...}
      }

      if (logging != null) {...}

      // Make sure that during the course of dispatching the
      // identity of the thread wasn't corrupted.
      final long newIdent = Binder.clearCallingIdentity();
      if (ident != newIdent) {...}
      msg.recycleUnchecked();
    }
  }
위의 소스 코드 는 메시지 순환 의 과정 으로 loop()방법 으로 만 메시지 순환 이 시작 되 었 습 니 다.순환 이 시 작 될 때:
  • 현재 스 레 드 의 Looper 대상 을 가 져 옵 니 다.null 이면 이상 을 던 집 니 다
  • 메시지 대기 열 을 가 져 오고 메시지 순환 에 들 어가 기 시작 합 니 다
  • 메시지 대기 열 에서 메 시 지 를 가 져 옵 니 다(Message Queue 의 next()방법 을 호출 합 니 다).null 이면 순환 을 끝 냅 니 다.그렇지 않 으 면 계속 집행 한다
  • 메시지 처리,메시지 자원 회수(msg.recycleUnchecked()
  • 메시지 순환 과정 에서 Message Queue 의 next()방법 으로 정 보 를 제공 하고 정보 가 없 을 때 수면 상태 에 들 어가 다른 인 터 페 이 스 를 처리 합 니 다.이 과정 은 매우 중요 하 다.next()방법 을 통 해 메시지 순환 의 종료 여 부 를 결정 한다.
    
     Message next() {
        final long ptr = mPtr; // native    , mPtr 0   null,      
        if (ptr == 0) {
          return null;
        }
    
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0; //0     ,-1    
        for (;;) {
          if (nextPollTimeoutMillis != 0) {
            //           Binder       
            Binder.flushPendingCommands(); 
          }
          //native  ,nextPollTimeoutMillis -1       
          nativePollOnce(ptr, nextPollTimeoutMillis); 
          synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
              // Stalled by a barrier. Find the next asynchronous message in the queue.
              do {
                prevMsg = msg;
                msg = msg.next;
              } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
              if (now < msg.when) {
                // Next message is not ready. Set a timeout to wake up when it is ready.
                nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
              } else {
                // Got a message.
                mBlocked = false;
                if (prevMsg != null) {
                  prevMsg.next = msg.next;
                } else {
                  mMessages = msg.next;
                }
                msg.next = null;
                if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                msg.markInUse();
                return msg; //    
              }
            } else {
              // No more messages.
              nextPollTimeoutMillis = -1; //       
            }
    
            // Process the quit message now that all pending messages have been handled.
            //      
            if (mQuitting) {
              dispose();
              return null;
            }
    
            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
              pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
              // No idle handlers to run. Loop and wait some more.
              mBlocked = true;
              continue;
            }
    
            if (mPendingIdleHandlers == null) {
              mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
          }
    
          // Run the idle handlers.
          // We only ever reach this code block during the first iteration.
          //        IdleHandler  
          for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
    
            boolean keep = false;
            try {
              keep = idler.queueIdle();
            } catch (Throwable t) {
              Log.wtf(TAG, "IdleHandler threw exception", t);
            }
    
            if (!keep) {
              synchronized (this) {
                mIdleHandlers.remove(idler);
              }
            }
          }
    
          // Reset the idle handler count to 0 so we do not run them again.
          pendingIdleHandlerCount = 0;
    
          // While calling an idle handler, a new message could have been delivered
          // so go back and look again for a pending message without waiting.
          nextPollTimeoutMillis = 0;
        }
      }
    
    메시지 순환 종료 과정
    위 에서 볼 수 있 듯 이 loop()방법 은 죽은 순환 입 니 다.Message Queue 의 next()방법 이 null 로 돌아 갈 때 만 순환 을 끝 냅 니 다.그럼 Message Queue 의 next()방법 은 언제 null 입 니까?
    Looper 클래스 에서 우 리 는 두 가지 끝 나 는 방법 인 quit()와 quit Salely()를 보 았 다.이들 의 차 이 는 quit()방법 이 순환 을 직접 끝내 고 Message Queue 의 모든 메 시 지 를 처리 하 는 것 이 며,quitSafely()는 메시지 대기 열 에 있 는 나머지 비 지연 메시지(지연 메시지(지연 메시지)를 처리 하고 나 서 야 종료 하 는 것 입 니 다.이 두 가지 방법 은 모두 Message Queue 의 quit()방법 을 호출 했다.
    
    void quit(boolean safe) {
        if (!mQuitAllowed) {
          throw new IllegalStateException("Main thread not allowed to quit.");
        }
    
        synchronized (this) {
          if (mQuitting) {
            return;
          }
          mQuitting = true; //      
    
          //          
          if (safe) {
            removeAllFutureMessagesLocked(); //         
          } else {
            removeAllMessagesLocked(); //       
          }
    
          // We can assume mPtr != 0 because mQuitting was previously false.
          nativeWake(mPtr); //       
        }
      }
    
    메시지 대기 열 에 있 는 메시지 처리:safe 로고 에 따라 다른 처리 방식 을 선택 하 십시오.
    
      /**
       * API Level 1
       *              
       */
      private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
          Message n = p.next;
          p.recycleUnchecked(); //      
          p = n;
        }
        mMessages = null;
      }
    
      /**
       * API Level 18
       *                
       */
      private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
          if (p.when > now) {
            removeAllMessagesLocked();
          } else {
            Message n;
            for (;;) {
              n = p.next;
              if (n == null) {
                return;
              }
              if (n.when > now) {
                //      
                break;
              }
              p = n;
            }
            p.next = null;
            //            when(      ,                      )
            do {
              p = n;
              n = p.next;
              p.recycleUnchecked(); //      
            } while (n != null);
          }
        }
      }
    
    메시지 전송 과정
    Android 응용 프로그램 에 서 는 Handler 클래스 를 통 해 스 레 드 메시지 큐 에 메 시 지 를 보 냅 니 다.Handler 대상 마다 Looper 대상 과 Message Queue 대상 을 하나씩 가지 고 있 습 니 다.
    
    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(); //  Looper  
        if (mLooper == null) {...}
        mQueue = mLooper.mQueue; //      
        mCallback = callback;
        mAsynchronous = async;
      }
    
    Handler 클래스 에서 우 리 는 여러 종류의 sendmessage 방법 을 볼 수 있 는데,그것들 은 결국 같은 방법 인 sendmessage AtTime()방법 을 호출 했다.
    
      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); 
      }
      
      private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
          msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
      }
    
    이 두 가지 방법 은 쉽게 이해 할 수 있 습 니 다.바로 Message Queue 대상 을 통 해 enqueueMessage()방법 으로 메시지 대기 열 에 메 시 지 를 추가 하 는 것 입 니 다.
    
    boolean enqueueMessage(Message msg, long when) {
        // Handler null
        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;
      }
    
    원본 코드 를 통 해 알 수 있 듯 이 하나의 메시지 가 메시지 대기 열 에 삽입 되 려 면 다음 과 같은 절차 가 필요 합 니 다.
    메시지 가 가지 고 있 는 Handler 대상 은 null 로 이상 을 던 집 니 다.소식 이 소비 되 었 을 때 이상 을 던 집 니 다.
    메시지 큐 에 메시지 가 없 을 때 직접 삽입 하기;
    메시지 큐 에 메시지 가 존재 할 때 메시지 의 실행 시간 을 비교 하여 해당 위치 에 메 시 지 를 삽입 합 니 다.
    메시지 순환 을 깨 울 필요 가 있 는 지 판단 합 니 다.
    메시지 처리 프로 세 스
    메시지 순환 과정 에서 새로운 메시지 가 들 어 오 면 메 시 지 를 처리 하기 시작한다.위의 분석 에서 우 리 는 메시지 순환 에서 목표 메 시 지 는 Handler 대상 의 dispatchMessage()방법 을 호출 하 는 것 을 볼 수 있다.이것 이 바로 메 시 지 를 처리 하 는 방법 이다.
    
     /**
       * Handle system messages here.
       */
      public void dispatchMessage(Message msg) {
        //   Callback    null,  Callback  
        if (msg.callback != null) {
          handleCallback(msg);
        } else {
          if (mCallback != null) {
            //Handler Callback    null,      
            if (mCallback.handleMessage(msg)) {
              return;
            }
          }
          handleMessage(msg); //    
        }
      }
    
    소스 코드 를 보면 핸들 러 처리 메 시 지 는 3 가지 상황 으로 나 뉜 다.
  • Message 의 callback 이 null 이 아 닐 때 Message 의 callback 방법 을 실행 합 니 다.이 콜 백 은 Runnable 인터페이스 입 니 다
  • Handler 의 Callback 인터페이스 가 null 이 아 닐 때 Callback 인터페이스 에서 의 방법 을 실행 합 니 다
  • Handler 의 handleMessage()방법 을 직접 실행 합 니 다
  • Looper 가 loop()을 호출 하기 시 작 했 을 때 메 인 스 레 드 가 왜 끊 기지 않 습 니까?
    위의 분석 을 한 후에 우 리 는 Looper.loop()이 하나의 순환 에 들 어 갔다 는 것 을 알 게 되 었 다.그러면 메 인 스 레 드 에서 이 순환 을 실행 하 는 것 이 왜 메 인 스 레 드 가 끊 기거 나 메 인 스 레 드 에서 의 다른 조작 이 순조롭게 진행 되 었 는 지 알 게 되 었 다.예 를 들 어 UI 작업 이다.안 드 로 이 드 애플 리 케 이 션 은 메시지 로 구동 되 기 때문에 안 드 로 이 드 애플 리 케 이 션 의 조작 도 안 드 로 이 드 메시지 메커니즘 을 통 해 이 루어 진다.이 럴 때 는 안 드 로 이 드 프로그램 이 시작 하 는 입구 클래스 인 Activity Thread 를 분석 해 야 한다.안 드 로 이 드 프로그램 이 시 작 될 때 자바 층 에서 Activity Thread 의 main()방법 을 입구 로 하 는 것 이 바로 우리 가 말 하 는 메 인 스 레 드 라 는 것 을 잘 알 고 있 습 니 다.
    
    public static void main(String[] args) {
        ...
        ...
        ...
        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false); //  Binder   (     ), ActivityManagerService    
    
        if (sMainThreadHandler == null) {
          sMainThreadHandler = thread.getHandler();
        }
        ...
        ...
        ...
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
      }
    
    Activity Thread 의 main()방법 에서 저 희 는 Looper 의 초기 화 와 메시지 순환 의 시작 을 볼 수 있 습 니 다.또한 관건 적 인 방법 인 attach()와 Activity Manager Service 가 링크 를 만 드 는 것 도 있 습 니 다.여기 서 링크 를 만 드 는 것 은 해당 Activity 에서 각종 사건 이 발생 하기 위해 서 입 니 다.여기까지 말 하면 Native 층 Looper 의 초기 화 와 관련 이 있 습 니 다.Looper 가 초기 화 될 때 메시지 큐 의 읽 기와 쓰 기 를 유지 하고 epoll 체 제 를 통 해 읽 기와 쓰기 이 벤트 를 감청 합 니 다(IO 다 중 재 활용 체제).
    4.567917.새로운 소식 이 처리 되 어야 하지 않 을 때 메 인 스 레 드 는 파이프 에 막 혀 새로운 소식 이 처리 되 어야 할 때 까지 막 힌 다
  • 다른 스 레 드 에서 메시지 대기 열 에 메 시 지 를 보 낼 때 파 이 프 를 통 해 데 이 터 를 작성 합 니 다
  • 우리 가 프로그램 을 디 버 깅 할 때,우 리 는 함수 호출 창 고 를 통 해 그 중의 이 치 를 발견 할 수 있다.

    이것 은 또한 시 작 된 그 말 인 안 드 로 이 드 응용 프로그램 이 메 시 지 를 통 해 작 동 한 다 는 것 을 증명 했다.
    모든 메시지 가 지 정 된 시간 에 실 행 될 지 여부 입 니 다.
    이 문 제 는 우리 가 메시지 대기 열 에서 메 시 지 를 보 낼 때(예 를 들 어 1000 ms 를 지연 시 켜 메시지 potDelay(action,1000)를 실행 하 는 것)1000 ms 후에 이 메 시 지 를 실행 하 는 것 이 아니 냐 는 뜻 이다.
    답 은 확실 하지 않다.저 희 는 안 드 로 이 드 메시지 만 시간 순서에 따라 메시지 대기 열 에 저 장 됩 니 다.만약 에 저희 가 대기 열 에 1000 ms 지연 메시지 10000 개 를 추가 하면 마지막 으로 실 행 된 메시지 와 첫 번 째 로 실 행 된 메시지 의 실행 시간 이 다 릅 니 다.
    총결산
    이로써 안 드 로 이 드 시스템 의 메시지 처리 메커니즘 은 분석 이 끝났다.Android 응용 프로그램 에서 메시지 처 리 는 주로 세 가지 과정 으로 나 뉜 다.
  • Looper 의 메시지 순환 을 시작 하고 메시지 대기 열 을 감청 합 니 다
  • Handler 를 통 해 메시지 대기 열 에 메 시 지 를 보 냅 니 다
  • 메시지 순환 을 통 해 Handler 대상 을 호출 하여 새로 가입 한 정 보 를 처리 합 니 다
  • 메시지 큐 를 사용 할 때 메 인 스 레 드 에서 프로그램 이 시 작 될 때 메시지 큐 를 만 듭 니 다.따라서 메 인 스 레 드 의 메시지 체 제 를 사용 할 때 메시지 순환 과 메시지 큐 를 초기 화 할 필요 가 없습니다.하위 스 레 드 에서 메시지 큐 를 초기 화 해 야 합 니 다.메시지 큐 를 사용 하지 않 아 도 될 때 Looper 의 quit 나 quitSafely 방법 으로 메시지 순환 을 닫 아야 합 니 다.그렇지 않 으 면 하위 스 레 드 가 대기 상태 에 있 을 수 있 습 니 다.
    이상 은 안 드 로 이 드 메시지 처리 체제 의 상세 한 내용 을 상세 하 게 설명 하 는 것 입 니 다.안 드 로 이 드 메시지 처리 체제 에 관 한 더 많은 자 료 는 우리 의 다른 관련 글 을 주목 하 세 요!

    좋은 웹페이지 즐겨찾기