從源碼簡要分析Handler的原理

Handler我們經常會用到,一般可以用來處理延時任務,或者進行異步耗時操作的同時更新UI等。

在使用過程中,不知道大家是否和我以前初學的時候一樣有這樣的疑惑:

一般我們都是在UI線程新建static hander對象,並且實現了它的HandleMessage()方法,然後在其他任何地方通過sendMessage()方法發送的消息,最後都會被handleMessage()所處理,包括子線程裏調用sendMessage()也可以。那麼從sendMessage()到handleMessage(),這其中是經歷了怎樣的過程呢?

帶着這個問題,我們來找源碼進行分析。(看這篇文章之前,最好已經聽過,或者瞭解過Handler,Looper, MessageQueue, Message這幾個東西,如果沒有,那就得多看看其他資料了。)

一、Handler的構造方法做了什麼

首先,我們日常使用時,必須得先創建一個handler對象,那麼我們看下它的默認構造方法,這是一定會被調用的:

    public Handler() {
        this(null, 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 that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

這個構造方法裏,首先對可能出現的內存泄漏問題進行了警告。什麼情況下可能出現內存泄漏呢?上面的判斷裏表明,如果這個Handler類是匿名類,內部類,或者本地類,則必須是static類型的,否則就會有警告。

(注:爲什麼必須是static類型呢?因爲在Java裏,匿名類,內部類,或者本地類如果不是靜態的,則會隱式地持有一份對外部類的引用。比如我們在一個activity裏新建了handler的匿名內部類,則它會隱式地持有此activity的引用。那麼存在這樣的隱患:有可能handler發送的message還沒來得及處理,activity就被finish掉了。本來activity被finish之後是要回收的,但是因爲handler還持有對它的引用,activity對象就無法被回收了,造成內存泄漏。)

好了繼續看上面的構造方法,警告完內存泄漏之後,接下來是對Handler的幾個成員變量做了賦值。其中mCallback和mAsynchronous都是傳入的值,一個爲null,一個是flase。

重點是mLooper和mQueue。
mLooper是個Looper類型的成員變量,它通過Looper.myLooper()得到。這個Looper.myLooper()是怎樣的實現先不管,這裏只要先了解Handler類裏包含了一個Looper類型的成員變量和一個MessageQueue類型的成員變量mQueue,並且在構造方法中作了初始化就好。

二、Handler如何發送Message

好了,Handler對象創建完畢,接下來就可以使用了。一般我們都是調用它的sendMessage方法。Handler有多個同類型的方法,但是最終調用都是一個,這裏就不細說了。下圖表示了它們的調用關係:

最終調用的都是Handler的enqueueMessage方法(其中傳入的queue就是handler的mQueue成員變量):

Handler還有個post()方法,傳入的是個Runnable對象,其最終也是同樣的嵌套調用。
Handler的post()方法的解釋點這裏

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //給要處理的message綁定了目標
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

上面做了個msg.target = this的操作,把發送的message的target賦值爲當前的handler對象,這裏後面會用到。

三、MessageQueue是如何處理Message的

繼續追MessageQueue的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) {
        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;
}

“enqueue message”這個詞,從意思上看,就是把消息放入隊列。實際上上面這個方法做的也就是這件事,把handler發送的message按時間順序插入消息隊列,也就是MessageQueue中。

具體是怎麼做的呢?MessageQueue類裏有個Message類型的成員變量mMessages,表示當前需處理的消息,而每一個Message對象都有一個Message類型的next成員變量,表示它的“下一個消息”,而這些消息的插入又是按時間排序的,因此所有這些消息就構成了一個按時間排序的隊列。
經過上面的處理,新消息就插入了消息隊列合適的位置。

四、Looper對MessageQueue做了什麼處理

然後消息一個個排好順序了,那怎麼處理它們呢?我們知道平時都是Handler的handleMessage()進行處理的,所以搜一下,搜到handler的這個dispatchMessage()方法;

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

從上面還暫時看不出來之前我們分析的消息隊列和Handler是怎麼聯繫的,不過可以看到的是消息處理之前是怎麼分發的:

1. 如果message對象定義了callback的話,則由callback進行處理。

2. 否則,如果定義handler的時候,傳入了callback(這個callback類和message的callback類不是同一個類),則由這個callback調用其handleMessage()方法進行處理。

3. 僅當callback的handleMessage()返回false時,handler的handleMessage()纔會得到執行。

好了言歸正傳,繼續追查dispatchMessage()是在哪裏被調用的,找到不止一處,但是實際我們想要的是這個: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;

    // 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(); // 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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(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();
    }
}

