Android中的線程間通信(Handler與Looper)

Java中的線程回顧

在操作系統的概念里,進程是具有一定獨立功能的程序、它是系統進行資源分配調度的一個獨立單位。
在Java的虛擬機中,進程擁有自己獨立的虛擬地址空間(準確的說是方法區和堆內存)。
而線程則是CPU調度的基本單元,它沒有自己獨立的虛擬地址空間,它依附于進程的虛擬地址空間。在Java虛擬機中,線程擁有的不過是PC計數器和方法棧而已。
在Android系統中,進程之間的通信稱為IPC,它主要由Binder完成,線程之間由于共享地址空間,各線程都可以對對象進行操作,因此線程間的通信機制更為簡單,由輕便的Handler和Looper完成。

線程獨有數據

由于同一個進程的線程共享資源,因此,如何保證數據在某線程的私有性變為極為重要,忽略這點會造成著名的多線程同步問題。在Java中,線程被封裝為Thread類,一種很常見的思維是維護一個共有的哈希表,然后將線程的私有變量存儲起來,當線程需要取用數據時,從哈希表中讀取私有數據。

    val threadData = ConcurrentHashMap<Thread,Any>()

但是如果這樣的話,程序員就需要自己在程序中維護這么一個表,且不說自己能不能記住到底存儲了什么,光是表的維護就已經很麻煩了,而且表的更新特別耗費資源。因此,比較好的方法是將數據存儲封裝在Thread這個類的內部,由于每一個線程都是一個Thread對象,它們需要這個值的時候直接讀取其內部的值即可。Android采用了ThreadLocal這個類,實現了在指定線程中存儲數據的功能。
首先我們來看一下ThreadLocal類怎么使用:

    fun test()
    {
        val localAge = ThreadLocal<Int>() // 申請一個Int類型的線程私有變量
        localAge.set(10) //主線程中修改值
        Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
        Thread({
            localAge.set(12345) //其他線程中修改值
            Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
        }, "Thread#2").start()
        Thread({
            localAge.set(18) //其他線程中修改值
            Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
        },"Thread#3").start()

        localAge.set(-33) //主線程中第二次修改值
        Log.d(Thread.currentThread().toString(),localAge.get().toString()+"-> $localAge")
    }

輸出結果:

01-09 21:33:50.274 11935-11935/cn.suiseiseki.www.androidstudy D/Thread[main,5,main]: 10-> java.lang.ThreadLocal@4368aaa8
01-09 21:33:50.274 11935-11950/cn.suiseiseki.www.androidstudy D/Thread[Thread#2,5,main]: 12345-> java.lang.ThreadLocal@4368aaa8
01-09 21:33:50.279 11935-11935/cn.suiseiseki.www.androidstudy D/Thread[main,5,main]: -33-> java.lang.ThreadLocal@4368aaa8
01-09 21:33:50.279 11935-11951/cn.suiseiseki.www.androidstudy D/Thread[Thread#3,5,main]: 18-> java.lang.ThreadLocal@4368aaa8

可以看到,在不同線程中訪問同一個Thread對象,其獲取的值不一樣,這就是ThreadLocal的奇妙之處
下面我們分析一下ThreadLocal的主要方法:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

其實代碼已經很明顯了,就是通過不同的Thread對象獲取其Thread內部維護的ThreadLocalMap,然后查詢是否有這個參數,如果有就返回這個參數的值,如果沒有則返回null.對于set方法來說,首先查找線程是否創建了ThreadLocalMap,如果沒有則創建之,然后就是正常的map操作了
如果你感興趣的話,可以再深入了解一下ThreadLocalMap這個類,它內部采用弱引用維護了一個哈希表,這里不再詳細介紹。
總之,現在我們已經知道,多個線程之間確實可以互不干擾地存儲和修改某些數據,這是線程間通信的基礎。

MessageQueue:消息隊列

Android中的線程間通信涉及到四個組件:Handler,Looper,Message,MessageQueue

MessageQueue稱為消息隊列,得力于上文所述的ThreadLocal類,現在每一個線程可以擁有自己的MessageQueue了。MessageQueue通過一個單鏈表來維護一個消息隊列,因為單鏈表在插入和刪除過程中有性能優勢。作為一個隊列,它自然有兩個方法,用于入隊(插隊)的enqueueMessage方法和出隊的next方法

    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) {
            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;
    }

可以看到,enqueueMessage方法先檢查Message的合法性,然后再根據Message的when屬性將Message插入單鏈表中。

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                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.
            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;
        }
    }

next方法是一個無限循環的方法,如果MessageQueue隊列中沒有消息,此方法會堵塞,直到新的消息到來位置,它會將消息取出,然后再鏈表中刪除它。

Looper:MessageQueue的使用者

有了消息隊列之后,我們還需要一個使用消息隊列的對象:Looper。從Looper的名字可以看出,它代表某種循環的意思。為了讓某個線程能不斷地處理消息(Message),我們就需要為線程創建Looper:

        Thread(
                {
                    Looper.prepare()
                    val handler = Handler()
                    Looper.loop()
                }
        ).start()

注意這里需要調用Looper的靜態方法。當然,我們也可以隨時停止Looper,在線程中調用quit或者quitSatety即可:

                    Looper.myLooper().quit()
                    Looper.myLooper().quitSafely()

可以看到,Looper的主要方法都是靜態方法,便于我們在線程的任意位置調用它們
Looper內部最重要的方法就是loop()方法,它是一個死循環,跳出循環的唯一方式是其MessageQueue的next返回了null(正常情況下應該是被堵塞的)。當Looper調用quit或者quitSafely時,它會通知消息隊列退出,使其返回null值。
如果MessageQueue的next返回了Message消息,它會將這個消息交給Handler去處理。

Handler:消息的發送與處理者

Handler的工作就是發送消息和處理消息,它主要通過兩個方法:sendMessage和post(runnable)去發送消息
它們的底層過程就是向消息隊列中插入一條消息,通過Looper接收后又交給了它自己的dispatchMessage方法去處理:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

首先,它會檢查消息本身的callback是否為空(這適用于post方法發送的runnable對象消息),如果不為空則由這個runnable對象接管執行。
如果為空,再檢查本Handler自身的mCallback是否為空,如果不為空則由這個對象代為處理。
如果mCallback也為空,則在自身的handleMessage方法中執行了,這是常規操作,自己在Handler的子類中實現這個方法即可

Handler還有一個很方便的構造方法,傳入一個指定Looper來構造Looper:

    public Handler(Looper looper) {
        this(looper, null, false);
    }

比如 val handler = Handler(Looper.getMainLooper()),快速在其他線程中獲取主線程的handler

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容