Handler原理

Handler 原理

一鲸鹦、Handler消息發(fā)送機(jī)制

1. 發(fā)送消息

1.1 添加消息

調(diào)用Handler.sendMessageXX方法發(fā)送消息蔗坯,這些方法最終都會(huì)調(diào)到MessageQueueenqueueMessage方法中,MessageQueue使用一個(gè)優(yōu)先隊(duì)列來保存添加的Message對(duì)象零抬,執(zhí)行時(shí)間越早的Message放的越靠近隊(duì)頭自晰。

//加入新的頭結(jié)點(diǎn)
if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    //隊(duì)頭指針
    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;
    //遍歷鏈表,把最快執(zhí)行的msg放到表頭
    for (;;) {
        prev = p;
        p = p.next;
        if (p == null || when < p.when) {
            break;
        }
        if (needWake && p.isAsynchronous()) {
            needWake = false;
        }
    }
    //插入新的Message節(jié)點(diǎn)
    msg.next = p; // invariant: p == prev.next
    prev.next = msg;
}

這樣就把一個(gè)Message加入到了MessageQueue中扁远。下面要看的就是怎么樣取出消息。

1.2 取出消息

Looper.loop()方法中通過一個(gè)死循環(huán)來不斷的從MessageQueue中取出Message

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
//使本線程休眠
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());//默認(rèn)不是異步的
    }
    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) {
                //不是隊(duì)頭
                prevMsg.next = msg.next;
            } else {
                //隊(duì)頭
                mMessages = msg.next;
            }
            msg.next = null;
            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
            msg.markInUse();
            return msg;
        }
    } else {
        // No more messages.
        nextPollTimeoutMillis = -1;
    }

MessageQueue方法中也有一個(gè)for循環(huán)來從優(yōu)先隊(duì)列中找出一個(gè)符合條件的Message刻像。

  • 由于MessageQueue中保存的Message是按有限順序排列的所以第一個(gè)一定是最先執(zhí)行的畅买,先取出第一個(gè),然后判斷這個(gè)Message是否可以開始處理细睡,

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

    如果不可以谷羞,計(jì)算還差多少時(shí)間可以處理這個(gè)Message,并把計(jì)算的時(shí)間賦值給nextPollTimeoutMillis,這樣當(dāng)循環(huán)到下一次的時(shí)候就會(huì)調(diào)用nativePollOnce這個(gè)方法來使本線程休眠,當(dāng)休眠時(shí)間到了之后就重新取出一個(gè)Message然后返回湃缎,這樣犀填,MessageQueue.next方法就執(zhí)行結(jié)束,loop方法就能拿到一個(gè)新的Message.

  • Loop方法拿到Message之后判斷是否為空嗓违,如果是那loop方法死循環(huán)就結(jié)束九巡,相當(dāng)于Looper就停止工作。

    如果不為空就調(diào)用Message.target.dispatchMessage方法蹂季。在loop方法拿Message的過程中可能會(huì)因?yàn)?code>MessageQueue的休眠而導(dǎo)致卡主

  • Message.target是一個(gè)Hander的引用冕广,msg.target = this;HandlerenqueueMessage中會(huì)將當(dāng)前Handler設(shè)置為Message.target的引用蟀苛。所以這個(gè)方法最終也就是調(diào)用Handler.dispatchMessage方法慧妄,

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

    這個(gè)方法最終會(huì)調(diào)到handleMessage方法中,也就是在創(chuàng)建Handler實(shí)例時(shí)重寫的那個(gè)方法巢钓。這樣就能處理到這個(gè)Message涕滋。

[圖片上傳失敗...(image-1acb02-1593160818812)]

二睬辐、Looper與線程的數(shù)量關(guān)系

