Handler 스 레 드 메시지 처리 논리
15099 단어 Handler 처리 메시지 프로 세 스
우 리 는 Handler 를 열 어 메시지 처리 에 사용 할 수 있다.우 리 는 메 인 스 레 드 looper 를 사용 하여 메시지 의 송 수신 loop 을 진행 할 수 있다.우리 가 새로 개척 한 비동기 스 레 드 도 사용 할 수 있다.
주 루틴:
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
비동기 스 레 드:HandlerThread hdThread = new HandlerThread("");
hdThread.start();
mHandler = new Handler(hdThread.getLooper());
사용자 정의 스 레 드.(사용자 정의 스 레 드 가 Handler 비동기 처리 메시지 의 스 레 드 로 바 뀌 려 면 run 에서 먼저 Looper. prepare 를 실행 하여 Messsage Queue 를 만 든 다음 Looper. loop () 에서 스 레 드 를 무선 순환 에 들 어가 게 해 야 합 니 다)
newThread newT = new newThread();
newT.start();
new Handler(newT.mLooper);
class newThread extends Thread{
public Looper mLooper;
public newThread(){}
@Override
public void run() {
super.run();
Looper.prepare();
Looper newLoop = Looper.myLooper();
mLooper = newLoop;
Log.d("hlwang","onCreate looper ..... newLooper is:"+mLooper);
mLooper.loop();
}
}
이 문 서 는 주로 Handler 가 정 보 를 어떻게 처리 하 는 지 논술 한 것 이다.
일반 스 레 드: run 방법 을 실행 하고 스 레 드 가 끝 났 습 니 다.(스 레 드 수명 주기 완료) [새 상태 (New), 준비 상태 (Runnable):, 실행 상태 (Running): 차단 상태 (Blocked)]
Handler 스 레 드: 스 레 드 시작 은 무선 순환 체 에 들 어가 순환 할 때마다 메시지 내부 에서 메 시 지 를 꺼 내 해당 하 는 메시지 처리 함 수 를 되 돌려 줍 니 다.메시지 대기 열 이 비어 있 으 면 메시지 대기 열 에 새로운 메시지 가 있 을 때 까지 스 레 드 를 중단 합 니 다.
handler 가 해결 해 야 할 문 제 는 다음 과 같 습 니 다.
1. 메시지 대기 열 을 포함 해 야 합 니 다. 대기 열 에 있 는 메 시 지 는 보통 대기 열 체 제 를 사용 합 니 다. 즉, 먼저 도착 한 메 시 지 를 먼저 처리 합 니 다.
2. 스 레 드 는 while (true) 를 실행 하여 무선 순환 을 하고 순환 에서 메시지 대기 열 에서 메 시 지 를 꺼 내 고 메시지 의 출처 에 따라 메시지 처리 함 수 를 되 돌려 줍 니 다.
3. 다른 외부 스 레 드 는 이 스 레 드 에 메 시 지 를 보 낼 수 있 습 니 다. 메시지 큐 에 삽입 하려 면 메시지 큐 에 자 물 쇠 를 추가 해 야 합 니 다. 즉, 메시지 큐 는 읽 기와 쓰기 작업 을 동시에 할 수 없습니다.
안 드 로 이 드 에서 Handler 의 이 세 가지 문 제 를 어떻게 실현 하 는 지 살 펴 보 자.
1. 먼저 메시지 큐 를 만 듭 니 다.외부 프로그램 은 Handler 를 통 해 스 레 드 에 메 시 지 를 보 내 고 메 시 지 는 Handler 를 통 해 Message Queue 대상 에 전 달 됩 니 다.
Message Queue 는 Looper 를 통 해 prepare 방법 을 실행 하여 만 들 었 습 니 다.모든 스 레 드 는 start 방법 을 실행 하고 run 방법 을 시작 합 니 다.Activity 메 인 스 레 드 든 HandlerThread 가 열 린 스 레 드 든
우리 의 Looper. prepare 방법 은 바로 run 방법 에서 실 행 된 것 이다.다음 과 같다.
HandlerThread. java (Looper. loop 방법 을 실행 하 는 것 을 잊 지 마 세 요)
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
우 리 는 Loop. prepare 방법 에 들 어 갔다.Looper.java
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
그 중에서 ThreadLocal 은 스 레 드 로 컬 저장 소 입 니 다.ps: 스 레 드 로 컬 저장 소 는 무엇 입 니까? (자바 프로 그래 밍 사상 참조)클래스 의 내부 정적 변 수 는 프로 세 스 의 그 스 레 드 에 접근 하 더 라 도 그 내용 은 항상 같 습 니 다. 컴 파일 러 내부 에 static 정적 변 수 를 위해 공간 을 따로 분 배 했 기 때 문 입 니 다.스 레 드 로 컬 저장 소 는 정반 대 입 니 다. 서로 다른 스 레 드 접근 은 서로 다른 결 과 를 얻 을 수 있 습 니 다.다시 말 하면 Looper 는 대응 하 는 스 레 드 이 고 하나의 스 레 드 는 Looper 대상 만 있 을 수 있 습 니 다. 이것 은 Looper 가 정의 한 것 입 니 다.처음으로 Looper. prepare 방법 을 호출 하면 sThreadLocal. get, null 을 얻 을 수 있 습 니 다. 이 때 new Looper 대상 을 호출 하고 sThreadLocal. set 에 줍 니 다.같은 스 레 드 에서 prepare 를 다시 호출 하면 throw Runtime Exception 입 니 다.
이어서 내 려 다 보 세 요. new Looper 에서 뭐 했 어 요?
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
Looper 의 구조 방법 에 Message Queue 를 만 들 었 습 니 다.
2. 스 레 드 를 무한 순환 에 들 어가 게 합 니 다.
Looper. loop 방법 을 실행 합 니 다.이 방법 은 다음 과 같다.
public static void loop() {
Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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();
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
long wallStart = 0;
long threadStart = 0;
// 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);
wallStart = SystemClock.currentTimeMicro();
threadStart = SystemClock.currentThreadTimeMicro();
}
msg.target.dispatchMessage(msg);
if (logging != null) {
long wallTime = SystemClock.currentTimeMicro() - wallStart;
long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
if (logging instanceof Profiler) {
((Profiler) logging).profile(msg, wallStart, wallTime,
threadStart, threadTime);
}
}
// 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.recycle();
}
}
}
2.1. 여기 서 먼저 my Looper 를 호출 하여 현재 Looper 대상 으로 돌아 갑 니 다. 이 함수 내 부 는 스 레 드 로 컬 저장 소 sThreadLocal. get 을 통 해서 만 가 져 옵 니 다.
2.2 while (true) 는 무한 순환 에 들어간다.
2.2.1. Message Queue. next 방법 으로 대기 열 에 있 는 Message 를 꺼 냅 니 다.현재 대기 열 이 비어 있 으 면 현재 스 레 드 가 걸 립 니 다. 즉, next 방법 내부 에서 스 레 드 를 걸 어 놓 는 논 리 를 처리 합 니 다.
2.2.2. message 가 null 이 아니라면 msg. target. dispatchMessage 함 수 를 되 돌려 줍 니 다. 이곳 의 target 은 msg 에 대응 하 는 Handler 입 니 다.
target 에 대해 논 리 는 다음 과 같 습 니 다.
Handler 가 Message 를 구성 할 때:
public final Message obtainMessage()
{
return Message.obtain(this);
}
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
또는:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
2.2.3 메시지 처리 가 완료 되면 msg. recycle 방법 으로 이 Message 대상 이 차지 하 는 시스템 자원 을 회수 합 니 다.Message 클래스 내부 에 하나의 데이터 풀 을 사용 하여 Message 대상 을 저장 하기 때문에 끊임없이 Message 클래스 대상 을 만 들 고 삭제 하 는 것 을 피 할 수 있 습 니 다.따라서 이 Message 를 처리 할 때마다 Message 대상 을 빈 상태 로 표시 하여 이 Message 대상 을 다시 사용 할 수 있 도록 해 야 합 니 다.
3. 메시지 대기 열 은 줄 을 서서 메 시 지 를 처리 합 니 다. 즉, 먼저 도착 한 메 시 지 를 먼저 처리 하지만 메시지 자체 가 처리 되 는 시간 을 지정 하면 이 시간 을 기 다 려 야 처리 할 수 있 습 니 다.메 시 지 는 Message Queue 에서 Message 를 사용 하여 열 에 있 는 메 시 지 를 링크 구조 로 저장 합 니 다.Message 에서 next 변 수 는 다음 메 시 지 를 가리 키 고 있 습 니 다.
Message Queue 에는 두 가지 주요 함수 가 있 습 니 다. '메시지 꺼 내기' next 방법, '메시지 추가' enquenceMessage 방법 입 니 다.
next 방법 내부 절 차 는 세 단계 로 나 뉜 다.
final Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(mPtr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
final Message msg = mMessages;
if (msg != null) {
final long when = msg.when;
if (now >= when) {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
} else {
nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
}
} else {
nextPollTimeoutMillis = -1;
}
// If first time, then get the number of idlers to run.
if (pendingIdleHandlerCount < 0) {
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.
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("MessageQueue", "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;
}
}
3.1, nativePollOnce 호출 방법
nativePollOnce(mPtr, nextPollTimeoutMillis);
이것 은 jni 함수 로 메시지 대기 열 에서 메 시 지 를 꺼 내 는 역할 을 합 니 다.Message Queue 클래스 내부 에 메시지 큐 가 저장 되 어 있 지 않 습 니 다. 실제 메시지 큐 는 jni 가 구현 한 c 코드 에 저 장 됩 니 다.즉, C 환경 에서 NativeMessage Queue 데이터 대상 을 만 들 었 습 니 다. 이것 은 nativePollOnce 함수 의 첫 번 째 매개 변수의 의미 입 니 다.private int mPtr; // used by native code
mPtr 는 int 형 변수 로 c 에서 NativeMessage Queue 대상 으로 강제 전 환 됩 니 다. C 환경 에서 메시지 대기 열 에 메시지 가 없 으 면 현재 스 레 드 가 걸 립 니 다 (wait). 메시지 가 있 으 면 c 코드 는 자바 환경 에서 Message 대상 에 게 이 메 시 지 를 할당 합 니 다.Message Queue 의 jni 코드 는 framework / base / core / jni / android 에 있 습 니 다.os_Message Queue. cpp 에서
3.2 다음 에 synchronized (this) 에 포 함 된 코드 세그먼트 를 실행 합 니 다.this 는 메시지 와 메 시 지 를 쓰 는 자물쇠 로 사 용 됩 니 다.enqueueMessage 방법 에서 도 synchronized (this) 를 사용 하여 코드 동기 화 를 진행 하 였 습 니 다.
이 코드 는 메 시 지 를 꺼 내 서 이 메시지 의 실행 시간 이 도 착 했 는 지 판단 하고 도착 하면 이 메 시 지 를 되 돌려 주 며 mMessage 를 비 웁 니 다.그렇지 않 으 면 다음 메 시 지 를 가 져 오 십시오.
3.3 mMessage 가 null 이면 c 환경의 메시지 대기 열 에 실행 가능 한 메시지 가 없다 는 것 을 의미한다.따라서 mPendingIdleHandler 목록 의 빈 리 셋 함 수 를 실행 합 니 다.
메시지 코드 추가:
enqueueMessage
final boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg
+ " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
final boolean needWake;
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
//Log.d("MessageQueue", "Enqueing: " + msg);
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked; // new head, might need to wake up
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
needWake = false; // still waiting on head, no need to wake up
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
}
인자 msg 를 mMessage 에 할당 합 니 다.nativeWake (mPtr) 를 호출 합 니 다.이것 은 jni 함수 입 니 다. 내부 에서 mMessage 메 시 지 를 C 환경의 메시지 대기 열 에 추가 하고 이 스 레 드 가 wait 상태 에 있 으 면 이 스 레 드 를 깨 웁 니 다.
。
Handler 의 메시지 처리 안 드 로 이 드 가 실 현 됩 니 다. 지금까지 저 희 는 Handler 를 사용 할 때 handleMessage 방법 을 실 현 했 습 니 다.이것 은 Message 가 target 의 dispatchMessage 를 되 돌 릴 때 다음 코드 를 실행 하기 때 문 입 니 다.
Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
여기까지.부적 절 한 점 이 있 으 면 교 류 를 환영 합 니 다.