Handler

Handler

1. ThreadLocal

線程本地存儲(chǔ)為使用相同變量的每個(gè)不同線程都創(chuàng)建不同的存儲(chǔ)艰管,每個(gè)線程雖然使用同一個(gè)變量彬呻,但是變量狀態(tài)互不干擾俭令。

  • 原理:
    每個(gè)線程有一個(gè)ThreadLocalMap 對(duì)象用來存儲(chǔ)該線程ThreadLocal類型的數(shù)據(jù)
class Thread implements Runnable {
  ...
      /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap 是自定義HashMap驻粟,以ThreadLocal 對(duì)象為key,要使用的數(shù)據(jù)為value

當(dāng)調(diào)用ThreadLocal的get/set方法的時(shí)候首先獲得該線程的threadLocals變量友扰,然后根據(jù)當(dāng)前的TreadLocal對(duì)象為key役电,在該線程中查找value赚爵。

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

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

對(duì)于一個(gè)線程來說,不同的ThreadLoacal對(duì)象都存在這個(gè)ThreadLocalMap 對(duì)象中法瑟,以ThreadLoacal對(duì)象為key查找對(duì)應(yīng)的值冀膝。

對(duì)于一個(gè)ThreadLoacal對(duì)象來說逮走,數(shù)據(jù)的拷貝存儲(chǔ)在不同線程的ThreadLocalMap 對(duì)象中揍堕,每次操作(set/get)先找出對(duì)應(yīng)線程的ThreadLocalMap 對(duì)象。由此實(shí)現(xiàn)多個(gè)線程中互不干擾的存儲(chǔ)和修改數(shù)據(jù)

Handler實(shí)現(xiàn)原理

1. 消息Message和消息隊(duì)列MessageQueue

(1)Message

Message使用享元設(shè)計(jì)模式梭域。定義:對(duì)象共享氓扛,避免創(chuàng)建多個(gè)對(duì)象

Message 不能直接new創(chuàng)建,需要調(diào)用其obtain方法

    Message message = Message.obtain();
    message.what = 100;
    mHandler.sendMessage(message);

Message.obtain 每次都是從Message對(duì)象池中選一個(gè)對(duì)象枯芬,而不是創(chuàng)建新的對(duì)象,當(dāng)一個(gè)消息處理完了之后论笔,在Looper.loop 中調(diào)用完msg.target.dispachMessage(msg)之后會(huì)調(diào)用recycleUnchecked,將Message對(duì)象放到對(duì)象池中千所。
Message對(duì)象池中的對(duì)象以鏈表形式存儲(chǔ)狂魔,每次obtain取出一個(gè)對(duì)象,sPoolSize--淫痰,每次recycleUnchecked 插入一個(gè)對(duì)象sPoolSize++最楷,sPoolSize最大為50.

Message.java

   /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }
 /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    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++;
            }
        }
    }

(1)MessageQueue

主要方法:

  • enqueueMessage 往消息隊(duì)列中插入一條消息,按時(shí)間排序待错,消息隊(duì)列鏈表形式
    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;
            //新消息時(shí)間最早籽孙,插入表頭,需要喚醒
            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.
                //p.target == null 是“同步分割欄”火俄,如果是同步分割欄犯建,也需要調(diào)整喚醒時(shí)間
                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.
            //喚醒隊(duì)列
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

needWake 表示最早的喚醒時(shí)間變了,需要喚醒隊(duì)列瓜客。

同步分割欄: 所謂“同步分割欄”适瓦,可以被理解為一個(gè)特殊Message,它的target域?yàn)閚ull谱仪。它不能通過sendMessageAtTime()等函數(shù)打入到消息隊(duì)列里玻熙,而只能通過調(diào)用Looper的postSyncBarrier()來打入。

“同步分割欄”就像一個(gè)卡子疯攒,卡在消息鏈表中的某個(gè)位置嗦随,當(dāng)消息循環(huán)不斷從消息鏈表中摘取消息并進(jìn)行處理時(shí),一旦遇到這種“同步分割欄”敬尺,那么即使在分割欄之后還有若干已經(jīng)到時(shí)的普通Message枚尼,也不會(huì)摘取這些消息了。請(qǐng)注意砂吞,此時(shí)只是不會(huì)摘取“普通Message”了姑原,如果隊(duì)列中還設(shè)置有“異步Message”,那么還是會(huì)摘取已到時(shí)的“異步Message”的,然后在執(zhí)行普通Message呜舒。

