Handler 源碼解析 (Android Learning Note)

工作流程簡述

Handler 涉及的知識點有,Thread嵌器,Loop,MessageQueue 谐丢,Message爽航。這些對象相互關聯(lián),相互配合完成 Handler 的工作乾忱。
大體流程示意圖如下:


Handle

簡單介紹下:
Handler 通過 post Runnable 或 sendMessage 方法來發(fā)送要處理的 Message 到消息隊列(MessageQueue)讥珍,這個消息隊列是由 Looper 創(chuàng)建,管理的窄瘟。Thread 中可以調用 Looper 的 loop() 方法衷佃,loop() 會無限循環(huán),從消息隊列中取出待處理的消息蹄葱,交給 Handler(Message 的 Target) 來處理氏义,處理完之后锄列,Looper 繼續(xù)從隊列中取下一個消息再給 Handler (Message 的 Target),直到隊列中沒有消息惯悠,退出循環(huán)邻邮。這個 Looper 是在線程中創(chuàng)建出來并運行的,被 Handler 持有克婶。

下面會結合一些概念筒严,源代碼和示例代碼介紹流程中的一些細節(jié),幫助理解情萤。

Handler 概念

Handler鸭蛙, (以下翻譯自官方文檔)Handler 可以發(fā)送和處理與它相關的 MessageQueue 中的 Message 和 Runnable。每個 Handler 實例都與一個單獨的線程和這個線程的消息隊列關聯(lián)筋岛。在創(chuàng)建一個新 Handler 時娶视,它與這個線程和消息隊列綁定到了一起,它將消息和任務發(fā)送至消息隊列睁宰,并在消息離開消息隊列時執(zhí)行它們歇万。

Handler 有兩個主要的用處:

  1. 在未來某個時間點去調度 Message 和 Runnable。
  2. 在不同的線程中將任務添加到消息隊列(線程間通信)勋陪。

可以使用 post(Runnable) , postAtTime(Runnable, long) , postDelayed(Runnable, long) , sendEmptyMessage(int) , sendMessage(Message) , sendMessageAtTime(Message, long) , and sendMessageDelayed(Message, long) 這些方法調度消息贪磺。 post 版本的方法可以將 Runnable 對象加到消息隊列中, sendMessage 版本的方法可以將綁定數(shù)據(jù)的 Message 對象用 Handler 的 handleMessage(Message) 方法處理(必需實現(xiàn) Handler 的子類)诅愚。

當應用程序的進程被創(chuàng)建的時候寒锚,主線程會運行起來一個消息隊列用來處理程序的高級對象(activities, broadcast receivers 等等 )和它們創(chuàng)建的任意 windows。你也可以創(chuàng)建自己的線程和主線程通過 Handler 進行通信违孝。

Looper刹前,MessageQueue 的創(chuàng)建和 Looper 的存取

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

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

這是 Handler 的兩個構造方法,從方法中可以看到雌桑,Handler 必須引用 Looper(mLooper) 和 MessageQueue(mQueue)對象喇喉,Looper 是怎么創(chuàng)建?如何傳給 Handler 被引用的校坑?可以先從下面的幾段代碼入手了解拣技。

public void mainThreadHandler(View view) {
    final Handler handler = new Handler();

    final Runnable task = new Runnable() {
        @Override
        public void run() {
            String msg = "Handler Post Runnable 線程名: " + Thread.currentThread().getName()
                    + (isMainThread()?", 是":", 不是") + "主線程";
            makeToast(msg);
        }
    };

    Runnable task2 = new Runnable() {
        @Override
        public void run() {
            String msg = "thread 線程名: " + Thread.currentThread().getName()
                    + (isMainThread()?", 是":", 不是") + "主線程";
            makeToast(msg);
            handler.post(task);
        }
    };
    Thread thread = new Thread(task2, "Sub Thread01");
    thread.start();
}

主線程創(chuàng)建的 Handler ,不用傳入 Looper耍目,因為主線程已經(jīng)調用了 prepareMainLooper(); 方法膏斤,擁有了 Looper。而 Handler 是通過前面第一種構造方法中 mLooper = Looper.myLooper(); 代碼獲得的 Looper邪驮。

現(xiàn)在來看 Looper.myLooper(); 中的代碼莫辨,了解獲取 Thread 中 Looper 的過程。

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

