Android Handler 메커니즘 과 Looper Handler Message 관 계 를 자세히 설명 합 니 다.
우 리 는 다음 과 같은 여섯 가지 문제 에서 Handler 체제 와 Looper,Handler,Message 이전의 관 계 를 토론 합 니까?
1.한 라인 에 Handler 가 몇 개 있 나 요?
2.하나의 스 레 드 에 Looper 가 몇 개 있 습 니까?어떻게 보증 합 니까?
3.Handler 메모리 누 출 원인?왜 다른 내부 류 는 이 문 제 를 말 하지 않 았 습 니까?
4.왜 메 인 스 레 드 는 new Handler 를 사용 할 수 있 습 니까?하위 스 레 드 에서 new Handler 는 어떤 준 비 를 해 야 합 니까?
5.하위 스 레 드 에서 유지 하 는 Looper,메시지 큐 에 메시지 가 없 을 때 처리 하 는 방안 은 무엇 입 니까?무슨 소 용이 있 습 니까?
6.Looper 사 순환 은 왜 카드 사 를 초래 하지 않 습 니까?
1.소스 코드 분석
1.Looper
Looper 에 대해 서 는 prepare()와 loop()두 가지 방법 이 있 습 니 다.
우선 prepare()방법 을 봅 니 다.
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 대상 입 니 다.ThreadLocal 은 스 레 드 가 아니 라 스 레 드 내부 의 저장 클래스 입 니 다.스 레 드 에 데 이 터 를 저장 할 수 있 습 니 다.다섯 번 째 줄 에서 볼 수 있 듯 이 Looper 인 스 턴 스 를 넣 었 습 니 다.ThreadLocal,그리고 2~4 줄 에서 sThreadLocal 이 비어 있 는 지 여 부 를 판단 합 니 다.그렇지 않 으 면 이상 을 던 집 니 다.이것 도 Looper.prepare()방법 은 두 번 호출 될 수 없습니다.이것 은 위의 두 번 째 문제 에 도 대응 합 니 다.
다음은 Looper 의 구조 방법 을 살 펴 보 겠 습 니 다.
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper 의 구조 방법 에 Message Queue(메시지 큐)를 만 들 었 습 니 다.그리고 우 리 는 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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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();
}
}
두 번 째 줄:final Looper me=my Looper();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
여섯 번 째 줄:Looper 인 스 턴 스 의 mque(메시지 큐)를 가 져 옵 니 다.23~98 번 째 줄:사순환 에 들 어 갔 습 니 다.
24 번 째 줄:Message msg=queue.next();next()방법 에 서 는 메 시 지 를 계속 가 져 오고 자 물 쇠 를 채 우 면 프로 세 스 가 계속 막 힌 다.이것 이 바로 우리 가 자주 말 하 는 Looper 의 순환 이 왜 다운 되 지 않 는 지 하 는 것 이다.이 next()소스 코드 는 붙 이지 않 고 뒤에 왜 다운 되 지 않 는 지 하 는 문제 가 있다.
57 줄:msg.target.dispatchMessage(msg)를 호출 합 니 다.msg 에 메 시 지 를 보 낸 target 의 dispatchMessage()방법 으로.msg 의 target 을 처리 하 는 것 은 무엇 입 니까?사실은 handler 대상 입 니 다.다음은 분석 하 겠 습 니 다.
97 줄:메시지 가 차지 하 는 자원 방출
Looper 의 주요 역할:
현재 스 레 드 와 연결 되 어 있 습 니 다.하나의 스 레 드 에 Looper 인 스 턴 스 만 있 고 하나의 Looper 인 스 턴 스 도 하나의 Message Queue 만 있 습 니 다.
loop()방법,Message Queue 에서 메 시 지 를 계속 가 져 오고 메시지 의 target 속성 에 전달 하 는 dispatchMessage()를 처리 합 니 다.
2.Handler
Handler 를 사용 하기 전에 우 리 는 UI 스 레 드 를 업데이트 하 는 데 사용 되 는 인 스 턴 스 를 초기 화 합 니 다.우 리 는 설명 할 때 직접 초기 화 하거나 onCreate 에서 Handler 인 스 턴 스 를 초기 화 합 니 다.그래서 우 리 는 Handler 의 구조 방법 을 먼저 봅 니 다.
Message Queue 와 어떻게 연결 되 는 지 보 세 요.하위 스 레 드 에서 보 내 는 메시지(일반적으로 보 내 는 메 시 지 는 비 UI 스 레 드)가 Message Queue 에 어떻게 보 내 는 지 보 세 요.
public Handler(Callback callback) {
this(callback, 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
열 다섯 번 째 줄:Looper.my Looper()를 통 해 현재 스 레 드 에 저 장 된 Looper 인 스 턴 스 를 가 져 온 다음 19 줄 에서 이 Looper 인 스 턴 스 에 저 장 된 Message Queue(메시지 큐)를 가 져 왔 습 니 다.이렇게 하면 handler 의 인 스 턴 스 가 우리 Looper 인 스 턴 스 에서 Message Queue 와 연결 되 는 것 을 보증 합 니 다.
그리고 가장 자주 사용 하 는 sendmessage 방법 을 보 겠 습 니 다.
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
마지막 으로 sendmessageAtTime 을 호출 한 것 을 발 견 했 습 니 다.이 방법 내부 에 Message Queue 를 직접 가 져 온 다음 에 enqueueMessage 방법 을 호출 했 습 니 다.이 방법 을 다시 보 겠 습 니 다.
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage 에서 먼저 meg.target 에 this 값 을 부여 합 니 다.Looper 의 loop()방법 은 각각 msg 를 꺼 내 msg,target.dispatchMessage(msg)에 메 시 지 를 처리 합 니 다.즉,현재 Handler 를msg 의 target 속성 은 최종 적 으로 queue 의 enqueue Message 방법 을 호출 합 니 다.즉,Handler 가 배 고 픈 메 시 지 를 보 내 고 최종 적 으로 메시지 대기 열 에 저 장 됩 니 다.
이제 잘 알 겠 습 니 다:Looper 는 Prepare()와 loop()방법 을 호출 합 니 다.현재 실행 중인 스 레 드 에 Looper 인 스 턴 스 를 저장 합 니 다.이 인 스 턴 스 는 Message Queue 대상 을 저장 한 다음 현재 스 레 드 에 들 어 갑 니 다.
Message Queue 에서 Handler 가 보 낸 메 시 지 를 무한 반복 해서 읽 습 니 다.그리고 이 메 시 지 를 만 든 handler 의 dispatchMessage()방법 을 되 돌려 보 냅 니 다.dispathmessage 방법 을 살 펴 보 겠 습 니 다.
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
열 번 째 줄:handle Message()방법 을 호출 했 습 니 다.다음은 이 방법 을 보 겠 습 니 다.
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
이것 은 빈 방법 인 것 을 볼 수 있 습 니 다.왜 일 까요?메시지 의 최종 리 셋 은 우리 가 제어 하기 때문에 handler 를 만 들 때 handle Message 방법 을 다시 쓴 다음 msg.what 에 따라 메시지 처 리 를 합 니 다.예 를 들 면:
private Handler mHandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
switch (msg.what)
{
case value:
break;
default:
break;
}
};
};
전체 절 차 는 이미 다 끝 났 으 니 정리 해 보 자.1.우선 Looper,prepare()방법 은 이 스 레 드 에 Looper 인 스 턴 스 를 저장 한 다음 이 인 스 턴 스 에 Message Queue 대상 을 저장 합 니 다.Looper.prepare()는 한 스 레 드 에서 한 번 만 호출 할 수 있 기 때 문 입 니 다.
그래서 Message Queue 는 한 라인 에 하나만 존재 합 니 다.
2.Looper.loop()은 현재 스 레 드 를 무한 순환 에 들 어가 Message Queue 의 인 스 턴 스 에서 메 시 지 를 계속 읽 은 다음 msg.target.dispatchMessage(msg)방법 으로 되 돌려 줍 니 다.
3.Handler 의 구조 방법 은 현재 스 레 드 에 저 장 된 Looper 인 스 턴 스 를 먼저 얻 고 Looper 인 스 턴 스 의 Message Queue 와 연 결 됩 니 다.
4.Handler 의 sendmessage()방법 은 msg 의 target 에 handler 자신 에 게 값 을 부여 한 다음 Message Queue 에 추가 합 니 다.
5.Handler 인 스 턴 스 를 구성 할 때 handlerMessage 방법 을 다시 씁 니 다.즉,msg.target,dispatchMessage(msg)의 최종 호출 방법 입 니 다.
고 개 를 돌려 우리 의 이전의 여섯 가지 문 제 를 보 자.
2.문제 분석
1.한 라인 에 Handler 가 몇 개 있 나 요?
나 는 모두 가 Handler 를 사용 한 적 이 있 을 것 이 라 고 믿는다.그래서 이 문제 의 답 은 여러 개 이다.
이 문 제 는 분석 할 만 한 것 이 없어 서 모두 가 직접 사용 한 적 이 있다!
2.하나의 스 레 드 에 Looper 가 몇 개 있 습 니까?어떻게 보증 합 니까?
하나의 스 레 드 에 Handler 가 여러 개 있 을 수 있 습 니 다.그러면 몇 개의 Looper 가 생 길 까요?정 답:1 개
왜?어떻게 보증 합 니까?
소스 코드 분석 에서 sThead Local 은 하나의 Looper 를 인 스 턴 스 할 수 있 습 니 다.같은 스 레 드 에서 Looper.prepare 방법 을 다시 호출 하면 이상 이 발생 합 니 다.Only one Looper may be created per thread
같은 스 레 드 는 Looper 대상 만 인 스 턴 스 할 수 있 음 을 설명 합 니 다.
3.Handler 메모리 누 출 원인?
왜 다른 내부 류 는 이 문 제 를 말 하지 않 았 습 니까?
Handler 메모리 유출 원인?정 답:내부 클래스 참조 외부 클래스 방법
private Handler mHandler =new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
setLog();
break;
default:
break;
}
}
};
private void setLog() {
Log.d(TAG,"This is Log!");
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.create_xml:
Log.d(TAG,"create_xml");
mHandler.sendMessageDelayed(0,1000*60);
break;
default:
break;
}
익명 의 내부 클래스 Handler 를 만 듭 니 다.이때 저 는 sendmessage Delayed()를 보 내 setLog()방법 을 실행 하 는 것 을 지연 시 킵 니 다.그러나 이 럴 때 제 가 Activity 를 강제로 닫 으 면 이 때 Activity 가 삭 제 됩 니 다.그러나 이 Handler 는 얻 을 수 없습니다.setLog()방법 을 1 분 더 늦 추어 야 실행 할 수 있 기 때문에 이 때 메모리 누 출 이 발생 합 니 다.
다른 내부 류 는 왜 못 해요?
아주 간단 합 니 다.예 를 들 어 ListView 의 ViewHolder 라 는 자주 사용 하 는 익명 내부 클래스 가 주 Activity 가 소각 되면 이 럴 때 ViewHolder 내부 클래스 도 직접 소 각 됩 니 다!그래서 메모리 누 출 문제 가 생기 지 않 습 니 다!
4.왜 메 인 스 레 드 는 new Handler 를 사용 할 수 있 습 니까?
하위 스 레 드 에서 new Handler 는 어떤 준 비 를 해 야 합 니까?
앞의 설명 을 통 해 알 수 있 듯 이 new Handler 의 조건 은 Looper 대상 이 필요 하고 Looper 대상 은 두 가지 방법 으로 prepare()와 loop()방법 을 호출 해 야 합 니 다.아래 메 인 스 레 드 의 Main 방법 을 보 실 수 있 습 니 다.
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
이 Main 방법 은 모든 프로그램 이 시작 되 기 전에 가 야 하 는 main 방법 입 니 다.20 번 째 줄:Looper.prepareMainLooper()를 호출 했 습 니 다.
47 줄:Looper.loop()을 호출 했 습 니 다.
그리고 Looper.prepareMainLooper()소스 코드:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
두 번 째 줄:Looper 의 prepare()방법 을 호출 하 는 것 을 볼 수 있 습 니 다.그 러 니까 메 인 스 레 드 에서 new Handler 를 직접 사용 할 수 있 습 니 다.
그럼 키 라인 new Handler 에 있다 면 어떤 준 비 를 해 야 합 니까?
당연히 필요 하 다.Looper.repar()와 Looper.loop()방법 을 호출 해 야 한다.
5.하위 스 레 드 에서 유지 하 는 Looper,메시지 큐 에 메시지 가 없 을 때 처리 하 는 방안 은 무엇 입 니까?무슨 소 용이 있 습 니까?
하위 스 레 드 에서 Handler 를 사용 할 때 Looper.loop()방법 을 호출 합 니 다.위의 소스 코드 에서[Message msg=quue.next()를 볼 수 있 습 니 다./might block]여기 서 계속 걸 려 죽 어 요?그럼 우 리 는 이 문 제 를 어떻게 해결 합 니까?
Looper 방법 에는 Quit Safely()방법 이 있 습 니 다.이 방법 은 Message Queue(메시지 큐)의 모든 메 시 지 를 제거 하고 메모리 와 스 레 드 를 방출 합 니 다.
이 때 네 번 째 문제 로 돌아 가 하위 스 레 드 에 Handler 를 만 들 려 면 무엇 을 준비 해 야 합 니까?
세 가지 방법 을 호출 합 니 다:
이 문 제 를 이해 하려 면 먼저 어떤 상황 에서 응용 카드 가 죽 을 수 있 는 지 알 아야 한다.
카드 가 죽 으 면 응용 프로그램 이 응답 하지 않 는 다.즉,우리 가 흔히 말 하 는 ANR 에 ANR 문제 가 발생 하 는 것 은 두 가지 가 있다.
이상 은 Android Handler 체제 와 Looper Handler Message 관 계 를 상세 하 게 설명 하 는 상세 한 내용 입 니 다.Android Handler 체제 와 Looper Handler Message 관계 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 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에 따라 라이센스가 부여됩니다.