結(jié)論:每條線程只有一個(gè)Looper

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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中維護(hù)了一個(gè)ThreadLoacal的靜態(tài)變量,當(dāng)調(diào)用這個(gè)Looperprepare方法時(shí)會(huì)先判斷當(dāng)前的線程是否有一個(gè)Looper的實(shí)例宾肺,如果沒有就創(chuàng)建一個(gè)溉委。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal維護(hù)了一個(gè)類似于HashMap的數(shù)據(jù)結(jié)構(gòu)用線程號(hào)作為Key,這樣就保證了每條線程只會(huì)有一個(gè)對(duì)應(yīng)的實(shí)例爱榕。

所以,Looper是通過ThreadLocal來保證每條線程只有一個(gè)Looper實(shí)例坡慌。如果在同一個(gè)線程中多次調(diào)用prepare方法就會(huì)拋出異常黔酥。

三、Handler內(nèi)存泄露

在實(shí)例化Handler時(shí)使用匿名內(nèi)部類來重寫handleMessage方法洪橘,這個(gè)Handler實(shí)例就持有了外部類的引用(通常會(huì)是一個(gè)Activity)跪者,在sendMessage時(shí)又會(huì)使Message引用當(dāng)前Handler實(shí)例,這樣Message就會(huì)最終引用到Activity實(shí)例。由于Handler發(fā)送的消息存在延時(shí)機(jī)制導(dǎo)致線程可能休眠熄求,這樣休眠線程中的Message就會(huì)一直持有Activity的引用渣玲。并且在prepare時(shí)ThreadLocal會(huì)持有創(chuàng)建的Looper實(shí)例的引用,而靜態(tài)的ThreadLocal又會(huì)被Looper.class持有引用弟晚。Looper.class是一個(gè)GCRoot所以在可達(dá)性分析時(shí)就不會(huì)釋放Activity忘衍。

[圖片上傳失敗...(image-170ef4-1593160818812)]

四、Handler的使用

1. 主線程上Handler的使用

Looper源碼中可以看到需用調(diào)用loop方法才能才能從MessageQueue中的取出消息并處理卿城,但是在Activity中使用Handler時(shí)枚钓,我們并不需要去手動(dòng)的prepare來調(diào)用loop方法。因?yàn)樵?code>Activity在啟動(dòng)的時(shí)候已經(jīng)由Android自動(dòng)prepareMainLooper瑟押。

ActivityThread.main()中已經(jīng)調(diào)用了Looper.prepareMainLooper方法搀捷,這個(gè)main方法時(shí)整個(gè)應(yīng)用啟動(dòng)的入口方法,所以在這個(gè)時(shí)候就會(huì)init主線程中的Looper

2. 在子線程使用Looper

  • prepare()先調(diào)用prepare方法來在本線程創(chuàng)建一個(gè)Looper實(shí)例多望,
  • loop(),調(diào)用loop方法來從MessageQueue中取出數(shù)據(jù)
  • quit()/quitSafty()清空MessageQueue退出``Looper

五嫩舟、多個(gè)線程往MessageQueue中發(fā)消息如何保證線程安全

使用synchronized關(guān)鍵字對(duì)enqueueMessagenext加上當(dāng)前MessageQueue的鎖氢烘,這樣在多個(gè)線程中操作這個(gè)MessageQueue時(shí)就能保證每次只有一個(gè)線程能執(zhí)行。所以發(fā)送Message的delay時(shí)間由于等待鎖的原因不一定是精確的家厌。

六播玖、如何創(chuàng)建一個(gè)Message