//public class ThreadLocal<T>
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Looper.myLooper(); 調用了 sThreadLocal.get() 方法,獲取當前線程的 ThreadLocalMap 對象沮榜,這個對象就是用來存儲 Looper 的盘榨,通過 ThreadLocal 做為 key 獲取到對應的實體,從而獲取我們想要的值蟆融,Looper较曼。

哪 Looper 又是怎樣存儲的呢?

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

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 方法振愿,只不過主線程調用的是 prepareMainLooper() ,子線程調用 prepare(boolean quitAllowed) 弛饭。
必要的校驗后冕末,new 出一個新 Looper 對象,使用 sThreadLocal.set(value) 方法存儲該 Looper 到當前線程中侣颂。這就和之前獲取 Looper 的方法對應了起來档桃。

MessageQueue 是在創(chuàng)建 Looper 過程中創(chuàng)建出來的,并被 Looper 所引用憔晒。

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

至此藻肄,Handler 相關的幾個重要的部分都被創(chuàng)建了出來。

將消息添加到消息隊列

下面的代碼拒担,使用的是傳入 Looper 的 Handler 構造方法嘹屯。

public void testHandler(View v) {
    final Runnable postTask = new Runnable() {
        @Override
        public void run() {
            String msg = "post: " + (isMainThread() ? "是" : "不是") + "主線程, 線程名:" + Thread.currentThread().getName();
            SecondActivity.this.makeToast(msg);
        }
    };

    final HandlerThread outerThread = new HandlerThread("HandlerThread");
    outerThread.start();

    final Runnable threadTask = new Runnable() {
        @Override
        public void run() {
            /* 1 */
            Looper.prepare();
            final Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == 1) {
                        String msgStr = "send: " + (isMainThread() ? "是" : "不是") + "主線程, 線程名:" + Thread.currentThread().getName();
                        SecondActivity.this.makeToast(msgStr);
                    }
                }
            };
            handler.post(postTask);
            Message message = new Message();
            message.what = 1;
            handler.sendMessage(message);
            Looper.loop();

            /* 2
            final Handler handler = new Handler(outerThread.getLooper());
            handler.post(postTask);
            */

            /* 3
            final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(postTask);
            */
        }
    };
    Thread t = new Thread(threadTask, "subThread");
    t.start();
}

注釋1,傳入的 Looper 是在 subThread 這個線程中創(chuàng)建的从撼,postTask 和 sendMessage 都會在線程 “subThread” 中執(zhí)行州弟。
注釋2,傳入的 Looper 是在 HandlerThread 這個線程中創(chuàng)建的低零,postTask 會在線程 “HandlerThread” 中執(zhí)行婆翔。
注釋3,傳入的 Looper 是在主線程中創(chuàng)建的掏婶,所以 postTask 會在主線程中執(zhí)行啃奴。
以上線程中創(chuàng)建 Looper 的方法都是一樣的,都遵從前面介紹的過程雄妥,handler 不同的構造方法大同小異最蕾,目的都是獲得 Looper。

Post Runnable 其實和 SendMessage 是一樣的老厌,最終還是封裝成 Message 發(fā)送出去揖膜,供 Handler 處理。

//Handler Class
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;
}

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

//Message Queue
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;
}

代碼雖長梅桩,但能展示清楚發(fā)送消息的過程壹粟。先看 post(Runnable r) ,里面調用的是 sendMessageDelayed ,原來 post 方法實際上也是發(fā)送消息趁仙,但是 Message 對象在哪呢洪添,再看 getPostMessage(Runnable r) 方法中,創(chuàng)建了一個 Message 對象雀费,并把 Runnable 傳給了它的 callback干奢。至此,我們明白了 post 和 send 兩種方法的區(qū)別盏袄。
sendMessageDelayed 方法調用 sendMessageAtTime 忿峻,而 sendMessageAtTime 中則是獲取到消息隊列 mQueue 調用 enqueueMessage ,將 Message 消息添加到了隊列里辕羽。
MessageQueue 中是使用鏈表的數(shù)據(jù)結構來管理隊列的逛尚, mMessages 是整個鏈表的第一個 Message 元素,下一個 Message 則是 mMessages.next刁愿,enqueueMessage(Message msg, long when) 方法的作用就是按照 when 時間來排序绰寞,插入新的 Message到整個 Message 鏈表中。
這個就是整個將消息添加到消息隊列的過程铣口。

消息是怎么 one by one 取出來處理的

...
final Runnable threadTask = new Runnable() {
    @Override
    public void run() {
    Looper.prepare();
    final Handler handler = new Handler();                          handler.post(postTask);
    Looper.loop();
    }
};
Thread t = new Thread(threadTask, "subThread");
t.start();