frameworks/base/core/java/android/os/Message.java

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}

源碼中 Choreographer中多次用到,為了保證繪制消息不被阻塞優(yōu)先執(zhí)行笨奠,防止繪制卡頓袭蝗。

frameworks/base/core/java/android/view/Choreographer.java

 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
 msg.setAsynchronous(true);
 mHandler.sendMessageAtFrontOfQueue(msg);
  • next 循環(huán)計(jì)算下一個(gè)消息的時(shí)間,當(dāng)消息隊(duì)列中沒有消息或者下一個(gè)消息需要等待一段時(shí)間時(shí)般婆,不會(huì)一直跑for循環(huán)到腥,而是進(jìn)入睡眠狀態(tài),當(dāng)有消息到來時(shí)蔚袍,消息隊(duì)列被喚醒乡范,查找新的消息并返回配名。
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();
                            }
            //沒有消息或者消息時(shí)間沒到。則進(jìn)入睡眠狀態(tài)晋辆,使用epoll_wait
            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;
                //如果是同步分割欄則直接找到下個(gè)異步消息
                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) {
                    //下個(gè)消息的時(shí)間還沒到渠脉,則計(jì)算睡眠時(shí)間
                    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 {
                    // 沒有消息 -1 是一直等待
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }


            ... ...

        }
    }

2. Looper

Looper 采用線程本地存儲(chǔ)方式ThreadLocal,一個(gè)線程只有一個(gè)Looper

構(gòu)造函數(shù)可以看出瓶佳,Looper里面定義本線程的消息隊(duì)列

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

Looper使用demo

    class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop();
        }
    }
  • Looper.prepare() 新建Looper對(duì)象芋膘,注意使用的是ThreadLocal
    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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.loop() 調(diào)用MessageQueue.next 得到消息,然后發(fā)給發(fā)消息的handler處理霸饲,最后回收消息recycleUnchecked
 public static void loop() {
     for (;;) {
         ... ...
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ... ... 
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ... ...
            msg.recycleUnchecked();
            ... ...
     }
 }

3. Handler處理消息

  1. msg.callback:先看是不是用Handler post方法發(fā)送的Runnable,如果是为朋,則先執(zhí)行
  2. mCallback: 采用Handler handler = new Handler(callback)方式新建Handler ,傳進(jìn)來的callback 參數(shù)即是mCallback厚脉,如果是這種方式习寸,則調(diào)用mCallback.handleMessage
  3. 最后調(diào)用該Handler的handleMessage
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市傻工,隨后出現(xiàn)的幾起案子霞溪,更是在濱河造成了極大的恐慌,老刑警劉巖精钮,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件威鹿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡轨香,警方通過查閱死者的電腦和手機(jī)忽你,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臂容,“玉大人科雳,你說我怎么就攤上這事∨迹” “怎么了糟秘?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)球散。 經(jīng)常有香客問我尿赚,道長(zhǎng),這世上最難降的妖魔是什么蕉堰? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任凌净,我火速辦了婚禮,結(jié)果婚禮上屋讶,老公的妹妹穿的比我還像新娘冰寻。我一直安慰自己,他們只是感情好皿渗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布斩芭。 她就那樣靜靜地躺著轻腺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪划乖。 梳的紋絲不亂的頭發(fā)上贬养,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音迁筛,去河邊找鬼煤蚌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛细卧,可吹牛的內(nèi)容都是我干的尉桩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼贪庙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蜘犁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起止邮,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤这橙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后导披,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屈扎,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年撩匕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹰晨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡止毕,死狀恐怖模蜡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扁凛,我是刑警寧澤忍疾,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站谨朝,受9級(jí)特大地震影響卤妒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜字币,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一荚孵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纬朝,春花似錦、人聲如沸骄呼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至隅茎,卻和暖如春澄峰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辟犀。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工俏竞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堂竟。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓魂毁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親出嘹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子席楚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容