안 드 로 이 드 메시지 처리 메커니즘 상세 설명
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()방법 으로 만 메시지 순환 이 시작 되 었 습 니 다.순환 이 시 작 될 때:
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 가지 상황 으로 나 뉜 다.위의 분석 을 한 후에 우 리 는 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 응용 프로그램 에서 메시지 처 리 는 주로 세 가지 과정 으로 나 뉜 다.
이상 은 안 드 로 이 드 메시지 처리 체제 의 상세 한 내용 을 상세 하 게 설명 하 는 것 입 니 다.안 드 로 이 드 메시지 처리 체제 에 관 한 더 많은 자 료 는 우리 의 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.