五、ThreadLocal保證了Handler的MessageQueue和Looper的MessageQueue,是同一個對象

在上面方法裏,最前面幾行,得到了一個MessageQueue 對象:

    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;

但是這個MessageQueue對象,和之前hander發送的消息最後插入的那個MessageQueue,是不是同一個呢?這就要看final Looper me = myLooper()這句,拿到的Looper對象是哪來的了,看Looper類的myLooper()源碼:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

sThreadLocal是Looper類的一個ThreadLocal類型的成員變量:

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

ThreadLocal在這裏的意義是,不同的線程,通過get()得到的ThreadLocal對象是不同的,而同一個線程裏拿到的是同一個ThreadLocal對象。
既然有get(),那就應該有set(),繼續看:

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

Looper的構造方法:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

ThreadLocal的set()是在Looper的prepare()方法裏被調用的。
到了這裏,再回頭梳理下,Handler的構造方法裏,就確定了它的mQueue,怎麼確定的呢?回頭再看下handler的構造方法裏這麼幾行:

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;

這裏得到mLooper和mQueue的方法,和Looper的loop()方法得到Looper對象和MessageQueue對象的方法,是一模一樣的,那麼很確定了:

Handler的構造方法裏,確定了它的mQueue,並且最後傳入了sendMessage()方法,然後由這個mQueue調用其enqueueMessage(),把消息插入隊列。

然後Looper類的loop()方法裏,再次獲取到這個mQueue對象,然後逐個取出其中的消息進行分發,交由消息的callback,handler的callback或者handleMessage()方法進行處理。

六、新線程裏如何使用Handler

還有個問題:Looper類的prepare()和loop()是在什麼時候被調用的呢?這個就不分析源碼了。答案是:

  1. 如果我們需要在新建的線程裏創建handler處理任務,則必須手動調用Looper.prepare()和Looper.loop()方法,而且必須像下面這樣,:
class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();// 步驟1

        mHandler = new Handler() {// 步驟2
            public void handleMessage(Message msg) {
                //消息處理邏輯
            }
        };

        Looper.loop(); // 步驟3
    }
}

步驟1,2,3的順序是不能亂的。爲什麼呢?上面我們貼的源碼已經說明了,Looper對象和MessageQueue對象被創建是在Looper.prepare()裏,因此這個必須放前面。而Looper.loop()是個循環執行的過程,如果一直有消息的話就會一直運行,所以要放在後面。
2. 那爲什麼平時在UI線程創建handler對象的時候,不需要調用Looper.prepare()和Looper.loop()方法呢?
實際上,這兩個方法,源碼裏已經調用了,在ActivityThread的main()方法裏,可自行查看。

###七、Hander的post()方法的分析: 使用Handler進行任務的處理時,可以用post()方法傳入一個Runnable對象進行處理。post()方法源碼: ``` public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } ```
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

可見,傳入的Runnable對象,被包裝成了一個Message對象,作爲這個Message對象的callback。然後還是通過sendMessage的方法發送消息。

所以要注意,如果handler對象是在UI線程創建的,則這個Runnable對象的run()方法裏不能做耗時操作,因爲run()也會運行在UI線程。

總結:

1. Handler用來發送message和處理message.
2. MessageQueue把handler發送的message按時間順序排序。
3. Looper不斷循環從MessageQueue中取出下一個message,然後交給對應的Handler進行分發和處理。
4. 同一個線程裏,可以定義多個Handler,但是隻會有一個Looper和一個MessageQueue。

另,創建新的Message對象時,用Message.obtain()方法,比new Message()要好,因爲obtain()是從消息池取出被recycle()的消息,如果消息池爲空再創建新消息。因此效率會高很多,也節省資源。

另,回答一個常見的問題:

應用主線程中的Looper.loop()一直無限循環爲什麼不會造成ANR?

答:主線程Looper會從消息隊列循環調用Queue.next()讀取消息,當讀完所有消息時,主線程會阻塞在Queue.next()方法這裏。此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據,主線程纔會被喚醒繼續工作。

當消息隊列有了消息,管道寫入數據時,主線程即被喚醒,從管道讀取數據。當消息讀取完畢,會再次休眠。

因此主線程大多數時候都是處於休眠狀態,loop的循環並不會對CPU性能有過多的消耗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章