Handler 메커니즘 분석[Beta]

11290 단어
Handler의 사용법은 인터넷에 많은데 여기서 그 실현 메커니즘을 주로 분석한다.
Handler와 관련된 몇 가지 중요한 개념:Handler,Looper,Message,MessageQueue
Handler: 루틴 간 통신에 사용되며, Looper와 연결됩니다. 루틴의 메시지 대기열에 메시지를 추가하거나, 루퍼가 보낸 메시지를 받고 처리합니다.
Message: 메시지로 모든 객체를 포함할 수 있습니다.
MessageQueue: 메시지 대기열, 메시지 객체를 저장합니다.
Looper: 하나의 Handler와 하나의 스레드와 연결되어 있으며, 스레드의MessageQueue에서 Message를 순환해서 가져와Handler에 보내는 데 사용됩니다
=======================
다음handler의 간단한 사용법을 돌이켜 보십시오: 메인 라인에handler를 만들고 하위 라인은sendMessage 방법을 사용해서 메시지를 보내고handleMessage 방법에서 메시지를 받습니다.
        handler  = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                textView.setText("11111111");

            }
        };

        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message msg = Message.obtain();
                        msg.what = 1;
                        handler.sendMessage(msg);
                    }
                }).start();

            }
        });

먼저 Handler의 구성 방법부터 분석합니다.
    public Handler() {
        this(null, false);
    }
    public Handler(Callback callback, boolean async) {
        ...

        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 = Looper.myLooper(); 
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

sThreadLocal은 ThreadLocal의 글로벌 변수입니다.
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

그래서 Looper.myLooper()가 현재 스레드와 연결된 Looper 객체를 반환합니다.mLooper 객체가 비어 있으면 예외가 발생합니다.주 스레드, 즉 UI 스레드의 경우 시작할 때 Looper와 연결되므로 null로 되돌아오지 않습니다.마스터 스레드의 시작 프로세스:
먼저 ActivityThread 클래스를 알아야 합니다.
/**
 * This manages the execution of the main thread in an
 * application process, scheduling and executing activities,
 * broadcasts, and other operations on it as the activity
 * manager requests.
 *
 * {@hide}
 */

이 클래스는 App 마스터 스레드, 즉 우리가 말하는 UI 스레드를 관리하는데 이것은Activity의 시작을 책임진다.
ActivityThread의 포털 함수 main:
    public static void main(String[] args) {
        ...

        Looper.prepareMainLooper();

        ...
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Looper를 호출하는 정적 방법prepareMainLooper를 보았습니다.
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();
        }
    }

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));
    }
Looper를 볼 수 있습니다.prepare 방법에서 Looper 대상을 만들고 Looper의 sThreadLocal ThreadLocal 변수에 저장합니다.
Looper 구성 방법 코드:
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper 내부에 MessageQueue 객체가 생성되어 mQueue 객체에 값이 지정되고 현재 스레드가 mThread에 지정됩니다.
다음은 Handler 구성 방법의 다음 코드 행입니다.
mQueue = mLooper.mQueue;
Looper 내부의 MessageQueue 객체에 글로벌 변수 mQueue
Handler로 메시지를 보내야 할 때 sendMessage 시리즈 방법을 사용합니다. 그 중에서 sendMessage 방법의 실현은 다음과 같습니다.
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    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);
    }

내부에서 enqueue Message를 호출하고 현재 스레드와 연결된 Message Queque 대상과 보내는 Message 대상을 매개 변수로 전송하는 것을 볼 수 있습니다. 그럼 enqueue Message를 보십시오.
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
msg.target = this;
이 줄은 현재Handler가Message의 target 변수에 값을 부여합니다.이 target 변수의 역할은handle Message를 리셋해야 하는handler를 기록하는 것입니다. 다음은MessageQueue의 원본 코드에서 실현을 볼 수 있습니다.
return 문에서 MessageQueue의 enqueueMessage 방법을 호출했습니다.
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) {
            ...
            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;
    }

동기화 블록의 문장 작용은 메시지 대상을 메시지Queue에 넣고 메시지를 통과하는 것이다.next에서 위치 순서를 지정합니다.
다음과 같은 일련의 코드를 읽어 결론을 얻을 수 있습니다.
Handler를 만들 때 Handler 대상은 현재 라인의 Looper 대상과 연결됩니다. Looper 내부에 MessageQueue가 포함되어 있습니다. Handler도 이 MessageQueue를 인용하고 Handler가sendMessage 방법으로 보낸 Message 대상은 이 MessageQue에 삽입됩니다.
다음에handler가 메인 라인에서 메시지를 보낸 후에handlerMessage 방법은 어떻게 리셋됩니까?답은 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;
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...

            msg.target.dispatchMessage(msg);
            ...
        }
    }

먼저 myLooper를 통해 현재 루트와 관련된 Looper를 가져온 다음에 Looper의 MessageQueue 대상을 가져옵니다. for 순환 문장 블록은 사순환입니다. MessageQueue에 Message 대상이 없을 때 막히고, Message 대상을 가져오면 이 Message의 target의 dispatchMessage 방법을 호출합니다. target 변수는 이 Message를 보내는 Handler 대상입니다. target은 Handler의 enqueue Message 방법에서 지정한 것입니다.
ActivityThread의 main 방법에서 볼 수 있습니다. Looper.loop이 마지막으로 호출되었기 때문에 메인 라인의 Looper에 대해MessageQueue에서 메시지를 계속 가져옵니다.다음은 dispatchMessage:
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
handler가callback을 설정하지 않았다면handleMessage 방법을 호출합니다.
상기 loop과 디스패치 메시지의 코드를 읽은 결과 Looper는MessageQueue에서 메시지를 가져오고 현재 메시지와 관련된Handler의 디스패치 메시지를 호출할 것이라고 결론을 내렸다.
상기 코드 분석은 주 라인에서Handler를 만드는 경우에 세워진 것입니다. 만약에 하위 라인에서Handler를 만들면 하위 라인에서 Looper를 만들지 않기 때문에 구조 방법에서 Looper를 호출합니다.myLooper()가 null로 반환되고 mLooper 객체가 비어 런타임 예외가 발생합니다.
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            handler = new Handler();
                            Message msg = Message.obtain();
                            msg.what = 1;
                            handler.sendMessage(msg);
                        }
                    }).start();
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                                                                    at android.os.Handler.<init>(Handler.java:200)
                                                                    at android.os.Handler.<init>(Handler.java:114)
                                                                    at ohehehou.libtest.MainActivity$1$1.run(MainActivity.java:38)
                                                                    at java.lang.Thread.run(Thread.java:818)

따라서 하위 루틴에Handler를 만들고 주 루틴에 메시지를 보내려면 Looper를 수동으로 호출할 수 있습니다.prepare는 하위 루틴과 연결된 Looper 대상을 만들고 Looper를 호출해야 합니다.looper에서 메시지를 가져오려면 looper를 반복합니다.
new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Looper.prepare();
                            handler = new Handler();
                            Message msg = Message.obtain();
                            msg.what = 1;
                            handler.sendMessage(msg);
                            Looper.loop();
                        }
                    }).start();

좋은 웹페이지 즐겨찾기