線程中如果只調用 Looper.prepare(); 是無法使 Handler 處理消息的滤钱,為什么呢?
因為沒有讓這個 Looper 運轉起來脑题,去取消息給 Handler件缸。只有使用了 Looper.loop(); 方法才能讓 Handler 正常的處理消息。
哪就來看看 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
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

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

loop() 方法中啟動了一個無限循環(huán)停团,通過 queue.next 方法獲取一個消息,如果獲取的是 null 則意味著消息隊列沒有了消息掏熬,退出循環(huán)佑稠。
如果有消息,則調用 msg.target.dispatchMessage(msg); 方法旗芬,msg.target 就是 Handler 舌胶,在前面的 sendMessage 源代碼中可以看到 msg.target = this;

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

private static void handleCallback(Message message) {
    message.callback.run();
}

該方法中疮丛,如果有 callback 就調用 callback 的 run 方法幔嫂,這對應的就是 handler.post(runnable)。而如果沒有 callback 則調用 Handler 的 handleMessage 方法誊薄,這對應的就是 handler.sendMessage(message) 方法履恩,需要 Handler 重寫 handleMessage 方法來處理消息。

完成消息的處理后呢蔫,則將該消息的數(shù)據(jù)重置切心,標記成已使用飒筑,放入重用池中等待重用。至此一個消息取出處理就完成绽昏,再發(fā)起新一輪循環(huán)协屡。

消息的重用

創(chuàng)建消息時,如果使用 Message message = Message.obtain(); 方法創(chuàng)建 Message 會比直接 new 出來的更省內存全谤,原因就是 Message 是可以復用的肤晓。

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

如果有 sPool 重用池(實際上就是 Message 單向鏈表的頭元素),則取出认然,重置狀態(tài)后 return 出去补憾。sPoolSize 減一。沒有重用池則 new Message卷员。

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

在介紹 loop() 的時候盈匾,處理完消息,會調用該 msg 的 recycleUnchecked 方法子刮,這個方法的作用看源代碼可知,重置消息的數(shù)據(jù)窑睁,標記為已使用挺峡,并且當重用池中消息的個數(shù)小于規(guī)定的 MAX_POOL_SIZE 時,則將重置后的消息插入到重用池鏈表中担钮,置于第一個位置橱赠。
至此,消息的復用也介紹完了箫津。

總結

通過這個簡單的示意圖狭姨,能夠清楚的知道整個 Handler 的運作流程。通過不同部分的源代碼苏遥,了解到不同流程部分的細節(jié)饼拍,線程如何創(chuàng)建 Looper,并啟動 looper田炭。Handler 如何發(fā)送消息到消息隊列师抄,looper 如何從消息隊列中去除消息交給 Handler 處理,消息如何被復用等教硫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末叨吮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瞬矩,更是在濱河造成了極大的恐慌茶鉴,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件景用,死亡現(xiàn)場離奇詭異涵叮,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門围肥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剿干,“玉大人,你說我怎么就攤上這事穆刻≈枚” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵氢伟,是天一觀的道長榜轿。 經(jīng)常有香客問我,道長朵锣,這世上最難降的妖魔是什么谬盐? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诚些,結果婚禮上飞傀,老公的妹妹穿的比我還像新娘。我一直安慰自己诬烹,他們只是感情好砸烦,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绞吁,像睡著了一般幢痘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上家破,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天颜说,我揣著相機與錄音,去河邊找鬼汰聋。 笑死门粪,一個胖子當著我的面吹牛,可吹牛的內容都是我干的烹困。 我是一名探鬼主播庄拇,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼韭邓!你這毒婦竟也來了措近?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤女淑,失蹤者是張志新(化名)和其女友劉穎瞭郑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸭你,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡屈张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年擒权,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阁谆。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡碳抄,死狀恐怖,靈堂內的尸體忽然破棺而出场绿,到底是詐尸還是另有隱情剖效,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布焰盗,位于F島的核電站璧尸,受9級特大地震影響,放射性物質發(fā)生泄漏熬拒。R本人自食惡果不足惜爷光,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望澎粟。 院中可真熱鬧蛀序,春花似錦、人聲如沸活烙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓣颅。三九已至倦逐,卻和暖如春譬正,著一層夾襖步出監(jiān)牢的瞬間宫补,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工曾我, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粉怕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓抒巢,卻偏偏與公主長得像贫贝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛉谜,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容