通過Message.obtain()方法來獲取一個(gè)對(duì)象。Message中維護(hù)了一個(gè)Message對(duì)象的實(shí)例鏈表像街,當(dāng)調(diào)用obtain方法時(shí)就從鏈表中取出一個(gè)Message實(shí)例黎棠,并將這個(gè)Message的標(biāo)志位重置。然后返回這個(gè)Message實(shí)例镰绎。當(dāng)使用完一個(gè)Message之后會(huì)調(diào)用recycleUnCheck()方法來重置這個(gè)Message,并將其加入到鏈表中脓斩。這里使用到了享元設(shè)計(jì)模式。

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 = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    //最大保存50個(gè)
    synchronized (sPoolSync) {
        if (sPoolSize < 50) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

七 畴栖、quit和quitSafty的區(qū)別

1. quit

這個(gè)方法直接移除MessageQueue中所有的Message并將其重置随静。

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

2. quitSafty

這個(gè)不會(huì)移除當(dāng)前已經(jīng)到達(dá)執(zhí)行時(shí)間的Message,會(huì)讓讓其正常處理完成吗讶,只會(huì)移除沒有到達(dá)處理時(shí)間的Message燎猛。

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            //所有的消息都還不執(zhí)行,直接將其移除
            removeAllMessagesLocked();
        } else {
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    //n節(jié)點(diǎn)及之后的節(jié)點(diǎn)都還不到執(zhí)行的時(shí)間
                    break;
                }
                p = n;
            }
            p.next = null;
            //將還不到執(zhí)行時(shí)間的message全部移除
            do {
                p = n;
                n = p.next;
                //把target置為null
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

在執(zhí)行著兩個(gè)方法之后會(huì)調(diào)用nativeWake(mPtr)來喚醒在休眠的next方法照皆,這個(gè)方法如果返回一個(gè)空的message重绷,Looper就會(huì)推出循環(huán)。

[圖片上傳失敗...(image-3daee6-1593160818812)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膜毁,一起剝皮案震驚了整個(gè)濱河市昭卓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘟滨,老刑警劉巖候醒,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異杂瘸,居然都是意外死亡倒淫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門败玉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敌土,“玉大人,你說我怎么就攤上這事绒怨〈渴辏” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵南蹂,是天一觀的道長(zhǎng)犬金。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么晚顷? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任峰伙,我火速辦了婚禮,結(jié)果婚禮上该默,老公的妹妹穿的比我還像新娘瞳氓。我一直安慰自己,他們只是感情好栓袖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布匣摘。 她就那樣靜靜地躺著,像睡著了一般裹刮。 火紅的嫁衣襯著肌膚如雪音榜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天捧弃,我揣著相機(jī)與錄音赠叼,去河邊找鬼。 笑死违霞,一個(gè)胖子當(dāng)著我的面吹牛嘴办,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播买鸽,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼涧郊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了眼五?” 一聲冷哼從身側(cè)響起底燎,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弹砚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枢希,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桌吃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了苞轿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茅诱。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搬卒,靈堂內(nèi)的尸體忽然破棺而出瑟俭,到底是詐尸還是另有隱情,我是刑警寧澤契邀,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布摆寄,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏微饥。R本人自食惡果不足惜逗扒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望欠橘。 院中可真熱鬧矩肩,春花似錦、人聲如沸肃续。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽始锚。三九已至刽酱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疼蛾,已是汗流浹背肛跌。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留察郁,地道東北人衍慎。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像皮钠,于是被迫代替她去往敵國(guó)和親稳捆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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

  • 首先麦轰,我將Handler相關(guān)的原理機(jī)制形象的描述為以下情景: Handler:快遞員(屬于某個(gè)快遞公司的職員) M...
    夏天之靈閱讀 219評(píng)論 0 0
  • 我的簡(jiǎn)書:http://www.reibang.com/u/c91e642c4d90我的CSDN:http://...
    在代碼下成長(zhǎng)閱讀 723評(píng)論 2 0
  • Handler原理分析: 概念: handler是Android提供給我們用來更新UI的機(jī)制乔夯,同時(shí)也是一套消息處理...
    劉筱陽閱讀 2,840評(píng)論 0 2
  • Handler簡(jiǎn)單使用 1.使用靜態(tài)內(nèi)部類的方式繼承Handler并重寫接受的方法handleMessage。之所...
    Android小工ing閱讀 165評(píng)論 0 1
  • 久違的晴天款侵,家長(zhǎng)會(huì)末荐。 家長(zhǎng)大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了新锈。班主任說已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)甲脏。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,513評(píng)論 16 22